I am creating a laravel package and have this setup at /packages/mycompany/mypackage.
I want to use a RouteServiceProvider to register some routes and specifically pass in a namespace so I don't have to fully qualify my controllers when using this file.
I understand that to load my routes from a package I should use loadRoutesFrom in the boot method of my main ServiceProvider:
public function boot()
{
$this->loadRoutesFrom(__DIR__.'/../../routes/api.php');
}
This works fine and I can use the routes defined there however I have to fully namespace my controllers. Ideally, I want to be able to call these simply with no namespacing (and not by wrapping them in namespace) - much like how the routes work for a Laravel app (I'm using a package).
I saw that the 'namespace' method in \App\Providers\RouteServiceProvider can define a namespace to be used in a given routes file so have been using my own RouteServiceProvider with the following method:
public function map()
{
Route::domain('api'.env('APP_URL'))
->middleware('api')
->namespace($this->namespace)
->group(__DIR__.'/../../routes/api.php');
}
(As an aside - if I use the map method here - is the loadRoutesFrom call above necessary?)
My route service provider is registered via the main Service Provider:
public function register()
{
$this->app->register(\MyCompany\MyPackage\Providers\RouteServiceProvider::class);
}
However when I access my app, I get a complaint that my controller doesn't exist - which can be fixed by properly namespacing my controllers in routes/api.php which is what I want to avoid.
Route::get('/somepath', 'MyController#show');
How do I leverage namespace so I don't have to fully qualify my controllers?
loadRoutesFrom is a helper that checks to see if the routes are cached or not before registering the routes.
You can start defining a group of routes then call loadRoutesFrom in that group in your provider's boot method:
Route::group([
'domain' => 'api'. config('app.url'), // don't call `env` outside of configs
'namespace' => $this->namespace,
'middleware' => 'api',
], function () {
$this->loadRoutesFrom(__DIR__.'/../../routes/api.php');
});
Or if you prefer the method style of setting the attributes:
Route::domain('api'. config('app.url'))
->namespace($this->namespace)
->middleware('api')
->group(function () {
$this->loadRoutesFrom(__DIR__.'/../../routes/api.php');
});
This is how Laravel Cashier is doing it:
https://github.com/laravel/cashier/blob/10.0/src/CashierServiceProvider.php#L86
Related
I am migrating some app functionality into a Laravel package. The package includes Models, Controllers and routes. I am using Laravel 9
Everything is working except my Models are not biding to the routes so the models are not being automatically resolved.
// Route
Route::get('/medium/{medium}',
[\ArtMoi\Http\Controllers\MediumController::class, 'fetch']
)->name("get-medium");
Model does not automatically load. In the controller below $medium is null. Route worked when part of the app, but fails when included via a package.
// MediumController
use ArtMoi\Models\Medium;
...
public function fetch(Request $request, Medium $medium)
{
$this->authorize('view', $medium);
return response()->json($medium);
}
Everything works if I don't try to automatically load the model
// This works but not the desired approach
use ArtMoi\Models\Medium;
public function fetch(Request $request, $id)
{
$medium = Medium::find($id);
$this->authorize('view', $medium);
return response()->json($medium);
}
In my package ServiceProvider I provide the routes with
public function boot()
{
Route::group(['prefix' => 'api'], function () {
$this->loadRoutesFrom(__DIR__ . '/../routes/api.php');
});
}
I tried to explicitly bind the Model and the route by adding the following to a RouteServiceProvider but it has no effect.
// RouteServiceProvider.php
use ArtMoi\Models\Medium;
...
public function boot()
{
Route::model('medium', Medium::class);
}
This is the first time I've moved resource type routes to a Package and feel like there is something I am missing. The only difference with the package from the original app is moving models from App\Models to the package namespace of ArtMoi\Models. Models function as expected when performing queries or other functions and the routes have no conflicts.
Can you test this solution? Modify your boot method.
public function boot()
{
Route::group([
'middleware'=>['bindings'],
'prefix' => 'api'
], function () {
$this->loadRoutesFrom(__DIR__ . '/../routes/api.php');
});
}
That bindings middleware is here and is registered by default in web and api middleware.
Solution found here
I hope this works, I didn't test it.
Found that I needed to add the SubstitudeBindings middleware to my routes.
Updated my ServiceProvider to load routes with
use Illuminate\Routing\Middleware\SubstituteBindings;
...
public function boot()
{
Route::group(['prefix' => 'api', 'middleware' => [SubstituteBindings::class]], function () {
$this->loadRoutesFrom(__DIR__ . '/../routes/api.php');
});
}
This question already has answers here:
Error “Target class controller does not exist” when using Laravel 8
(27 answers)
Closed 1 year ago.
When I run `php artisan route:list' I get an error it can't find the RegisterController from the auth scaffolding.
Illuminate\Contracts\Container\BindingResolutionException : Target class [App\Http\Controllers\Auth\RegisterController] does not exist.
at C:\xampp\htdocs\antheap\vendor\laravel\framework\src\Illuminate\Container\Container.php:805
801|
802| try {
803| $reflector = new ReflectionClass($concrete);
804| } catch (ReflectionException $e) {
> 805| throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
806| }
807|
808| // If the type is not instantiable, the developer is attempting to resolve
809| // an abstract type such as an Interface or Abstract Class and there is
Exception trace:
1 Illuminate\Foundation\Console\RouteListCommand::Illuminate\Foundation\Console\{closure}(Object(Illuminate\Routing\Route))
[internal]:0
2 ReflectionException::("Class App\Http\Controllers\Auth\RegisterController does not exist")
C:\xampp\htdocs\antheap\vendor\laravel\framework\src\Illuminate\Container\Container.php:803
Which makes sense considering the RegisterController.php file is in Controllers/Web/Auth/ and its namespace is namespace App\Http\Controllers\Web\Auth;
I'm guessing it's looking for the wrong place because of default routing in the illuminate framework. However all the other Auth controllers are functioning fine. I'm not keen on moving everything just to make the list route:list command happy, though I kinda need it not crashing right now to help fix some other issue.
I've changed the Auth helper in web.php and added the auth routes:
// helper class generating all routes required for user authentication
// (authentication, registration and password resetting)
Auth::routes(['verify' => true, 'register' => false]);
Route::get('/login', 'Auth\LoginController#showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController#login');
Route::get('/logout', 'LoginController#logout')->name('logout');
Route::group(['middleware' => 'auth'], function () {
Route::get('/password/confirm', 'Auth\ConfirmPasswordController#showConfirmForm')->name('password.confirm');
Route::post('/password/confirm', 'Auth\ConfirmPasswordController#confirm');
});
Route::post('/password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
Route::get('/password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.request');
Route::post('/password/reset', 'Auth\ForgotPasswordController#reset')->name('password.update');
Route::get('/password/password/reset/{token}', 'ResetPasswordController#showResetForm')->name('password.reset');
Route::group(['middleware' => 'guest'], function () {
Route::get('/password/register', 'Auth\RegisterController#showRegistrationForm')->name('register');
Route::post('/password/register', 'Auth\RegisterController#register');
});
However, this still doesn't allow for route:list to work correctly. The weird thing is I can comment out routes from about and they would still work normally (I assume covered by the default ones). Using route:clear doesn't change anything.
I can also add or remove the Auth\ in front of the Controller name, this doesn't keep it from working , nor will it fix route:list
But the app is definitely using it because if I change the URI suffix (like 'login' to 'logintest', it will show that in the browser address.
One thing that I forget to mention is that in RouteServiceProvide.php I added the \Web namespace (I don't know how route:list deals with that?)
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* #return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace . '\Web')
->group(base_path('routes/web.php'));
}
This because I had to break up the route file at some point into multiple ones with their own namespaces. And I put the Auth inside the Web routes due to many of its routes using limited to no middleware.
Problem is that taking Auth from the web folder and namespace just breaks way more than just route:list.
Ok I solved it, but not in a pretty way by moving back all the auth controllers to the folder route:list was looking for and just adding another separate routing file solely for auth
RouteServiceProvider.php
protected function mapAuthRoutes()
{
Route::middleware('web')
// ->namespace($this->namespace . '\Auth')
->group(base_path('routes/auth.php'));
}
I commented out the \Auth namespace because with testing it didn't seem to work properly
And then routes/auth.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
/*
|--------------------------------------------------------------------------
| Auth Routes
|--------------------------------------------------------------------------
|
*/
// helper class generating all routes required for user authentication
// (authentication, registration and password resetting)
// Auth::routes(['verify' => true, 'register' => false]);
Route::get('/login', 'App\Http\Controllers\Auth\LoginController#showLoginForm')->name('login');
Route::post('/login', 'App\Http\Controllers\Auth\LoginController#login');
Route::post('/logout', 'App\Http\Controllers\Auth\LoginController#logout')->name('logout');
Route::group(['middleware' => 'auth'], function () {
Route::get('/password/confirm', 'App\Http\Controllers\Auth\ConfirmPasswordController#showConfirmForm')->name('password.confirm');
Route::post('/password/confirm', 'App\Http\Controllers\Auth\ConfirmPasswordController#confirm');
});
Route::post('/password/email', 'App\Http\Controllers\Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
Route::get('/password/reset', 'App\Http\Controllers\Auth\ForgotPasswordController#showLinkRequestForm')->name('password.request');
Route::post('/password/reset', 'App\Http\Controllers\Auth\ForgotPasswordController#reset')->name('password.update');
Route::get('/password/password/reset/{token}', 'App\Http\Controllers\Auth\ResetPasswordController#showResetForm')->name('password.reset');
Route::group(['middleware' => 'guest'], function () {
Route::get('/password/register', 'App\Http\Controllers\Auth\RegisterController#showRegistrationForm')->name('register');
Route::post('/password/register', 'App\Http\Controllers\Auth\RegisterController#register');
});
I had to completely write out the paths for the controllers or it wouldn't work
And the namespace used in the auth controllers (which is also the file path):
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
I don't feel like this was the cleanest solution, and I would love to know why route:list doesn't seem to understand the namespacing used before. But at least it's working again now.
I'm making a new Service called Factures in App\Services\Factures.
I created the \App\Services\Factures\FacturesServiceProvider:
public function register() {
$this->app->bind('factures', function ($app) {
return new Facture;
});
}
public function boot() {
// laod Routes
$this->loadRoutesFrom(__DIR__ .'/Http/routes.php');
// load Views
$this->loadViewsFrom(__DIR__ . '/views', 'factures');
}
I registered my provider everything works fine expect the Auth::user() in returns me null in the views and the routes.php.
How can I get access to the Auth() in custom service?
This post resolved my problem: User Auth not persisting within Laravel package
I figure out that Laravel apply to the default routes/web.php file a middleware called 'web' And doesn't apply this group to external package routes loaded via service provider's.
So my routes in the custom file should be in web middleware:
Route::group(['middleware' => ['web']], function () {
Route::get('testing-services', function(){
dd(Auth::user());
// output is valid
});
});
What is the correct way to authenticate all routes except login and register when I apply auth middleware in all controllers? Is there a way to apply auth middleware in one place and exclude login, register routes?
You can group all your authenticated routes like following, laravel provides a default middleware for auth and guest users
Route::group(['middleware' => ['auth']], function () {
Route::get('home', 'HomeController#index');
Route::post('save-user', 'UserController#saveUser');
Route::put('edit-user', 'UserController#editUser');
});
The above route names are just made up, please follow a proper naming convention for your routes and controllers. Also read about middlewares over here and about routing over here
you can apply middlewares in the routes.php file, what you need to do is to put all your routes on a group, and add the middleware 'auth' ( except the Auth::routes() which are already configured), for example :
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {
// Uses first & second Middleware
});
Route::get('user/profile', function () {
// Uses first & second Middleware
});
});
more information can be found in the docs: https://laravel.com/docs/5.7/routing#route-group-middleware
You can add middleware to your whole web.php route file by adding the middleware to your routes mapping in RouteServiceProvider.
Go to app/Providers/RouteServiceProvider.php and in mapWebRoutes(), change middleware('web') to middleware(['web', 'auth']):
protected function mapWebRoutes()
{
Route::middleware(['web', 'auth'])
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
This is (not?) totally unrelated but here's an example of a clean way to handle a lot of route files instead of throwing all your routes into a single web.php file:
Create a new method mapAdminRoutes():
protected function mapAdminRoutes()
{
Route::middleware(['web', 'auth:admin'])
->namespace('App\Http\Controllers\Admin')
->name('admin.')
->group(base_path('routes/admin.php'));
}
Map it:
public function map()
{
$this->mapWebRoutes();
$this->mapAdminRoutes(); // <-- add this
...
}
Create an admin.php file in your routes folder, then create your routes for Admin:
<?php
use Illuminate\Support\Facades\Route;
// This route's name will be 'admin.dashboard'
Route::get('dashboard', 'DashboardController#dashboard')->name('dashboard');
// This route's name will be 'admin.example'
Route::get('example', 'ExampleController#example')->name('example');
...
Now you can configure everything in 1 place, like prefix, name, middleware and namespace.
Check php artisan route:list to see the results :)
I am trying to require auth before reaching the create resource and have seperated my resource routes accordingly.
Route::resource('posts','PostsController', ['except' => ['store','edit','update','destroy','create']]);
Route::group(['before'=>'auth'], function() {
Route::resource('posts','PostsController', ['only' => ['store','edit','update','destroy','create']]);});
Now for some reason when going to posts/create it redirects me to the show route. The auth is working fine on all other routes, and when create is removed it asks for login upon posting the create, but obviously I would like this section to be off limits regardless.
I would suggest you use controller filters instead.
This simplifies the routing to this:
Route::resource('posts', 'PostsController');
And in your post controller's constructor, you can configure the filter:
public function __construct()
{
$this->beforeFilter('auth', array('except' => array('index', 'show')));
}