Using `boot()` on a model conflicts with RevisionableTrait - php

I'm using the Revisionable package in my laravel app to log edits to a model.
In addition I have also implemented an observer class to listen for specific model events (update, delete, create etc) and perform actions (such as clear caches etc). This observer is instantiated in the model using the boot() method as follows:
class Client {
use \Venturecraft\Revisionable\RevisionableTrait;
public static function boot()
{
parent::boot();
Client::observe(new App\Observers\ClientObserver);
}
}
What I find is that when I define a boot() method in my model the Revisionable Trait stops working and does not log changes - presumably because it too uses a boot method that is being overridden by the one in the model.
How would I fix this to allow listening for model events as well as utilizing the Revisionable package?

this link helped me
https://github.com/VentureCraft/revisionable/issues/175
I used in laravel 5.1 i hope it will work to you
use RevisionableTrait, UuidTrait {
UuidTrait::boot insteadof RevisionableTrait;
}

Related

Laravel - Is it possible to replace/extend a trait from within Laravel core?

Is this possible? I am using a multi-tenancy package that conflicts with other Laravel packages such as Scout. Ideally I want to be able to replace/extend the Illuminate\Queue\SerializesModels trait within the core.
Obviously the other way to do this is just replace that trait with my own one in every job that runs, but it does then mean in the case of things like Laravel Scout I have to rewrite the scout classes, just to replace this one trait, within my own app which feels wrong.
As an example, Laravel Scout has the MakeSearchable job class. In order to replace the trait in this model I have to make my own implementation of the class which is identical to the package apart from changing this one trait:
class MyMakeSearchable implements ShouldQueue
{
use TenantAware, Queueable, MySerializesModels;
/** #var Collection */
public $models;
public function __construct($models)
{
$this->models = $models;
}
public function handle()
{
if (count($this->models) === 0) {
return;
}
$this->models->first()->searchableUsing()->update($this->models);
}
}
To connect up this job class, I then have to override the Scout Searchable trait too.
It would be nice if I could just say to Laravel "Whenever you want this SerializesModels trait, use this one instead`

How can I register a View::composer from a Laravel package?

How can I register a View::composer for use in a Laravel project from within a Laravel package?
In my package service provider I have a boot method with routes/views etc and this in the register function:
public function register()
{
$this->app->register(ComposerServiceProvider::class);
}
In the ComposerServiceProvider I have:
public function boot()
{
View::composer(
'admin.*', ProfileComposer::class
);
}
Which should load the ProfileComposer class into all admin.* views, but it's not working. It's definitely loading the class as a dd('Test'); in the boot method shows the 'Test' message in the browser, just not applying the view composer.
I can't see anything in the Laravel documentation regarding loading View Composers from packages
This code has been extracted from my working laravel project for use as a package going forward but the view composers are causing issues
A snippet from one of my own packages that works:
class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function boot(Factory $view)
{
$view->composer('template::name', ProfileComposer::class);
}
}
The official docs don't mention packages, but it works exactly the same. The only difference is the place on the filesystem, but use the correct namespaces and you can just follow the docs: https://laravel.com/docs/5.8/views#view-composers

Laravel 5.8 syncing / attaching / detaching events

Laravel 5.8 is supposed to dispatch the syncing, attaching and detaching events (https://laravel.com/docs/5.8/releases search for Intermediate Table / Pivot Model Events section).
UPDATE: the release notes have been update after posting this question (more info: https://github.com/laravel/framework/issues/28050 - https://github.com/laravel/docs/pull/5096).
I tried it out but the following code throws the exception:
Call to undefined method App\ProjectUser::syncing()
NOTE: since Laravel 5.8 is supposed to dispatch the syncing event I don't want to use an external package.
class User extends Model
{
public function projects()
{
return $this->belongsToMany(\App\Project::class)->using(\App\ProjectUser::class);
}
}
class Project extends Model
{
public function users()
{
return $this->belongsToMany(\App\User::class)->using(\App\ProjectUser::class);
}
}
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::syncing(function ($item) {
dd('syncing event has been fired!');
});
}
}
// web.php
$project = \App\Project::first();
$project->users()->sync([1,2]);
I tried to move the boot method from ProjectUser to User and Project but I get the same exception.
On Laravel 5.8, when you are using the methods sync, attach or detach is going to be fired the appropriate model events (creating, updating, saving, ...) for the called action. Note that using sync, attach or detach is not going to fire any event like syncing, attaching or detaching.
More specifically, the sequence of events fired for each element passed to the sync method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the attach method are:
saving
creating
created
saved
The sequence of events fired for each element passed to the detach method are:
deleting
deleted
So if you want to observe the syncing operation you actually have to observe the saving (or saved) event from the pivot model (in this case ProjectUser):
class ProjectUser extends Pivot
{
public static function boot()
{
parent::boot();
static::saving(function ($item) {
// this will die and dump on the first element passed to ->sync()
dd($item);
});
}
}
A working example https://github.com/danielefavi/laravel-issue-example
More info on this issue https://github.com/laravel/framework/issues/28050
The release notes were misleading and they have been changed https://github.com/laravel/docs/pull/5096.
If detach method called without ids (for detach all relations), events are not firing
https://github.com/laravel/framework/pull/27571#issuecomment-493451259
i tried many different way for the solve this need, but it is impossible without use external package or override many method.
I choose chelout/laravel-relationship-events package.
It's look clean and understable. And use with trait.

Extend Laravel Logging to Include Method

I'm in Laravel 5.1, for reference.
I'm looking to extend the Log feature in such a way that running it from any point in the code (including in the built in vendor libraries) will also trigger an additional method of my choosing. How would I go about doing this?
Laravel fires an event named illuminate.log when logging the data. So your can listen to that event and can call your extra methods.
For example:
Add following code in your boot() method of class App\Providers\AppServiceProvider:
public function boot()
{
\Event::listen('illuminate.log', function ($level, $message, $context) {
// call your extra functions
});
}

Adding routes to laravel via plugins

I am working on designing a specific web framework that allows our team to add new components as plugins, then allowing customers to add these plugins or modules using a control panel.
With CodeIgniter things were easy , just copy the controller into the controllers folder and the client-side module will find its way via the URL app/index.php/module/function
But Laravel doesn't allow such dynamic routing.
Is there anyway to extend the route configuration without editing the routes.php by hand ?
You can simply add any routes you want in your service provider's 'boot' method:
public function boot()
{
$this->app['router']->get('my-route', 'MyVendor\Mypackage\MyController#action');
}
If you want to have a kind of automatic prefix, that doesn't happen automatically, but it's not too hard to create one:
public function boot()
{
$this->app['router']->group(['prefix' => 'my-module'], function ($router) {
$router->get('my-route', 'MyVendor\MyPackage\MyController#action');
$router->get('my-second-route', 'MyVendor\MyPackage\MyController#otherAction');
});
}
A lot of people will have this prefix as a config variable so that developers can choose the prefix they want (if you do this remember to name your routes so you can refer to them easily):
public function boot()
{
$this->app['router']->group(['prefix' => \Config::get('my-package::prefix')], function ($router) {
$router->get('my-route', 'MyVendor\MyPackage\MyController#action');
$router->get('my-second-route', 'MyVendor\MyPackage\MyController#otherAction');
});
}
I know I'm bit late but in Laravel 5.4 we can achieve something like this:
Step 1 Create your package and create service provider in it.
Step 2 Register your package service provider in laravel config app.
Step 3 Now create a sperate routes service provider which will contain following
namespace MyPackage\Providers;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Route;
class MyPackageRouteServiceProvider extends RouteServiceProvider
{
protected $namespace='MyPackage\Controllers';
public function boot()
{
parent::boot();
}
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
protected function mapApiRoutes()
{
Route::prefix('Mypackage\api')
->middleware('api')
->namespace($this->namespace)
->group(__DIR__ . '\..\Routes\api.php');
}
protected function mapWebRoutes()
{
Route::prefix('Mypackage')
->middleware('web')
->namespace($this->namespace)
->group(__DIR__ . '\..\Routes\web.php');
}
}
Note: I'm considering there is Routes Folder and contain web.php and api.php file. According to your question you want to load it dynamically you can have a constructor function and pass the package name, prefix and namespace as per your ease.
Step 4 Now the final step is registering the service provider you can call something like this in your package service provider:
public function boot()
{
$this->app->register('Mypackage\Providers\MyPackageRouteServiceProvider');
}
Hope this helps. Cheers
Just some theory
That's in fact pretty easy! When you think about it, Laravels routing layer is also just a component that is bound to Laravels container.
That allows us to grab it from there wherever we're accessing the container. Since you're trying to modify routes in a package, a great place to do it would be in your packages Service Provider.
Also, when doing that in a Service Provider you'll automatically have access to the app property (Your service provider is a child class of Laravels ServiceProvider class) and you can grab the router pretty easy!
Hands on code
<?php namespace My\Packages\Namespace;
use Illuminate\Support\ServiceProvider;
class MyPackageProvider extends ServiceProvider {
public function boot()
{
$this->app['router']->get('package-route', function(){
return "I just dynamically registered a route out of my package";
});
}
}
That's the Service Provider of your package. The only thing the user will have to do is to add the Service Provider to his providers array in the config/app.php.
Be careful!
When a user has defined a route that is identically named as your dynamically added route, your route will be overwritten. Make sure that you're using some kind of route prefixes if you are dynamically adding routes.
Further Reading
Laravel Docs - IoC Container

Categories