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).
Related
I want to save additional record in database while saving a model in laravel, my model looks like:
class Document extends Model
{
use DocumentSetup;
}
And my trait looks like:
trait DocumentSetup {
protected static function boot()
{
static::saving(function ($model) {
$documentSetup = new DocumentSetup();
$documentSetup->document_id = $model->id;
$documentSetup->is_public = false;
$documentSetup->need_verification = true;
$documentSetup->save();
});
parent::boot();
}
}
If I try that I don't get any error, but document or document setup are not created, does anyone know what i'm doing wrong here?
My idea is to create this additional model while saving...
When using traits for Eloquent lifecycle hooks, you must name the boot method boot[traitName] in your case bootDocumentSetup. You should also remove the parent::boot() call in the trait, as there is no such parent call. If you name it like this it will work.
This is to avoid clashes when you are using one or more traits in a model, that each have its own boot method.
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 am trying to check in the constructor of a model if the currently authenticated user is allowed to access the given model, but I am finding that $this from the constructor's context is empty. Where are the attributes assigned to a model in Laravel and how should I go about calling a method once all of the attributes have been loaded?
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
var_dump($this); // empty model
$this->checkAccessible();
}
Cheers in advance
As stated in the other answers & comments, there are better ways to achieve the aims of the question (at least in modern Laravel). I would refer in this case to the Authorization chapter of the documentation that goes through both gates and policies.
However, to answer the specific question of how to call a method once a models attributes have been loaded - you can listen for the Eloquent retrieved event. The simplest way to do this within a class is using a closure within the class booted() method.
protected static function booted()
{
static::retrieved(function ($model) {
$model->yourMethod() //called once all attributes are loaded
});
}
You can also listen for these events in the normal way, using listeners. See the documentation for Eloquent events.
you can use controller filter to check whether user logged in or not and than you call any model function.
public function __construct(array $attributes = []){
$this->beforeFilter('auth', array('except' => 'login')); //login route
if(Auth::user()){
$user_id = Auth::user()->user_id;
$model = new Model($attributes);
//$model = User::find($user_id);
}
}
Binding Attributes to Model from constructor
Model.php
public function __construct(array $attributes = array())
{
$this->setRawAttributes($attributes, true);
parent::__construct($attributes);
}
As it was mentioned by Rory, the retrieved event is responsible for that.
Also, it could be formed in a much cleaner and OOP way with Event/Listener approach, especially if you need to write a lot of code or have few handlers.
As it described here, you can just create an event for the Model like
protected $dispatchesEvents = [
'retrieved' => UserLoaded::class,
];
You need to create this class, eloquent event accepts the model by default:
class UserLoaded
{
protected User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Then here is described how to declare listener for this event. It should be somewhere in the EventListenerProvider like this:
protected $listen = [
UserLoaded::class => [
UserLoadedListener::class
],
];
The listener should just implement method handle() (check article) like:
public function handle(UserLoaded $event)
{
// your code
}
Another possibility is to register model Observer, as it´s described here
I look at many search results with this trouble but i can`t get it to work.
The User Model:
<?php namespace Module\Core\Models;
class User extends Model {
(...)
protected function Person() {
return $this->belongsTo( 'Module\Core\Models\Person', 'person_id' );
}
(...)
And the Person Model:
<?php namespace Module\Core\Models;
class Person extends Model {
(...)
protected function User(){
return $this->hasOne('Module\Core\Models\User', 'person_id');
}
(...)
Now, if i use User::find(1)->Person->first_name its work. I can get the Persons relations from the User Model.
But.. User::with('Person')->get() fails with a Call to undefined method Illuminate\Database\Query\Builder::Person()
What im doing wrong? i need a collection of all the users with their Person information.
You have to declare the relationship methods as public.
Why is that? Let's take a look at the with() method:
public static function with($relations)
{
if (is_string($relations)) $relations = func_get_args();
$instance = new static;
return $instance->newQuery()->with($relations);
}
Since the method is called from a static context it can't just call $this->Person(). Instead it creates a new instance of the model and creates a query builder instance and calls with on that and so on. In the end the relationship method has to be accessible from outside the model. That's why the visibility needs to be public.
I'm attempting to implement a global scope in Laravel 4.2 by following the documentation. I have created a UserTypeTrait to add a UserTypeScope to the global scope. This is my UserTypeTrait:
trait UserTypeTrait
{
public static function bootUserTypeTrait()
{
Log::info('bootUserTypeTrait fired');
static::addGlobalScope(new UserTypeScope);
}
}
This is my UserTypeScope:
class UserTypeScope implements ScopeInterface
{
public function apply(Builder $builder)
{
Log::info('apply fired');
$model = $builder->getModel();
}
public function remove(Builder $builder)
{
// #todo
}
I'm attempting to apply this trait to my Customer object:
class Customer extends User
{
use UserTypeTrait;
}
Whenever the application boots, I'm getting confirmation that the bootUserTypeTrait() method fired in my log file. I'd like to add a constraint to all queries related to my Customer model in the apply method of my UserTypeScope, but I can't figure out how to trigger the apply method. From the documentation, it seems that it should be triggered any time I query the model. I've tried a few tests, but none of them result in the apply method being fired. For example:
public function testUserTypeScopeApplyFires()
{
FactoryMuffin::create('Users\Customer');
$customers = Customer::all(); // this does note fire `UserTypeScope::apply()`
$customer = Customer::first(); // neither does this
}
In my log file, I see plenty of bootUserTypeTrait fired and no apply fired entries. When is the apply() method invoked? I thought I followed the documentation correctly. Am I missing something?