How to get subdomain inside RouteServiceProvider map() method while phpunit testing? - laravel

Is there a way to get the subdomain while running phpunit test in Laravel?
And more specific in this method:
app/Providers/RouteServiceProvider.php
/**
* Define the routes for the application.
*
* #return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
// inside here get subdomain while running a phpunit test??
So if I make some dummy test like this:
/** #test */
public function user_can_see_welcome_page()
{
$response = $this->call('GET', 'https://subdomain.domain.com');
$response->assertStatus(200);
}
I want to get the subdomain inside that map() method of RouteServiceProvider

In order to change the domain you can add it to the phpunit.xml to set it globally:
<php>
<env name="APP_URL" value="https://subdomain.domain.com"/>
</php>
But discussing your problem in the comments, this is the actual solution to your problem:
Multiple route files:
You could split each subdomain in its own map method and route file and register them in your RouteServiceProvider.
for each subdomain:
protected function mapSubDomainRoutes()
{
Route::group([
'middleware' => 'web',
'domain' => 'subdomain.domain.com',
'namespace' => $this->namespace,
], function () {
require base_path('routes/subdomain.domain.php');
});
}
Single route file:
Or if you have everything inside one route file you can wrap the routes inside a group:
Route::group(['domain' => ['subdomain.domain.com']], function () {
// domain specific routes
});

Related

Use Explicit or Implicit Model Binding on Routes in Laravel Package

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');
});
}

Laravel 6.0 php artisan route:list returns “Target class [App\Http\Controllers\SessionsController] does not exist.”

I am using Laravel 6.0 and I try to list all my routes with artisan route:list, but it fails and returns:
Illuminate\Contracts\Container\BindingResolutionException : Target
class [App\Http\Controllers\SessionsController] does not exist.
at /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/Container.php:806
802|
803| try {
804| $reflector = new ReflectionClass($concrete);
805| } catch (ReflectionException $e) {
> 806| throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
807| }
808|
809| // If the type is not instantiable, the developer is attempting to resolve
810| // 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\SessionsController does not exist")
/home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/Container.php:804
3 ReflectionClass::__construct("App\Http\Controllers\SessionsController")
/home/vagrant/code/vendor/laravel/framework/src/Illuminate/Container/Container.php:804
Up to now I just have a very simple web.php routes file:
Route::get('/', function () {
return view('index');
});
Route::prefix('app')->group(function () {
// Registration routes
Route::get('registration/create', 'RegistrationController#create')->name('app-registration-form');
});
// Templates
Route::get('templates/ubold/{any}', 'UboldController#index');
Any idea how I could debug this issue?
I was upgrading from Laravel 7 to Laravel 8 (Laravel 8 is still a few days in development) and also had this issue.
The solution was to use a classname representation of the controller in the route:
So in web.php instead of
Route::get('registration/create', 'RegistrationController#create')
it is now:
Solution 1: Classname representation
use App\Http\Controllers\RegistrationController;
Route::get('/', [RegistrationController::class, 'create']);
Solution 2: String syntax
or as a string syntax (full namespaces controller name):
Route::get('/', 'App\Http\Controllers\RegistrationController#create');
Solution 3: Return to previous behaviour
As this should issue should only happen if you upgrade your application by creating a brand new laravel project you can also just add the default namespace to the RouteServiceProvider:
app/Providers/RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
/* ... */
/** ADD THIS PROPERTY
* If specified, this namespace is automatically applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* #var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::middleware('web')
->namespace($this->namespace) // <-- ADD THIS
->group(base_path('routes/web.php'));
Route::prefix('api')
->middleware('api')
->namespace($this->namespace) // <-- ADD THIS
->group(base_path('routes/api.php'));
});
}
/* ... /*
}
See also https://laravel.com/docs/8.x/routing#basic-routing or https://laravel.com/docs/8.x/upgrade (search for "Routing").
For those who have similar issue with Illuminate\Contracts\Container\BindingResolutionException : Target class [<className>] does not exist. message, this also could be helpful:
composer dump-autoload
Run this command
php artisan config:cache
In my case it was solved by running
php artisan optimize:clear
php artisan config:cache
The optimize:clearcommands clears everything
In my case it was a matter of Linux's file name case sensitivity. For a file named IndexController, having Indexcontroller will work in windows but not in Linux
Simply add the following line in app->Providers->RouteServiceProvider.php
protected $namespace = 'App\\Http\\Controllers';
In my case same error occurred because of forward slash / but it should be backward slash \ in defining route,
it happens when you have controller in folder like as in my case controller was in api Folder, so always use backward slash \ while mentioning controller name.
see example:
Error-prone code:
Route::apiResource('categories', 'api/CategoryController');
Solution code:
Route::apiResource('categories', 'api\CategoryController');
This is the perfect answer, I think:
Option 1:
use App\Http\Controllers\HomepageController;
Route::get('/', [HomepageController::class, 'index']);
Option 2:
Route::get('/', 'App\Http\Controllers\HomepageController#index');
Alright i got similar problem, i was trying to be smart so i wrote this in my web.php
Route::group([
'middleware' => '', // Removing this made everything work
'as' => 'admin.',
'prefix' => 'admin',
'namespace' => 'Admin',
],function(){
});
All i had to do is just to remove all the unnecessary/unused option from group. That's all.
try to correct your controller name
my route was
Route::get('/lien/{id}','liensControler#show');
and my controller was
class liensController extends Controller
{
// all the methods of controller goes here.
}
I did all these
1: php artisan config:cache
2: checked for controller name spellings.
3: composer dump-autoload
4: Just changed the forward / slash to backward \ in route.
4th one worked for me.
Now You Can use controller outside controller folder
use App\Http\submit;
Route::get('/', [submit::class, 'index']);
Now my controller excited in http folder
You have to change in controller file something
<?php
namespace App\Http;
use Illuminate\Http\Request;
use Illuminate\Http\Controllers\Controller;
class submit extends Controller {
public function index(Request $req) {
return $req;
}
}
You can define a route to this controller action like so:
use App\Http\Controllers\UserController;
Route::get('user/{id}', [UserController::class, 'show']);
I am running Laravel 8.x on my pc.
This error gave me headache. To recreate the error, this is what I did:
First I created a controller called MyModelController.php
Secondly, I wrote a simple function to return a blade file containing 'Hello World', called myFunction.
Lastly, I created a Route:
Route::get('/','MyModelController#myFunction');
This did not work.
This was how I solved it.
First you would have to read the documentation on:
(https://laravel.com/docs/8.x/releases#laravel-8)
At the 'web.php' file this was the Route i wrote to make it work:
use App\Http\Controllers\MyModelController;
Route::get('/', [MyModelController::class, 'myFunction']);
on Larave 7 I had the same issue.
I checked the spelling of the controller name.
I recognize I have the wrong spelling in the "AlbumContoller" and I rename it to "AlbumController". so I forgot "r"
after I renamed the file and controller name and controller name in the web.php
Route::resource('albums', 'AlbumsController');
everything worked well
So You Don't need these two:
1- use App\Http\Controllers\IndexContoller;
2- Route::get('/', [MyModelController::class, 'myFunction']);
you have to specify full path to the class
Previously it was
Route::get('/wel','Welcome#index');
Now it has changed to
use App\Http\Controllers\Welcome;
Route::get('wel',[Welcome::class,'index']);
I had this problem while leaving an empty middleware class in my middleware groups by mistake :
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:100,1',
'bindings',
'localization',
'' // Was empty by mistake
],
];
replace-
Route::resource('/admin/UserOff','admin/UsersController');
with-
Route::resource('/admin/UserOff','admin\UsersController');
forward / with \
I had the same problem but with a middleware controller. So finally I linked that middleware in kerner.php file. It is located at app\Http\Kernel.php
I have added this line in route middleware.
'authpostmanweb' => \App\Http\Middleware\AuthPostmanWeb::class
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'authpostmanweb' => \App\Http\Middleware\AuthPostmanWeb::class
];
just check web.php and see lower and upper case letters
On top of ensuring that you use the updated route syntax:
Route::get('/', [RegistrationController::class, 'create']);
make sure that at the top of your routes/web.php the controller is included in the name space like:
use App\Http\Controllers\RegistrationController;

Laravel Broadcast - Combining multiple middleware (web, auth:api)

I am using Laravel Event Broadcast and Pusher to utilize websockets both on my API and Web. If I try them individually, both work fine. What I mean is:
Broadcast::routes(['middleware' => 'web']); // this works for my Laravel website
Broadcast::routes(['middleware' => 'auth:api']); // this works for my api
However, if I want to use both at the same time like this:
Broadcast::routes(['middleware' => ['auth:api', 'web']]); // doesn't work
... it crashes for both, which I suspect that it is assuming I am trying to enable for both auth:api && web middlewares.
Is there a way to use an OR kind of statement for this (auth::api || 'web')? What if I want to use both at the same time and if it passes one middleware, it bypasses the middleware.
Please note that I am using Laravel Passport for my api.
Or is there a way to combine and creating a mixed middleware for both (which will essentially check for either api or web)? So I can use something like this maybe:
Broadcast::routes(['middleware' => 'broadcast']); // or auth:broadcast
Update:
As far as I understand, if I create a new Middleware called broadcast, I can do:
class BroadcastMiddleware() {
public function handle() {
$web = Auth::guard('web')->user();
if ($web) {
return response()->json($web);
}
$api = Auth::guard('api')->user();
if ($api) {
return response()->json($api);
}
return response()->json('Unauthorized.', 500);
}
}
But then how do I change /broadcasting/auth route? If I try this:
Route::post('/realtime/auth', function(){
return true;
})->middleware('broadcast');
This returns the user object info, however instead, it should return something like: auth:"374f7ff42b7da877aa35:c29addedec281b418331a61dc3cfc74da8b108222565fa4924a8..."
Why not just use something like this in the BroadcastServiceProvider? This creates two separate endpoints with separate middleware assigned.
Broadcast::routes(['middleware' => 'web']);
Broadcast::routes(['prefix' => 'api', 'middleware' => 'api']);
I finally figured out how to do it.
I am not sure if it is the best way of achieving this, and I'd highly appreciate any improvements.
How I achieved is created a new middleware for 'web' and left the other one as it it. Here are the steps.
1) In 'BroadcastServiceProvider', left only auth:api guard for Broadcast::routes(['middleware' => 'auth:api']);.
This way, Laravel's auth:api method for authenticating broadcasting works as expected.
2) Created a middleware called "Broadcast" and mapped it in Kernel.php like so:
'broadcast' => \App\Http\Middleware\Broadcast::class
and the Broadcast.php middleware looks like this:
public function handle($request, Closure $next)
{
$web = Auth::guard('web')->user();
if ($web) {
return response()->json(\Illuminate\Support\Facades\Broadcast::auth($request));
}
return response()->json('Unauthorized.', 500);
}
3) Created a unique route other than Laravel's /broadcasting/auth in my routes>web.php
Route::post('/guard/broadcast/auth', function(\Illuminate\Support\Facades\Request $req){
return true;
})->middleware('broadcast');
4) And then only on my blade, I use it like so:
<script>
let pusher = new Pusher("{{ env('PUSHER_APP_KEY') }}", {
cluster: 'us2',
encrypted: true,
auth: {
headers: {
'X-CSRF-TOKEN': "{{ csrf_token() }}"
}
},
authEndpoint: '{{ env('APP_URL') }}' + '/guard/broadcast/auth',
});
let channel = pusher.subscribe('private-channel.{{ Auth::user()->id }}');
channel.bind('my-event', addMessage);
function addMessage(data) {
console.log(data);
}
</script>
I'm preferable just using middleware that extends to both auth:api and web middlewares.
like what I posted in here: https://github.com/tlaverdure/laravel-echo-server/issues/266#issuecomment-365599129. So, I just maintenance 1 middleware if I wanted to change it in the future
BroadcastServiceProvider
if (request()->hasHeader('authorization')){
Broadcast::routes(['middleware' => 'auth:api']);
} else {
Broadcast::routes();
}
It is better to use prefix approach for achieve multiple authorization types.
If you will use a middleware - it is just redundant middleware.
If you will use if block (as on a code snippet below): you will face problem with routes caching, it will return 403 error becuase Laravel should cache a route with a set of middlewares.
if (request()->hasHeader('authorization')){
Broadcast::routes(['middleware' => 'auth:api']);
} else {
Broadcast::routes();
}
You even may register separate service providers for web and api to split responsibilities and it will work.
For Web
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['web']);
require base_path('routes/channels.php');
}
}
And for Api
class ApiBroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:api']]);
require base_path('routes/dam-channels.php');
}
}

Protecting some routes with auth middleware but leaving others unprotected

I have a protected route group:
Route::group(['prefix' => 'member', 'middleware' => 'auth'], function () {
Route::get('/')->name('member.home')->uses('MemberController#index');
Route::get('show')->name('member.show')->uses('MemberController#show');
// ...various additional protected member routes...
});
However, I have two routes that should not be protected:
Route::get('member/pay')->name('member.pay')->uses('MemberController#pay');
Route::get('member/confirm/{payment}')->name('member.confirm')->uses('MemberController#confirm');
So long as 'middleware' => 'auth' is applied to that separate route group, ALL of the member prefixed routes are covered by it even if I don't place them in the group.
I've tried moving the unprotected routes above and below the protected route group but I still get 401 unauthorized unless I remove the middleware entirely.
This isn't impacting any of my other routes...only the ones prefixed by member.
How can I exclude these two routes from auth?
One approach would be to remove the middleware assignment from the Route group and instead assign the middleware in your MemberController constructor. That way you can exclude which methods should not have it applied, like so:
class MemberController extends Controller
{
/**
* Instantiate a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth')->except(['pay', 'confirm']);
}
}
Use nested groups like this.
Route::group([ 'prefix' => 'member' ], function() {
// Protected routes
Route::group([ 'middleware' => 'auth' ], function() {
Route::get('/', 'MemberController#index')->name('member.home');
Route::get('show', 'MemberController#show')->name('member.show');
// ...various additional protected member routes...
});
// Non protected routes
Route::get('pay', 'MemberController#pay')->name('member.pay');
Route::get('confirm/{payment}', 'MemberController#confirm')->name('member.confirm');
// ...various additional non protected member routes...
});
how about
Route::group(['prefix' => 'member'], function () {
Route::get('/')->name('member.home')->uses('MemberController#index')->middleware('auth');
Route::get('show')->name('member.show')->uses('MemberController#show')->middleware('auth');
//
Route::get('member/pay')->name('member.pay')->uses('MemberController#pay');
Route::get('member/confirm/{payment}')->name('member.confirm')->uses('MemberController#confirm');
});

Laravel authenticated dynamic subdomain routing

I'm trying to get authenticated subdomain routing working for some specific variable subdomains:
app.example.com
staging.app.example.com
testing.app.example.com
These should be guarded by the auth middleware. They all essentially reference app.example.com but for different environments.
Everything that hits these domains should go to the guest routes:
example.com
staging.example.com
testing.example.com
This is what I've tried so far...
Created this middleware to prevent the subdomain parameter from messing up other routes and to allow successful authentication to redirect to app.example.com:
class Subdomain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$route = $request->route();
$subdomain = $route->parameter('subdomain');
if (!empty($subdomain) && preg_match('/^(staging|testing)\.(app.\)?/', $subdomain, $m)) {
\Session::put('subdomain', $m[1]);
}
$route->forgetParameter('subdomain');
return $next($request);
}
}
Added this to Kernel.php:
protected $routeMiddleware = [
'subdomain' => \App\Http\Middleware\Subdomain::class,
];
Contents of routes.php:
Route::group(['domain' => '{subdomain?}example.com', 'middleware' => 'subdomain'], function () {
// Backend routes
Route::group(['middleware' => 'auth'], function () {
Route::get('/', ['as' => 'dashboard', 'uses' => 'Controller#dashboard']);
// ...various other backend routes...
});
// Frontend routes
Route::auth();
Route::get('/', function () {
return view('frontend');
});
});
When I access any route, I can trace that nothing hits the subdomain middleware...it just routes to the 404 page.
How would I make this work in Laravel 5.2?
Since the goal of my setup was to allow handling certain subdomain groups with optional environment prefixes, I handled it in the following way.
I dropped the Subdomain class as being unnecessary.
I added this to the .env file so that each environment can have it's own domain so the local dev server still works independent of the staging and production server:
APP_DOMAIN=example.dev
On production and staging it would simply be:
APP_DOMAIN=example.com
Within config/app.php I added:
'domain' => env('APP_DOMAIN', null),
I added these methods to \App\Http\Controllers\Controller:
public static function getAppDomain()
{
return (!in_array(\App::environment(), ['local', 'production']) ? \App::environment() . '.' : '') . 'app.' . config('app.domain');
}
public static function getAppUrl($path = '', $secure = false)
{
return ($secure ? 'https' : 'http') . '://' . static::getAppDomain() . ($path ? '/' . $path : '');
}
Within Auth\AuthController.php I added this to handle redirects to the app.example.com from example.com even if prefixed with staging or testing:
public function redirectPath()
{
if (\Auth::check()) {
return redirect()->intended(static::getAppUrl())->getTargetUrl();
} else {
return $this->redirectTo;
}
}
New contents of routes.php:
// Backend routes
Route::group(['domain' => Controller::getAppDomain(), 'middleware' => 'auth'], function () {
Route::get('/', ['as' => 'dashboard', 'uses' => 'Controller#dashboard']);
// ...various other backend routes...
});
// Frontend routes
Route::auth();
Route::get('/', function () {
return view('frontend');
});
Hope this helps if anyone tries similar!

Resources