How to 'use' packages in my own Laravel middleware? - php

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

Laravel 8 Changes to Route::group method

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

Function AFTER authentication and BEFORE view (laravel)

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

Laravel route optional parameter to controller

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.

How do I implement CSRF protection with Slim 4 and slim/csrf?

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.

laravel multiple models in policy

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.

Categories