I'd like to access a request variable from the constructor of my controller in Laravel. How can I do this?
this is my route:
Route::post('bookGetById', ['uses' => 'v1\BookController#getBookById']);
and here is my controller:
public function __construct(Request $request = null)
{
parent::__construct();
$this->bookStructure = new bookStructure($request->imageHeight);
}
but the request variable is always null. How can I pass the request into the constructor?
Don't know what version of laravel you're using but I think in 5.2+ the controller is created before the request is bound. There's a workaround:
public function __construct()
{
parent::__construct();
$this->middleware(function (Request $r, $next) {
$this->bookStructure = new bookStructure($request->imageHeight);
return $next($r);
});
}
However I would recommend doing this is via the service container:
File AppServiceProvider
$this->app->bind("bookstructure.withheight", function ($app) {
return new bookStructure($app->make("request")->get("imageHeight",0));
});
You can instantiate your bookStructure wherever you need it via:
app()->make("bookstructure.withheight");
I know you are asking how to pass a request to a class constructor but you may want to rethink it and pass it to a controller that validates the request and instantiates your class with validated request variables.
Related
In my API I used "with" method to get parent's model relation and everything works fine.
I want to add an attribute in my relation and return it in my API but I should use request in my model.
Something like this :
Book.php
protected $appends = ['userState'];
public function getUserStateAttribute () {
return User::find($request->id); //request not exists here currently
}
I have $request in my controller (api controller)
Controller.php
public function get(Request $request) {
Post::with('books')->all();
}
I believe using static content to append in array of model is so easy but how about using request's based content ?
I guess you can use request() helper :
public function getUserStateAttribute () {
return User::find(request()->get('id'));
}
Sure this is not really MVC pattern, but it can work
You want to take request as a parameter here:
public function getUserStateAttribute (Request $request) {
return User::find($request->id);
}
That way you can access it in the function. You will just need to always pass the Request object whenever you call that function.
e.g. $book->getUserStateAttribute($request);
Alternatively, you could just pass the ID, that way you need not always pass a request, like so:
public function getUserStateAttribute ($id) {
return User::find($id);
}
Which you would call like:
e.g. $book->getUserStateAttribute($request->id);
I am having an issue setting up an injection on both the constructor and the method in a controller.
What I need to achieve is to be able to set up a global controller variable without injecting the same on the controller method.
From below route;
Route::group(['prefix' => 'test/{five}'], function(){
Route::get('/index/{admin}', 'TestController#index');
});
I want the five to be received by the constructor while the admin to be available to the method.
Below is my controller;
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(Admin $admin, Request $request)
{
dd($request->segments(), $admin);
return 'We are here: ';
}
...
When I run the above, which I'm looking into using, I get an error on the index method:
Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Argument 1 passed to App\Http\Controllers\TestController::index() must be an instance of App\Models\Admin, string given"
Below works, but I don't need the PrimaryFive injection at the method.
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(PrimaryFive $five, Admin $admin, Request $request)
{
dd($request->segments(), $five, $admin);
return 'We are here: ';
}
...
Is there a way I can set the constructor injection with a model (which works) and set the method injection as well without having to inject the model set in the constructor?
One way you could do this is to use controller middleware:
public function __construct()
{
$this->middleware(function (Request $request, $next) {
$this->five = PrimaryFive::findOrFail($request->route('five'));
$request->route()->forgetParameter('five');
return $next($request);
});
}
The above is assuming that PrimaryFive is an Eloquent model.
This will mean that $this->five is set for the controller, however, since we're using forgetParameter() it will no longer be passed to your controller methods.
If you've specific used Route::model() or Route::bind() to resolve your five segment then you can retrieve the instance straight from $request->route('five') i.e.:
$this->five = $request->route('five');
The error is because of you cannot pass a model through the route. it should be somethiing like /index/abc or /index/123.
you can use your index function as below
public function index($admin,Request $request){}
This will surely help you.
Route::group(['prefix' => 'test/{five}'], function () {
Route::get('/index/{admin}', function ($five, $admin) {
$app = app();
$ctr = $app->make('\App\Http\Controllers\TestController');
return $ctr->callAction("index", [$admin]);
});
});
Another way to call controller from the route. You can control what do you want to pass from route to controller
I'm setting up slim router v4, and I'd like to be be able to dynamically call the controller methods, using the placeholder from the route.
I.e when a request is made to 'example.com/users/{action}', the router would call the method from Users.php controller automatically without me having to specify the routes manually.
Basically I'm trying to avoid manually adding over 100 group->get(...) when they're all under /user route.
namespace core\router;
use Slim\Interfaces\RouteCollectorProxyInterface;
use app\controllers\users;
$app->group('/user', function(RouteCollectorProxyInterface $group){
$group->get('/get-name', '\Users:name')
$group->get('/get-personality', '\Users:personality');
});
Further explanation is provided here but I'm not sure how to go about this.
The way I would suggest doing this is having a single, catch all route with a placeholder. You can then set action to an invocable controller, and execute a method based on the route parameter.
Route:
$app->get('/user/{method}', Users::class);
Controller
class Users
{
public function __invoke(Request $request, Response $response, $args)
{
if (empty($args['method'])) {
throw new InvalidArgumentException();
}
$methodName = toCamelCase($args['method']);
if (!method_exists($this, $methodName)) {
throw new InvalidArgumentException();
}
return $this->{$methodName};
}
public function getName(Request $request, Response $response)
{
// ...
}
public function getPersonality(Request $request, Response $response)
{
// ...
}
}
I'm doing an existence check within a middleware, by checking a route-parameter.
If the check succeeds, I'm attaching it's model to the request to make it available throughout the rest of the request-cycle, application.
// App\Http\Middleware\CheckForExistence.php:
...
public function handle($request, Closure $next)
{
// some checks...
// success
$request->attributes->add([
'company' => $someModel
]);
}
I now have a controller which 'needs' this information in a couple of methods. So my thought was to add it to the construct of the controller and add it as a protected var in the whole controller:
// App\Http\Controllers\MyController.php
<?php
use Illuminate\Http\Request;
class MyController extends Controller
{
protected $company;
public function __construct(Request $request)
{
$this->company = $request->attributes->get('company');
}
public function index()
{
dd($this->company); // returns null
}
}
This controllers index() returns null instead of the give model.
If I change the index() method to:
public function index(Request $request)
{
return $request->attributes->get('company');
}
This returns the model; as expected.
Why is this happening? It looks like the middleware is not run when the controller is constructed.... Is there a way to circumvent it?
Or am I missing the obvious here.....
I could off course repeat myself in each method; but that is not very DRY ;)
You can't access the session or authenticated user in your controller's constructor because the middleware has not run yet, So you can do it like this :
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->company = $request->attributes->get('company');
return $next($request);
});
}
For reasons currently unclear to me, the controller object is constructed before the request changes are reflected in the request object. In short the request is not considered properly constructed when a controller is constructed. This post seems to imply that.
There's two ways to work around this (if for a second we ignore what you're trying to do).
Use request dependency injection
public function index(Request $request)
{
$compary = $request->attributes->get('company');
}
This is not really WET because you're just swapping $this->company with $request->attributes->get('company') it's just a refactor. You should be injecting the request in the controller action anyway and if you don't want to do that you can use the request() helper.
Use a callback middleware in the constructor (Maraboc's answer explains how)
Now if you want a more case specific solution though you can use case specific dependency injection:
If you need to bind a model to a specific route parameter you can use route model binding and add the following in your RouteServiceProvider (or any provider).
Route::bind("companyAsARouteVarName", function () {
// this is why more details in the question are invaluable. I don't know if this is the right way for you.
//checks
// success
return $someModel;
});
Then you will register your route as:
Route::get("/something/{companyAsARouteVarName}", "SomeController#index");
and your controller will be:
public function index(Company $companyAsARouteVarName) {
//Magic
}
Controller constructor will be initialized before middleware execution.
You can get data from Injected $request object in controller functions.
Is it possible to inject a route-paramter (or an route segment) to the controller-constructor?
You find some code to clarify my question.
class TestController{
protected $_param;
public function __construct($paramFromRoute)
{
$this->param = $paramFromRoute;
}
public function testAction()
{
return "Hello ".$this->_param;
}
}
----------------------------------------------------
App::bind('TestController', function($app, $paramFromRoute){
$controller = new TestController($paramFromRoute);
return $controller;
});
----------------------------------------------------
// here should be some magic
Route::get('foo/{bar}', 'TestController');
It's not possible to inject them, but you have access to all of them via:
class TestController{
protected $_param;
public function __construct()
{
$id = Route::current()->getParameter('id');
}
}
Laravel 5.3.28
You can't inject the parameter...
But, you can inject the request and get it from the router instance, like this:
//route: url_to_controller/{param}
public function __construct(Request $request)
{
$this->param = $request->route()->parameter('param');
}
In Laravel 5.4, you can use this to request the parameter:
public function __construct(Request $request) {
$id = $request->get("id");
}
If you want a more testable solution, you can use Service Provider power.
$this->app->bind(TestController::class, function ($app) {
return new TestController(request()->testParam);
});
UPDATE FOR LARAVEL 8
you can use the route() method to get the value from the route url parameter from laravel 8:
$id = request()->route('id')
Lastly, but most importantly, you may simply "type-hint" the dependency in the constructor of a class that is resolved by the container, including controllers, event listeners, queue jobs, middleware, and more. In practice, this is how most of your objects are resolved by the container.
http://www.golaravel.com/laravel/docs/5.1/container/