How to pass function name to model event callback in Laravel 5 - laravel

I'd like to hook a model event to perform a task after the model has been deleted. I've added the following code to my model:
protected static function boot()
{
parent::boot();
static::deleted( 'static::removeStorageAllocation' );
}
Rather than put the logic I want to run inside a closure in the boot function, which seems a pretty ugly spot for it, I noticed in the method signature it supposedly takes "\Closure|string $callback" is there a way I can specify a function name like I've tried to do above? I can't seem to come up with anything that works. I've tried lots of combinations:
'self::removeStorageAllocation'
'static::removeStorageAllocation'
'\App\MyModel::removeStorageAllocation'
I know I can probably just specify a closure which calls my function, but I'm wondering what the string form of $callback is for?

You could just pass an anonymous function:
static::deleted(function() {
static::removeStorageAllocation();
});
To know the string representation of $callback, you could look at the source of deleted:
/**
* Register a deleted model event with the dispatcher.
*
* #param \Closure|string $callback
* #param int $priority
* #return void
*/
public static function deleted($callback, $priority = 0)
{
static::registerModelEvent('deleted', $callback, $priority);
}
You'll see it is registering an event listener:
/**
* Register a model event with the dispatcher.
*
* #param string $event
* #param \Closure|string $callback
* #param int $priority
* #return void
*/
protected static function registerModelEvent($event, $callback, $priority = 0)
{
if (isset(static::$dispatcher))
{
$name = get_called_class();
static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback, $priority);
}
}
Therefore, $callback is used eventually as a listener. A string representation would most likely be the name of a listener class, not a method.

Create a protected or public static function on your model (private will not work):
protected static function myStaticCallback($model)
{
// Your code
}
Then add a boot method to your model, using an array for the callback [class, function]:
protected static function boot()
{
parent::boot();
static::creating(['MyModel', 'myStaticCallback']);
}

Related

Having some issue with laravel collection and a callback function

pro's, amateurs and php enthousiasts.
I am working on a Laravel task wicht envolved dynamic data, collections and graphs.
In order to see what is wrong i kinda need some help, since I can't see it clearly anymore. I should pause and work on something else but this is a bottleneck for me.
I have a collection called orders.
in those orders I have grouped them by date. So far so good. Example below is a die and dump.
Exactly what i need in this stage.
"2022-01-29" => Illuminate\Support\Collection {#4397 ▶}
Now comes the mweh part.
I have a class called Datahandler
in that class I have three methods in it
simplified version of it:
Abstract Class DataHandler
{
/**
* Handles the conversion to dataset for the chart
*
* #param string $label
* #return void
*/
public function handle(string $label):void
{
$this->chart->addDataset($this->process->map(
$this->bind([$this, 'dataLogic'])
)->toArray()
, $label
);
}
/**
* Binds callbacks for the Handler of the class
*
* #param array $callable
* #return Closure
*/
function bind(array $callable): Closure
{
return function () use ($callable) {
call_user_func_array($callable, func_get_args());
};
}
/**
* Defines the fields I need to return to the collection
*
* #param Collection $group
* #return array
*/
#[Pure] #[ArrayShape(['total' => "int"])]
protected function dataLogic(Collection $group): array
{
return [
'total' => $group->count()
];
}
}
So in the handle function you can see I am binding ($this->bind()) my $this->process (collection data) to a callback ( $this->dataLogic() ). The protected function dataLogic is protected because every child of this Abstract class needs to have it's own logic in there.
so this function is being executed from within the parent, this is good cause it should be the default behaviour unless the child has the same function. If i do a var_dump on $group in method dataLogic I also have the correct value and the $group->count() also presents the corrent count of said data.
however the return is null. I am not so well trained in the use of callbacks, has anyone an idea on what is going wrong or even a better solution then the one I am trying to create?
forgot to mention the result of my code:
"2022-01-29" => null
It should be
"2022-01-29" => 30
Kind Regards,
Marcel
I solved it by doing the following.
I completely removed the bind function and handled my function as a callable for it got the needed solution, is there a better one, sure there is somewhere so any ideas are still welcome, but for now i can continue further.
Abstract Class DataHandler
{
/**
* Handles the conversion to dataset for the chart
*
* #param string $label
* #return void
*/
public function handle(string $label):void
{
$this->chart->addDataset($this->process->map($this->dataLogic()
)->toArray()
, $label
);
}
/**
* Defines the fields I need to return to the collection
*
* #return array
*/
#[Pure] #[ArrayShape(['total' => "int"])]
protected function dataLogic(): callable
{
return function ($group) {
return $group->count();
};
}
}

Query to all contents of tabel in related model in laravel

Assume we have a model name "Article" in Laravel and want to make query for retrieving latest articles, so one way is to define a method in the "Article" model like this:
public function newArticle()
{
return static::where('created_at', '>', Carbon::subMonths(1));
}
The question is, why we should use
static::
in the above code?
Is it possible to use
$this or self::
instead of
"static::" ?
Thanks in advance,
You could but there is not interest because:
where method does not exist statically on Illuminate\Database\Eloquent\Model class, so it calls __callStatic magic method which delegates the call to an instance
/**
* Handle dynamic static method calls into the method.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
It calls where method on instance but it does not exist either, so it calls __call magic method which delegates it an Illuminate\Database\Eloquent\Builder instance.
/**
* Handle dynamic method calls into the model.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
/**
* Get a new query builder for the model's table.
*
* #return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}

Laravel mass update , still fire event

As stated in the doc, laravel will not fire an event on mass update/insert/delete.
https://laravel.com/docs/5.8/eloquent#events
It uses the Builder for this and will not fire an event.
Is there a way that I can still fire an event after a mass update for example? I would only need the query Builder to extract the needed info myself ( log purposes).
It is actually possible , but you have to extend the Eloquent builder ,overwrite the update/insert methods and send the event there.
Just been playing around with it... Needs work, but the basic idea is the following :
class Test extends Model
{
protected $guarded = [];
public $dispatchesEvents = [
'saved' => SavedTest::class
];
/**
* INCLUDE this as a trait in your model.
* Overwrite the eloquentBuilder.
*
* #param \Illuminate\Database\Query\Builder $query
* #return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new TestBuilder($query);
}
}
Extend the eloquent builder...
class TestBuilder extends Builder
{
/**
* Update a record in the database and fire event.
*
* #param array $values
* #return int
*/
public function update(array $values)
{
// normal eloquent behavior.
$result =$this->toBase()->update($this->addUpdatedAtColumn($values));
/*
* Fire event.
*/
if($result){
if( $event = Arr::get($this->model->dispatchesEvents,'saved')){
// at the attributes.
$this->model->fill($this->addUpdatedAtColumn($values));
$queryBuilder =$this->toBase();
event(new $event($this->model,$queryBuilder));
}
}
}
public function insert(array $values)
{
// same idea..
}
}
The event class :
class SavedTest
{
use SerializesModels;
public $model;
public $query;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($model,$query =null)
{
$this->model = $model;
$this->query = $query;
}
}
The listener.
class SavedTestEvent
{
/**
* Create the event listener.
*
*
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
// The model , with the attributes.
dump($event->model);
// the query builder , you could extract the wheres or whatever to build your own log for it.
dump($event->query);
}
}
#Paolo on batch request it would not be file the event you must have to perform operation on single record.. like
Analytic::where('id', '>', 100)->get()->each(function($analytic) {
$analytic->delete();
});

How where method works in Laravel Models?

We frequently use where method with our models but it's not defined in Base Model class so how laravel is doing this magic?
e.g MyModel::where('id, 2)->get();
The above where will definitely fetch the record having id equals 2 but where & how is this happening! I traced back to the Base Model in Laravel but didn't found where method!
Some of this magic resides behind a Facade Pattern https://www.tutorialspoint.com/design_pattern/facade_pattern.htm
But in Laravel case, they use Static methods in their Facades (\Illuminate\Support\Facades namespace), and create instance automatically to call those methods from instance, so you don't have to instantiate the Class yourself to start using the methods. For example when you use DB::, Cache::, Str:: to call static method.
However for Eloquent, Laravel uses this approach more internally without exposing the Model to \Illuminate\Support\Facades namespace, by configuring Eloquent Model to instantiate automatically the Model and creates\Illuminate\Database\Eloquent\Builder Eloquent Builder and forward those static methods call to the Eloquent Builder instance or to Query Builder one.
A model extends \Illuminate\Database\Eloquent\Model which has a special way to call any method statically at runtime.
/**
* Handle dynamic static method calls into the method.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
So at a certain point, the Builder classes an Eloquent use a Trait
use Illuminate\Support\Traits\ForwardsCalls;
to forward call to each others so that a Model can forward its static method to method from the created instance of Eloquent builder. So if model does not have where method, it will forward it to the where method of the newly created nstance of Eloquent Builder.
From a look of Facade Pattern, here is how Laravel calls a method from an instance but in a static way :
// \Illuminate\Support\Facades\Facade.php
/**
* Handle dynamic, static calls to the object.
*
* #param string $method
* #param array $args
* #return mixed
*
* #throws \RuntimeException
*/
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
So calling User::where() creates an instance of \Illuminate\Database\Eloquent\Builder and forward method call to that instance, here is the method :
// Illuminate\Database\Eloquent\Builder.php
/**
* Add a basic where clause to the query.
*
* #param string|array|\Closure $column
* #param mixed $operator
* #param mixed $value
* #param string $boolean
* #return $this
*/
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$column($query = $this->model->newModelQuery());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
You can find the method here . framework/src/Illuminate/Database/Eloquent/Builder.php
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$column($query = $this->model->newModelQuery());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
Some more info about it

Laravel 4 - Extend Pagination Class

Is there a way to extend the Pagination Class of Laravel 4 ?
I tried some things but nothing good...
I'm here :
PaginationServiceProvider.php
class PaginationServiceProvider extends \Illuminate\Pagination\PaginationServiceProvider {
/**
* Indicates if loading of the provider is deferred.
* #var bool
*/
protected $defer = false;
/**
* Bootstrap the application events.
* #return void
*/
public function boot(){
$this->package('thujohn/pagination');
}
/**
* Register the service provider.
* #return void
*/
public function register(){
$this->app['paginator'] = $this->app->share(function($app){
$paginator = new Environment($app['request'], $app['view'], $app['translator']);
$paginator->setViewName($app['config']['view.pagination']);
return $paginator;
});
}
/**
* Get the services provided by the provider.
* #return array
*/
public function provides(){
return array();
}
}
Environment.php
class Environment extends \Illuminate\Pagination\Environment {
public function hello(){
return 'hello';
}
}
I replaced 'Illuminate\Pagination\PaginationServiceProvider', by 'Thujohn\Pagination\PaginationServiceProvider',
When I call $test->links() it's ok
When I call $test->hello() it fails
When I call Paginator::hello() it's ok
Any idea ?
Everyting is fine except that Paginator::make() returns Paginator instance, not Environment.
You should move Your method to Paginator class.
Today I've posted on GH my extension for Paginator. You can check it as a reference desmart/pagination

Resources