I am currently doing an upgrade from Laravel 7 to 8, and then from 8 to 9. I am getting this error when running composer update:
Array to string conversion
at vendor/laravel/framework/src/Illuminate/Routing/RouteFileRegistrar.php:35
31▕ public function register($routes)
32▕ {
33▕ $router = $this->router;
34▕
➜ 35▕ require $routes;
36▕ }
37▕ }
38▕
+4 vendor frames
5 routes/api/features.php:21
Something seems to have changed between versions with the group function. Here is the routes file:
Route::prefix('features')->middleware('api', 'admin')->group([ [HasFeature::feature(Features::featureFlags)]], function () {
Route::post('', FeatureFlagController::method('createFeature'));
Route::patch('/{uuid}/client/{client_id}', FeatureFlagController::method('addClientToFeature'));
Route::delete('/{uuid}/client/{client_id}', FeatureFlagController::method('removeClientFromFeature'));
Route::get('{client_id?}', FeatureFlagController::method('getAllFeatures'));
Route::post('/clear/{client_id?}', FeatureFlagController::method('clearFeatureCache'));
Route::patch('{uuid}', FeatureFlagController::method('updateFeature'));
Route::get('{uuid}', FeatureFlagController::method('getFeature'));
});
The issue appears to be with the group function, looks like an array with another array is being passed in. This is a codebase that is still rather new to me, so I'm trying to figure out what is happening.
Here is the middleware file of HasFeature:
<?php
namespace App\FeatureFlags\Middleware;
use App\Facades\Client;
use Closure;
class HasFeature
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $featureKey = null)
{
if (!Client::hasFeature($featureKey)) {
abort('403', "Forbidden");
}
return $next($request);
}
public static function feature(String $feature)
{
return static::class . ":" . $feature;
}
}
So it appears, that we are calling this static method feature within two arrays. I'm not sure 100% what is happening here, but it seems to be causing some issues with the Laravel 8 upgrade.
Does anyone know what the array within another array is doing as the first parameter in this route group method? And how I can fix this?
Thanks
This commit can be helpful, I assume that for newer Laravel version this code:
$router = $this;
require $routes;
was changed to this:
(new RouteFileRegistrar($this))->register($routes);
Related
I'm trying to get settings from the database and put them in the config,
my function need the user id so it can bring his settings only,
in the service provider ( boot function ) there is no authentication yet, can you please advise me to the right place to run my function, please note that I need it to run before the view get rendered because there are settings for the layout inside it, this is my function :
// public static becouse it's inside Class//
public static function getAppSettings(){
if (!config('settings') && Auth::check()) {
$user_id = Auth::user()->id;
$settings = AppSettings::where('user_id', $user_id)->get()->all();
$settings = Cache::remember('settings', 60, function () use ($settings) {
// Laravel >= 5.2, use 'lists' instead of 'pluck' for Laravel <= 5.1
return $settings->pluck('value', 'key')->all();
});
config()->set('settings', $settings);
}else{
// this is for testing//
dd('no');
}
}
without the auth, it can work inside the service provider ( boot function ) but it will bring all settings for all the users.
You can create middleware for this.Middleware calls after routes and before controller
php artisan make:middleware Settings
This will create below class
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Settings
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
// exicute your logic here
return $next($request);
}
}
You can call your method inside handle and before next
You can read more about this in
https://laravel.com/docs/8.x/middleware
Hi please help me with the following,
on Laravel 5.5
I have the following routes:
This one works:
Route::delete('/delete-comment/{id}', 'CommentController#destroy');
This one does not work as I'm using the same method as the above route and does not have the first parameter 're_id' which is not required:
Route::delete('/your-template/{re_id}/delete-comment/{id}', 'CommentController#destroy');
The method being:
public function destroy($id)
{
//do something
}
I want to use the same method without the first parameter for the sub route 're_id'.
I do not need this kind of solution, since I want to use the same function for both routes.
public function destroy($re_id= '' $id)
{
//do something
}
Is there a way to ignore the first parameter 're_id' on the route or a more generic way to use a slug on the first fragment on the route like:
Which btw does not work:
Route::delete('/{slug?}/delete-comment/{id}', 'CommentController#destroy');
In PHP generally the optional parameter/s MUST be at the end...
for example this will cause a Fatal Error in PHP v7.1^
function test($first = null, $second)
{
echo $first .' '.$second;
}
test('string');
In your case I would try it like this (not sure if it will work)
Route::delete('/delete-comment/{id}/{slug?}', 'CommentController#destroy');
public function destroy($id, $re_id = null)
{
//do something
}
If anyone gets stuck on this, I got a solution:
1.- Create a config file 'route.php'
<?php
return [
'filters' => [
// Routes
'your-template/{re_id}/delete-comment/{id}',
'your-template/{re_id}/update-comment/{id}',
'article' => [
// Route arguments {name}
're_id',
]
]
];
2.- Create a Middleware with the command:
php artisan make:middleware RouteArgumentsFilterMiddleware
<?php
namespace App\Http\Middleware;
use Closure;
class RouteArgumentsFilterMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$config = config('route.filters');
if (in_array($request->route()->uri, $config)) {
foreach ($config['article'] as $argument) {
$request->route()->forgetParameter($argument);
}
}
return $next($request);
}
}
3.- On your web.php file add the middleware to the needed routes:
Route::delete('/your-template/{re_id}/delete-comment/{id}', 'CommentController#destroy')->middleware('param_filter');
Route::put('/your-template/{re_id}/update-comment/{id}', 'CommentController#update')->middleware('param_filter');
4.- run composer dump-autoload
Then the desired parameter will be ignored when sent to the controller.
In my app I provided prefix e.g. en which my site loads as domain.xyz/en now this is working, but what I need to add is:
force redirect when user visits websites without any prefix:
e.g. user try to load domain.xyz this domain must redirect to domain.xyz/en (default prefix) for the first time in order to site loads, but later if user choose another lang it can be set to that. r.g. domain.xyz/es
Question
What should I write in my middleware to achieve that redirect?
I've tried to use:
1-$request->route().parameters();
and
2-$request->route().getPrefix();
no luck.
code
<?php
namespace App\Http\Middleware;
use Closure;
class PrefixMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
}
Update
Here is how my web.php looks like:
$route_prefix = \Config::get('app.route_prefix');
Route::group(['middleware' => 'verified', 'prefix' => $route_prefix.'/'], function () use ($route_prefix){
Route::get('/', 'HomeController#welcome')->name('homepage');
});
Update 2
Based on comments suggestion now I have something like:
public function handle($request, Closure $next)
{
$prefix = \Config::get('app.route_prefix');
\App::setLocale($prefix);
return $next($request.'/'.$prefix);
}
and it returns:
Call to a member function setUserResolver() on string
Solved
I used this tutorial and it fixed my problem
I wrote a localization middleware in Laravel using the LaravelGettext package which looks like this:
<?php
namespace App\Http\Middleware;
use Closure;
class Locale {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
if ($request->method() === 'GET') {
$segment = $request->segment(1);
if (!in_array($segment, config('laravel-gettext.supported-locales'))) {
$segments = $request->segments();
$fallback = session('locale') ?: config('laravel-gettext.fallback-locale');
$segments = array_prepend($segments, $fallback);
return redirect()->to(implode('/', $segments));
}
session(['locale' => $segment]);
LaravelGettext::setLocale($segment);
}
return $next($request);
}
}
I am routing into the middleware via:
Route::prefix('{lang?}')->middleware('locale')->group(function () {
...
}
Running through the middleware gives me this error though:
"Class 'App\Http\Middleware\LaravelGettext' not found"
So I figured I might have to import the LaravelGettext package manually by adding:
use Xinax\LaravelGettext\LaravelGettext;
Which now gives me this Exception:
"Non-static method Xinax\LaravelGettext\LaravelGettext::setLocale() should not be called statically"
Which makes me wonder: Is there even a valid option to access the package inside a middleware? Or did I drive into a design flaw here?
Well, it just came to me that I had to import the Facade, not the actual class itself. So adding
use Xinax\LaravelGettext\Facades\LaravelGettext;
made it finally work!
It is common that errors from Authentication and CSRF arise when running phpunit.
In the TestCase we use:
use WithoutMiddleware;
The problem is when forms fail, it usually comes back with a Flash Message and Old Input. We have disabled all middleware so we have no access to Input::old('username'); or the flash message.
Furthermore our tests of this failed form post returns:
Caused by
exception 'RuntimeException' with message 'Session store not set on request.
Is there a way to enable the Session Middleware and disable everything else.
The best way I have found to do this isn't by using the WithoutMiddleware trait but by modifying the middleware you want to disable. For example, if you want to disable the VerifyCsrfToken middleware functionality in your tests you can do the following.
Inside app/Http/Middleware/VerifyCsrfToken.php, add a handle method that checks the APP_ENV for testing.
public function handle($request, Closure $next)
{
if (env('APP_ENV') === 'testing') {
return $next($request);
}
return parent::handle($request, $next);
}
This will override the handle method inside of Illuminate\Foundation\Http\Middleware\VerifyCsrfToken, disabling the functionality entirely.
Laravel >= 5.5
As of Laravel 5.5, the withoutMiddleware() method allows you to specify the middleware to disable, instead of disabling them all. So, instead of modifying all of your middleware to add env checks, you can just do this in your test:
$this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);
Laravel < 5.5
If you're on Laravel < 5.5, you can implement the same functionality by adding the updated method to your base TestCase class to override the functionality from the framework TestCase.
PHP >= 7
If you're on PHP7+, add the following to your TestCase class, and you'll be able to use the same method call mentioned above. This functionality uses an anonymous class, which was introduced in PHP7.
/**
* Disable middleware for the test.
*
* #param string|array|null $middleware
* #return $this
*/
public function withoutMiddleware($middleware = null)
{
if (is_null($middleware)) {
$this->app->instance('middleware.disable', true);
return $this;
}
foreach ((array) $middleware as $abstract) {
$this->app->instance($abstract, new class {
public function handle($request, $next)
{
return $next($request);
}
});
}
return $this;
}
PHP < 7
If you're on PHP < 7, you'll have to create an actual class file, and inject that into the container instead of the anonymous class.
Create this class somewhere:
class FakeMiddleware
{
public function handle($request, $next)
{
return $next($request);
}
}
Override the withoutMiddleware() method in your TestCase and use your FakeMiddleware class:
/**
* Disable middleware for the test.
*
* #param string|array|null $middleware
* #return $this
*/
public function withoutMiddleware($middleware = null)
{
if (is_null($middleware)) {
$this->app->instance('middleware.disable', true);
return $this;
}
foreach ((array) $middleware as $abstract) {
$this->app->instance($abstract, new FakeMiddleware());
}
return $this;
}
You can use trait in test:
use Illuminate\Foundation\Testing\WithoutMiddleware;
Laravel >= 5.7
I might be late, but what I've figured it out:
$this->withoutMiddleware([
'email-verified', //alias does NOT work
EnsureEmailIsVerified::class //Qualified class name DOES WORK
]);
The following worked for me:
use WithoutMiddleware;
public function setUp(): void
{
parent::setUp();
$this->withoutMiddleware();
}
The withoutMiddleware method can only remove route middleware and does not apply to global middleware.
From https://laravel.com/docs/8.x/middleware