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!
Related
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);
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.
Slim 4 is already here and I am trying to move to Slim 4. Everything is great, but CSRF returns an error when i try to implement it. I tried the simplest setup, but I get this error:
Message: Argument 2 passed to Slim\Csrf\Guard::__invoke() must be an instance of Psr\Http\Message\ResponseInterface, instance of Slim\Routing\RouteRunner given, called in /Volumes/Web/slim/vendor/slim/slim/Slim/MiddlewareDispatcher.php on line 180
File: /Volumes/Web/slim/vendor/slim/csrf/src/Guard.php
Here is my code:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Csrf\Guard;
require __DIR__ . '/../vendor/autoload.php';
/**
* Instantiate App
*
* In order for the factory to work you need to ensure you have installed
* a supported PSR-7 implementation of your choice e.g.: Slim PSR-7 and a supported
* ServerRequest creator (included with Slim PSR-7)
*/
$app = AppFactory::create();
$app->add(Guard::class);
// Add Routing Middleware
$app->addRoutingMiddleware();
/*
* Add Error Handling Middleware
*
* #param bool $displayErrorDetails -> Should be set to false in production
* #param bool $logErrors -> Parameter is passed to the default ErrorHandler
* #param bool $logErrorDetails -> Display error details in error log
* which can be replaced by a callable of your choice.
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Define app routes
$app->get('/', function (Request $request, Response $response, $args) {
$response->getBody()->write('Hello');
return $response;
});
// Run app
$app->run();
Any help is greatly appreciated! Thanks!
The package is not compatible with Slim4. I wrote a wrapper so you can use it.
`
<?php
declare(strict_types=1);
namespace App\Application\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Csrf\Guard as Guard;
class CsrfMiddleware extends Guard implements Middleware
{
/**
* Process middleware
*
* #param ServerRequestInterface $request request object
* #param RequestHandlerInterface $handler handler object
*
* #return ResponseInterface response object
*/
public function process(Request $request, RequestHandler $handler): Response
{
$this->validateStorage();
// Validate POST, PUT, DELETE, PATCH requests
if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
$body = $request->getParsedBody();
$body = $body ? (array) $body : [];
$name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
$value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
if (!$name || !$value || !$this->validateToken($name, $value)) {
// Need to regenerate a new token, as the validateToken removed the current one.
$request = $this->generateNewToken($request);
$failureCallable = $this->getFailureCallable();
return $failureCallable($request, $handler);
}
}
// Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
$request = $this->generateNewToken($request);
} elseif ($this->persistentTokenMode) {
$pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken();
$request = $this->attachRequestAttributes($request, $pair);
}
// Enforce the storage limit
$this->enforceStorageLimit();
return $handler->handle($request);
}
/**
* Getter for failureCallable
*
* #return callable|\Closure
*/
public function getFailureCallable()
{
if (is_null($this->failureCallable)) {
$this->failureCallable = function (Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$stream = $response->getBody();
$stream->write('CSRF fail');
return $response->withStatus(400);
};
}
return $this->failureCallable;
}
}
`
The relevant bit is:
$app->add(Guard::class);
The signature of middleware callbacks has changed. In Slim/3 it used to be like this:
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface
... and then the method had to call $next like $next($request, $response).
In Slim/4 it's like this:
public function __invoke(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
.. and the internal call to $handler is $handler->handle($request).
The library does not seem to have been updated for Slim/4. It declares Slim/3 as dev (?) dependency in composer.json and mentions in README.md. Perhaps it isn't very difficult to either fix the library or write a compatible wrapper on top of it but if you aren't familiar with the overall ecosystem it's probably easier to install a replacement.
I have a two level resource in Laravel as below;
Route::resource("domains", "DomainsController");
Route::resource("domains/{domain}/subdomains", "SubDomainsController");
and I have two policies;
DomainPolicy.php
SubDomainPolicy.php
the problem is that these domains belong to different users, so I have to authorize these domains and subdomains. I can authorize DomainsController easily since all I have to do is Domain::class => DomainPolicy::class in AuthServiceProvider.php.
When it comes to authorizing SubDomainsController I can use the same policy input such as SubDomain::class => SubDomainPolicy::class, BUT when I access the /domains/1/subdomains/create link since there is no Domain::class delivered to the SubDomainPolicy::class it always prevents access to create page.
I use $this->authorizeResource(Domain::class) and $this->authorizeResource(SubDomain::class) in resource controller constructors without any arguments.
I need to pass Domain model to the SubDomainPolicy someway, thanks in advance.
I have found the solution not through a policy but a middleware. Since the models are binded on web.php Domain::class is always delivered to the SubDomainsController class, so I changed the constructor as;
public function __construct(Domain $domain) {
$this->middleware("domain-access");
}
or you can set it on web.php as a middleware group (eg. ['middleware' => 'domain-access']).
In middleware folder create a middleware named DomainAccess.php with this content;
namespace App\Http\Middleware;
use Closure;
use Illuminate\Auth\Access\AuthorizationException;
class DomainAccess
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();
$domain = $request->domain;
if ($domain->user_id != $user->id) {
return redirect("/");
}
return $next($request);
}
}
And, voila! Everything is working perfectly.
Have a beautiful day.