Previously i have this code working in laravel 5.2
RouterServiceProvider
public function boot(Router $router)
{
parent::boot($router);
// Model binding
$router->model('house', 'App\house');
}
and in controller
public function show(House '$house')
{
return view('house.show', compact('house'));
}
and when i upgrade to Laravel 5.4 this code doesn't work.
So i change my code to this in RouterServiceProvider
public function boot()
{
//
parent::boot();
Route::model('house', App\House::class);
}
But I dont know what to change in the controller codes below
public function show(House '$house')
{
return view('house.show', compact('house'));
}
, when I run the code I got this error
(1/1) FatalThrowableError
Parse error: syntax error, unexpected ''$house'' (T_CONSTANT_ENCAPSED_STRING), expecting variable (T_VARIABLE)
I need Route Model Binding to simplify my code-> (house = House::FindOrFail($id);)
Thanks for the help!
Change this
public function show(House '$house')
{
return view('house.show', compact('house'));
}
to this
public function show(House $house)
{
return view('house.show', compact('house'));
}
Implicit binding works out of the box:
Implicit Binding
Laravel automatically resolves Eloquent models defined in routes or
controller actions whose type-hinted variable names match a route
segment name. For example:
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});
Since the $user variable is type-hinted as the App\User Eloquent
model and the variable name matches the {user} URI segment, Laravel
will automatically inject the model instance that has an ID matching
the corresponding value from the request URI. If a matching model
instance is not found in the database, a 404 HTTP response will
automatically be generated.
But in case you want to declared explicitly, you can check the Explicit Binding of the documentation:
Explicit Binding
To register an explicit binding, use the router's model method to
specify the class for a given parameter. You should define your
explicit model bindings in the boot method of the
RouteServiceProvider class:
public function boot()
{
parent::boot();
Route::model('user', App\User::class);
}
Next, define a route that contains a {user} parameter:
Route::get('profile/{user}', function (App\User $user) {
//
});
Since we have bound all {user} parameters to the App\User model, a
User instance will be injected into the route. So, for example, a
request to profile/1 will inject the User instance from the
database which has an ID of 1.
If a matching model instance is not found in the database, a 404
HTTP response will be automatically generated.
So, in your case:
RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::model('house', App\House::class);
}
Then in your controller:
HousesController.php
public function show(House $house)
{
return view('house.show', compact('house'));
}
Related
I have a model in laravel and I want to do something after the first time which an object of my model is created. the simplest way is to add a static boot method inside my model's class like the code below:
class modelName extends Model
{
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
});
}
}
so far so good! the problem is: the ONLY parameter that created method accepts is the model object itself(according to the documentation) :
Each of these methods receives the model as their only argument.
https://laravel.com/docs/5.5/eloquent#events
I need more arguments to work with after model creation. how can I do that?
Or is there any other way to do something while it's guaranteed that the model has been created?
laravel version is 5.5.
You're close. What I would probably do would be to dispatch an event right after you actually create the model in your controller. Something like this.
class WhateverController
{
public function create()
{
$model = Whatever::create($request->all());
$anotherModel = Another::findOrFail($request->another_id);
if (!$model) {
// The model was not created.
return response()->json(null, 500);
}
event(new WhateverEvent($model, $anotherModel));
}
}
I solved the issue using static property in eloquent model class:
class modelName extends Model
{
public static $extraArguments;
public function __construct(array $attributes = [],$data = [])
{
parent::__construct($attributes);
self::$extraArguments = $data ;
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
self::$extraArguments; // is available in here
});
}
}
It works! but I don't know if it may cause any other misbehavior in the application.
Using laravel events is also a better and cleaner way to do that in SOME cases.but the problem with event solution is you can't know if the model has been created for sure and it's time to call the event or it's still in creating status ( and not created status).
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 would like to create a question which has many surveys. In the questions Model:
public function surveys()
{
return $this->belongsToMany(Survey::class, 'survey__surveyquestions');
}
And in the controller when saving a new question:
private $questions;
public function __construct(QuestionsRepository $questions)
{
parent::__construct();
$this->questions = $questions;
}
public function store(Request $request)
{
$this->questions->create($request->all());
$this->questions->surveys()->attach($request->surveys);
return redirect()->route('admin.survey.questions.index')
->withSuccess(trans('core::core.messages.resource created', ['name' => trans('survey::questions.title.questions')]));
}
But I get the following error when it gets to the attach line:
(1/1) FatalErrorException Call to undefined method
Modules\Survey\Repositories\Eloquent\EloquentQuestionsRepository::surveys()
I notice the error mentions EloquentQuestionsRepository but I have added no methods in there so it's just an empty class:
class EloquentQuestionsRepository extends EloquentBaseRepository implements QuestionsRepository
{
}
QuestionRepository:
interface QuestionsRepository extends BaseRepository
{
}
As explained in the response to the main post - the constructor resolves the QuestionsRepository to instance of EloquentQuestionsRepository, which by the look of it is not what the store method needs.
What I would probably do is to make call to create method directly on the model and remove constructor all together - that is unless you need the instance of QuestionsRepository anywhere else in your controller:
public function store(Request $request)
{
$question = Question::create($request->all());
$question->surveys()->attach($request->surveys);
...
}
Also - I'm not sure passing $request->all() is the best thing to do - I'd probably use $request->only(...) or $request->all(...) specifying which items you want to get from the request rather than passing everything from the request to the create method.
On the other note - you could also use Form Request, which would validate data for your before passing it to the store method.
https://laravel.com/docs/5.5/validation#form-request-validation
I'm trying to resolve a primitive inside a controller method.
This is the register method of my Provider:
public function register()
{
$this->app->when('App\Http\Controllers\InvalidCustomerController')
->needs('$customers')
->give(function () {
return InvalidCustomer::latest()
->paginate(20);
});
}
And this is the controller method I'm trying to resolve $customers:
public function index($customers)
{
return view(
'customer.invalid.index',
compact('customers')
);
}
$customers is not filled.
Everything will work if I resolve that on constructor.
What am I doing wrong?
ps: I'm using Laravel 5.2
Not sure if you found a solution but to use a primitive in a controller, pass it through the __constructor of the controller class as so:
private $customers;
public function __construct($customers) {
parent::__construct();
$this->customers = $customers;
}
This $customers variable can then be used elsewhere inside of the class.
I'm using model binding within my routes to pass models into my controller actions and would like to be able to write tests. It would be preferable if it wasn't required for the test to hit the database.
The model is bound using the username in this example, and then used in the definition of the routes.
// routes.php
Route::model('user', function($value, $route)
{
return User::whereUsername($value)->firstOrFail();
});
Route::get('users/{user}', 'UsersController#show');
In my controller the bound user is passed to the action.
// UsersController.php
function show(User $user)
{
return View::make('users.show', compact('user');
}
Now, in my tests I'm attempting to mock the User.
// UsersControllerTest.php
public function setUp()
{
parent::setUp();
$this->mock = Mockery::mock('Eloquent', 'User');
$this->app->instance('User', $this->mock);
}
public function testShowPage()
{
$this->mock->shouldReceive('whereSlug')->once()->andReturn($this->mock);
$this->action('GET', 'UsersController#show');
$this->assertResponseOk();
$this->assertViewHas('user');
}
When running this test, I get the following error:
ErrorException: Argument 1 passed to UsersController::show() must be an instance of User, instance of Illuminate\Database\Eloquent\Builder given
I'd also like to be able to use return User::firstByAttribtues($value); but Mockery won't let me mock a protected method - is there any way I can get around this?
I had to dig thru Mockery's source code to find this, but have you looked at shouldAllowMockingProtectedMethods ?
Ie, to mock class foo and allow protected methods to be mocked:
$bar = \Mockery::mock('foo')->shouldAllowMockingProtectedMethods();
// now set your expectations up
and then keep going from there.
Not sure why you're not getting an error like unexpected method "firstOrFail" called. But, at first glance, I think the problem is that your model route defined in routes.php is also calling the firstOrFail method.
So, your test should look something like this:
public function testShowPage()
{
$stubQuery = \Mockery::mock('Illuminate\Database\Eloquent\Builder');
$this->mock->shouldReceive('whereSlug')->once()->andReturn($stubQuery);
$stubQuery->shouldReceive('firstOrFail')->andReturn($this->mock);
$this->action('GET', 'UsersController#show');
$this->assertResponseOk();
$this->assertViewHas('user');
}