Are there callbacks in Laravel like:
afterSave()
beforeSave()
etc
I searched but found nothing. If there are no such things - what is best way to implement it?
Thanks!
The best way to achieve before and after save callbacks in to extend the save() function.
Here's a quick example
class Page extends Eloquent {
public function save(array $options = [])
{
// before save code
parent::save($options);
// after save code
}
}
So now when you save a Page object its save() function get called which includes the parent::save() function;
$page = new Page;
$page->title = 'My Title';
$page->save();
Adding in an example for Laravel 4:
class Page extends Eloquent {
public static function boot()
{
parent::boot();
static::creating(function($page)
{
// do stuff
});
static::updating(function($page)
{
// do stuff
});
}
}
Actually, Laravel has real callback before|after save|update|create some model. check this:
https://github.com/laravel/laravel/blob/3.0/laravel/database/eloquent/model.php#L362
the EventListener like saved and saving are the real callbacks
$this->fire_event('saving');
$this->fire_event('saved');
how can we work with that? just assign it to this eventListener example:
\Laravel\Event::listen('eloquent.saving: User', function($user){
$user->saving();//your event or model function
});
Even though this question has already been marked 'accepted' - I'm adding a new updated answer for Laravel 4.
Beta 4 of Laravel 4 has just introduced hook events for Eloquent save events - so you dont need to extend the core anymore:
Added Model::creating(Closure) and Model::updating(Closure) methods for hooking into Eloquent save events. Thank Phil Sturgeon for finally pressuring me into doing this... :)
In Laravel 5.7, you can create a model observer from the command line like this:
php artisan make:observer ClientObserver --model=Client
Then in your app\AppServiceProvider tell the boot method the model to observe and the class name of the observer.
use App\Client;
use App\Observers\ClientObserver;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Client::observe(ClientObserver::class);
}
...
}
Then in your app\Observers\ you should find the observer you created above, in this case ClientObserver, already filled with the created/updated/deleted event hooks for you to fill in with your logic. My ClientObserver:
namespace App\Observers;
use App\Client;
class ClientObserver
{
public function created(Client $client)
{
// do your after-model-creation logic here
}
...
}
I really like the simplicity of this way of doing it. Reference https://laravel.com/docs/5.7/eloquent#events
Your app can break using afarazit solution*
Here's the fixed working version:
NOTE: saving or any other event won't work when you use eloquent outside of laravel, unless you require the events package and boot the events. This solution will work always.
class Page extends Eloquent {
public function save(array $options = [])
{
// before save code
$result = parent::save($options); // returns boolean
// after save code
return $result; // do not ignore it eloquent calculates this value and returns this, not just to ignore
}
}
So now when you save a Page object its save() function get called which includes the parent::save() function;
$page = new Page;
$page->title = 'My Title';
if($page->save()){
echo 'Page saved';
}
afarazit* I tried to edit his answer but didn't work
If you want control over the model itself, you can override the save function and put your code before or after __parent::save().
Otherwise, there is an event fired by each Eloquent model before it saves itself.
There are also two events fired when Eloquent saves a model.
"eloquent.saving: model_name" or "eloquent.saved: model_name".
http://laravel.com/docs/events#listening-to-events
Related
I need to add a column to some of my database tables and populate that on Model::create().
All I want is functionality like Laravel built in timestamps i.e created_at and updated_at
I know of some solutions but I wanted to know if something else could be done in such case.
For example:
protected static function booted()
{
static::created(function ($user) {
//
});
}
OR
class UserObserver
{
public function creating(User $user)
{
//
}
}
I will need to do that for every model which is repeating myself.
Another solution that comes to my mind is creating a BaseModel and then extend everything from that.
I wanted to do something like we have in SoftDeletes trait, Add whatever logic inside of that and populate that column using some laravel hook that I am not aware of. If someone can guide me to accomplish such thing using traits and HOW?
You can extend all your models off of a base model class (a small refactor), and then in that class add a booted method with your custom logic.
If you then need (or already have) extra logic in a model (say the user model) that uses the booted method, you can still keep that, but make sure you call parent::booted();
class BaseModel extends Model {
protected static function booted()
{
static::created(function ($model) {
//logic
});
}
}
class User extends BaseModel {
protected static function booted()
{
parent::booted();
//any other logic you may have/want in user
}
}
This is in my opinion the most scalable approach. Make use of OOP! You can also then reuse this base class for other global logic in the future.
I have an observer for my User model. Inside my observer->created event i have some code.
public function created(User $user)
{
sendEmail();
}
So, the idea is, when a user is created, the system will send for the user email notification that the account was created.
Question: When the database is seeding, it also calls this method 'created' and sends users (that are in the seeds) email notification.
So, my question is, how can i check, probably inside this 'created' method if at the moment laravel is seeding data -> do not send email of do not run the 'created' observer method.
Tried to google, found something, but not working correct.
Something like YourModel::flushEventListeners();
You can use YourModel::unsetEventDispatcher(); to remove the event listeners for a model temporary.
If you need them after seeding in the same execution, you can read the dispatchers, unset them and then set them again.
$dispatcher = YourModel::getEventDispatcher();
// Remove Dispatcher
YourModel::unsetEventDispatcher();
// do stuff here
// Re-add Dispatcher
YourModel::setEventDispatcher($dispatcher);
namespace Database\Seeders;
use App\Models\Blog;
use Illuminate\Database\Seeder;
class BlogsTableSeeder extends Seeder
{
public function run()
{
Blog::withoutEvents(function () {
// normally
Blog::factory()
->times(10)
->hasUploads(1) //hasOne
->hasComments(2) //hasMany
->create();
});
}
}
You may mute event with WithoutModelEvents trait
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class SomeSeeder extends Seeder
{
use WithoutModelEvents;
public function run()
{
User::factory( 30 )->create();
}
}
or you may try createQuietly method of a factory, for example
class SomeSeeder extends Seeder
{
public function run()
{
User::factory( 30 )->createQuietly();
}
}
You could use the saveQuietly() function https://laravel.com/docs/8.x/eloquent#saving-a-single-model-without-events
This allows you to disable all events for a single model.
If you wanna disable a single event for a single model, read about it here: http://derekmd.com/2019/02/conditionally-suppressing-laravel-event-listeners/
I'm having some issues using Voyager. I can create Accessors to attributes, as described in the documentation. It works nicely to access the data, but I also have to create a Mutator to change the data before saving it. Apparently, there's no implementation by Voyager, so I tried to do it through Laravel way. It also works very nicely in common environments, but for some reason, there is a different behavior with Voyager.
The Mutator is called after the Accessor, even when only browsing:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
public function setNameAttribute($value){ // my mutator
$value = preg_replace("/[^a-zA-Z]/", '', $value);
$this->attributes['name'] = $value;
}
public function getNameBrowseAttribute(){ // my accessor
return $this->name . '...'; // example
}
}
What is happening:
When I access the browser of my Model, the getNameBrowseAttribute is called, as it should be, but after that, the setNameAttribute is called as well, which should not happen because I'm browsing, and not saving or updating the Model.
I tried to debug the code, and the last Voyager file called is a view, from voyager/storage/framework/views/, where $data is the Model:
if ($data->{$row->field.'_browse'}) {
$data->{$row->field} = $data->{$row->field.'_browse'}; // <-- this line
}
As you can see, it is calling a method with ...browse, and not set...
Any help is appreciated :)
You could try to do with model events. Use the saving event.
https://laravel.com/docs/8.x/eloquent#events
I have an application where I have to deal with multiple vendors - each having their different implementation. For example, let's say Payment Systems, there are many vendors and banks.
There are a few things common, like implementation process. In each case, I have to give a callback URL to them to give me a response.
For now, I have two vendors - VendorPay and VendorA.
I will have two routes:
payment/callback/vendorpay
payment/callback/vendora
each of them call two methods in controller.
processVendorpay and processVendora
Now, if I want to add, lets say, 15 more vendors like this, will I have to create methods everytime I add new vendor? Is there any cleaner solution for this?
My controller right now looks like this:
class PaymentController extends BaseController
{
protected $vendorpay_validator, $vendora_validator, $transaction, $transaction_log, $vendor_product, $vendor_transaction;
public function __construct(VendorpayValidator $vendorpay_validator, VendorAValidator $vendora_validator, Transaction $transaction, TransactionLog $transaction_log, VendorProduct $vendor_product, VendorTransaction $vendor_transaction)
{
$this->vendorpay_validator = $vendorpay_validator;
$this->vendora_validator = $vendora_validator;
$this->transaction = $transaction;
$this->transaction_log = $transaction_log;
$this->vendor_product = $vendor_product;
$this->vendor_transaction = $vendor_transaction;
}
}
These four are the Model Repository Objects: $transaction, $transaction_log, $vendor_product, $vendor_transaction
If I have to add more venodrs, it keeps on adding validator object here. What would be a much cleaner way to do it?
One of the solutions that I thought was - for multiple routes, I just create one method. Now I'll check the route in this method and call the Factory Object basis on that.
You should have just one route...
payment/callback/{vendor}
Then if you want to go the factory route (which I think would be a good idea in this case)...
class VendorValidatorFactory
{
private function __construct() {}
public static function getValidator($vendor)
{
switch ($vendor) {
case 'vendorpay':
return new VendorPayValidator;
case 'vendora':
return new VendarAValidator;
}
}
}
Remove the now unnecessary injections from your constructor and in the method which responds to your route, use the factory to grab the correct validator...
class SomeController extends Controller
{
public function __construct(Transaction $transaction, TransactionLog $transaction_log, VendorProduct $vendor_product, VendorTransaction $vendor_transaction)
{
$this->transaction = $transaction;
$this->transaction_log = $transaction_log;
$this->vendor_product = $vendor_product;
$this->vendor_transaction = $vendor_transaction;
}
public function processVendorResponse($vendor)
{
$validator = VendorValidatorFactory::getValidator($vendor);
}
}
And just a suggestion, every time you need to add a new method to your validator classes which your controller uses, add that to a ValidatorInterface and make sure all your validators implement that ValidatorInterface. This way when you need to add more, all you need to do is implement that interface and that should tell you exactly what functions you need to write. Then just update your factory method to include the new one and you are done. No more changing your controller, adding routes, or adding dependencies to your controller.
I'm trying to create some tests.
Here's my Test Class:
class ExampleTest extends TestCase {
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
$this->seed();
Auth::loginUsingId(1);
}
public function testActionUpdateNew()
{
$action = new Action(Array());
$action->save();
var_dump($action->id);
Action::with('reponses','contact','user','etudiant','entreprise','etude')->findOrFail($action->id);
}
public function testEtudes()
{
$etudes=Etude::all()->toArray();
$this->assertCount(10, $etudes, "Nombre d'études incorrectes");
$numEtudes=count($etudes);
//Buggy part
$etude= Etude::create(Array());
var_dump($etude->id);
$etudes=Etude::all()->toArray();
$this->assertCount(11, $etudes, "Nombre d'études incorrectes");
//10+1 should equal to 11 but it hasnt updated
}
}
The test that is not passing is the second one: I count the number of eloquent Objects Etudes, which are of 10 at the beginning, I then add one etude to the database (using Etude::create()) , the object is created, because $etude->id gives out a real number. Howewer, the number of Etude hasn't updated.
The problem does go away when I remove the 'etude' from the eager loading in Action::with('reponses',...)
Here is the etudes relationship in the Action class:
public function etude() {
return $this->belongsTo('Etude');
}
Do you guys have any idea if eager-loading in laravel can have such strange behavior and how to fix that ?
EDIT
I found out that calling with('etude') had the action to remove the events registered to the Eloquent Model:
boot Method of Etude:
public static function boot()
{
parent::boot();
static::creating(function($etude)
{
var_dump("creating etude"); //This doesn't get executed even when I run Etude::create(Array());
}
);
}
So If I add Etude::boot() at the beginning of testEtudes, it works again. This is still strange.
Does eager loading has any effect on events or the boot method ? Or is the boot method not called automatically after each test ?
In Laravel tests, the event dispatcher is reset between each test, but the models are still only booted once as they live a pretty independent life. This means that between each test, the model listeners are erased but never re-registered. The solution is to not use boot() for registering model events, but rather but them in a separate file - either a service provider or a file included from app/start/global.php (app/events.php is a common one).