Laravel - How to call function from trait boot - php

I just create a Trait to generate slugs to my models:
<?php
namespace App\Traits;
trait SlugGenerator
{
public function slugGenerator($slug = null)
{
return $slug ?? 'UUIDGeneratorFunction';
}
public static function bootSlugGenerator()
{
static::creating(function ($model) {
// How to call slugGenerator() function here?
$model->slug = slugGenerator();
});
}
}
The question is: How to call the slugGenerator() function inside my trait boot?
And if I want to change the slug Generator from any model, the way I'm setting the $slug variable is right? Example:
<?php
namespace App\Models;
use App\Traits\SlugGenerator;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use SlugGenerator;
public function slugGenerator($slug = null)
{
return 'customSlug';
}
}

The event receives the instance of the model, so you can call the method on that instance:
public static function bootSlugGenerator()
{
static::creating(function ($model) {
$model->slug = $model->slugGenerator();
});
}
Docs: https://laravel.com/docs/5.8/eloquent#events

Related

Auto create logs (inserts data to table) whenever a function was called

I'm new to laravel and to php oop. My main goal is to call createLogs() everytime a function is called without putting the call method in each function because it's a hassle. I need help please.
I made a controller called WebLogs with a function called createLogs() that inserts data to a table. I want it to be auto-called whenever another function is called. I tried using this solution and put it in Controller class because WebLogs extends Controller class, and all my other controllers extends Controller class, but the solution doesn't seem to work.
So my Controller class now looks like this:
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
// Added this function from the solution I mentioned
public function __call($method, $arguments) {
echo 'hello world';
echo '<br><br>';
echo $method;
if(method_exists($this, $method)) {
return call_user_func_array(array($this,$method),$arguments);
}
}
}
Example controller:
class DashboardController extends Controller
{
public function index()
{
(new WebLogs)->createLogs(); //I don't want to call this for every function
return view('dashboard');
}
public function showSomething()
{
(new WebLogs)->createLogs();
return view('something');
}
public function updateSomething()
{
(new WebLogs)->createLogs();
return redirect()->back()->with('message','yeey');
}
}
How the functions from DashboardController are being called from web.php:
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/something', [DashboardController::class, 'showSomething'])->name('something');
Route::post('/something/update', [DashboardController::class, 'updateSomething'])->name('something.update');
Create app\Classes\WebLogs.php with content:
<?php
namespace App\Classes;
class WebLogs {
public function __construct() {
return "WebLogs class with construct function was initialized.";
}
public function createLogs($routeName,$routePath) {
$status = 0;
logger('WebLogs class is running:');
logger([$routeName,$routePath]);
// Save to database here
// ...
return $status;
}
}
Then, create an AutoCreateLogs middleware, it will save as app\Http\Middleware\AutoCreateLogs.php:
$ php artisan make:middleware AutoCreateLogs
With content:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Classes\WebLogs;
class AutoCreateLogs
{
public function handle(Request $request, Closure $next)
{
$route = Route::current();
$routePath = $route->uri;
$routeName = $route->action['as'];
$w = new WebLogs;
$w->createLogs($routeName,$routePath);
return $next($request);
}
}
And use this middleware like this:
Route::middleware([AutoCreateLogs::class])->group(function () {
Route::get('/', [App\Http\Controllers\DashboardController::class, 'index'])->name('dashboard');
Route::get('/something', [App\Http\Controllers\DashboardController::class, 'showSomething'])->name('something');
Route::post('/something/update', [App\Http\Controllers\DashboardController::class, 'updateSomething'])->name('something.update');
Route::get('/something/{value}', [App\Http\Controllers\DashboardController::class, 'getSomething'])->name('get.something');
});
With app\Http\Controllers\DashboardController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index()
{
$page = 'index';
return view('welcome', ['page'=>$page]);
}
public function showSomething()
{
$page = 'showSomething';
return view('welcome', ['page'=>$page]);
}
public function updateSomething()
{
$page = 'updateSomething';
return response()->json(['page'=>$page]);
}
public function getSomething(Request $request)
{
$page = 'getSomething';
return view('welcome', ['page'=>$page]);
}
}
Then, empty storage\logs\laravel.log, and run with example route
http://laravel-me.com/something/value99
It will show the successful result:
[2022-02-18 22:56:09] local.DEBUG: WebLogs class is running:
[2022-02-18 22:56:09] local.DEBUG: array (
0 => 'get.something',
1 => 'something/{value}',
)
Read more about middleware: https://laravel.com/docs/8.x/middleware

How to delete parent static methods in extended class

Here is my model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Associate extends Model
{
// some code
}
In controller I use this model similar this
<?php
namespace App\Http\Controllers;
use App\Models\Associate;
use Illuminate\Http\Request;
class AssociatesController extends Controller
{
protected $associate;
public function __construct(Associate $associate)
{
$this->associate = $associate;
}
public function edit(Request $request, $id)
{
$associate = $this->associate->with('some-relation')->find($id);
// other part of code
}
}
When i wont to testing in controller edit method using phpunit I cant mock with method because it is static method of Illuminate\Database\Eloquent\Model.
My question there is way to delete some method of parent class??
From Laravels documentation
static Builder|Model with(array|string $relations)
Being querying a model with eager loading.
From Php docs
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // Here comes Late Static Bindings
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
The above example will output:
B

Create a model with a required relationship

I'm trying to create a model that has a relationship which is required for the object to be valid. Querying this model should not return any results that are missing this relationship. It seems like global scopes are the best option for this scenario, however I've been unable to make this work. Am I doing something wrong? Perhaps there's a better way?
Here is a simplified version of the model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Car extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function ($builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}
And here is a one-to-many relationship method on another model.
public function cars()
{
return $this->hasMany(Car::class);
}
Without the global scope, this code returns all related "cars", including ones without "details". With the global scope, no "cars" are returned. I want this code to only return "cars" with "details".
Thank you.
You have some mistakes at Anonymous Global Scopes declaration:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Car extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function (Builder $builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}
You might try eager-loading the relationship, so that the has() inspection will actually see something. (I suspect because the relationship is not loaded, the details relationship is never populated.)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Car extends Model
{
protected $with = ['details'];
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function (Builder $builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}

How to register a default Observer for every Model instance with Laravel

Laravel 5.1
I'm trying to register a single model observer for every Model that extends my AbstractModel (who are extending Illuminate\Database\Eloquent\Model).
The problem is my GenericModelObserver can't listen to events fired by Models inheriting AbstractModel.
Let me show what I did so far.
A Service Provider was created and put on the last position of the providers array inside config/app.php
<?php
// app/Providers/ObserverServiceProvider.php
namespace App\Providers;
use App\Models\Quotation;
use App\Models\AbstractModel;
use App\Observers\QuotationObserver;
use App\Observers\GenericModelObserver;
use Illuminate\Support\ServiceProvider;
class ObserverServiceProvider extends ServiceProvider
{
public function boot()
{
AbstractModel::observe(GenericModelObserver::class);
Quotation::observe(QuotationObserver::class);
}
public function register()
{
}
}
Then I have my plain simple GenericModelObserver
<?php
// app/Observers/GenericModelObserver.php
namespace App\Observers;
use App\Models\AbstractModel;
class GenericModelObserver
{
public function saving(AbstractModel $model)
{
return $model->valid();
}
}
The Abstract Model
<?php
// app/Models/AbstractModel.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AbstractModel extends Model
{
// ...
}
My Quotation Model
<?php
// app/Models/Quotation.php
namespace App\Models;
class Quotation extends AbstractModel
{
// ...
}
When Quotation is saved, the GenericModelObserver can't listen to the saving event or any other event.
The same applies for other Models that don't have a specific Model Observer.
Is this the right strategy? I would not like to bind a observer to every model through the boot method.
Instead of extending model - write your own trait which will work as observer.
Below I wrote some basic trait:
<?php
namespace App\YourPackage\Traits;
use Illuminate\Database\Eloquent\Model;
trait Observable
{
public static function bootObservable()
{
static::updating(function (Model $model) {
dd('updating');
});
}
}
and use it by typing use Observable; in your model class.
Also for your learning take a note how traits is booting: You have to put boot[TraitClassName] method into trait, to boot it properly.
Never write boot method inside your trait, it's dangerous!
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Observers\TeamObserver;
class Team extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
self::observe(TeamObserver::class);
}
}
Why not simply extend a parent class say BaseObserver
I have something similar in my caching system
<?php namespace App\Observers;
class BaseObserver {
public function saving($model)
{
//do your thing here that apply to all observers, like caching
}
}
Then in your Observers
<?php namespace App\Observers;
class Quotation extends BaseObserver{
//you can override any of the methods if you wish
}
Update the boot method in your AppServiceProvider to the following:
public function boot()
{
# Register all model observers
$filesInFolder = \File::files(app_path('/Observers'));
foreach($filesInFolder as $path) {
$observerClassName = pathinfo($path)['filename'];
$className = str_replace('Observer', '', $observerClassName);
$observerClassName = 'App\\Observers\\' . $observerClassName;
$className = 'App\\' . $className;
$className::observe($observerClassName);
}
}
Models should follow this format:
App\User
Observers should follow this format:
App\Observers\UserObserver
When models are in the 'models' folder:
Swap this $className = 'App\\' . $className; for this $className = 'App\\Models\\' . $className;
In your parent model you can do something like that
/**
* If true will attach the observers of the parent class
* #var bool
*/
protected $shouldAttachParentObservers = true;
public static function boot()
{
$instance = new static;
$instance->attachParentObservers();
parent::boot();
}
public function attachParentObservers() {
$parentClass = get_parent_class($this);
if(!empty($parentClass) && $this->shouldAttachParentObservers) {
$eventObservers = [];
foreach ($this->getObservableEvents() as $event) {
$eventObservers[$event] = ($this::$dispatcher->getListeners("eloquent.{$event}: {$parentClass}"));
foreach ($eventObservers[$event] as $observer) {
$eventName = "eloquent.{$event}: {$this::getClassName()}";
$this::$dispatcher->listen($eventName, $observer);
}
}
}
}
/**
* You may use different way to find the class name
*/
public static function getClassName() {
return static::class;
}

Laravel 5 Resolving dependencies in ServiceProvider

I have a class which acts like a storage (add/get item). I try to bind it as a singleton in one service provider, and resolve it in another's boot method.
The code is changed for simplicity.
app/Providers/BindingProvider.php
<?php namespace App\Providers;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider as ServiceProvider;
class MyBindingFacade extends Facade {
public static function getFacadeAccessor() {
return 'my.binding';
}
}
class MyBinding {
protected $items = [];
public function add($name, $item) {
$this->items[$name] = $item;
}
public function get($name) {
return $this->items[$name];
}
public function getAll() {
return $this->items;
}
}
class BindingProvider extends ServiceProvider {
public function register() {
$this->app->singleton('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
}
public function provides() {
return [
'my.binding',
];
}
}
app/Providers/ResolvingProvider.php
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider as ServiceProvider;
use App\Providers\MyBinding;
class ResolvingProvider extends ServiceProvider {
public function boot(MyBinding $binding) {
$binding->add('foo', 'bar');
// $manual = $this->app->make('my.binding');
// $manual->add('foo', 'bar');
}
public function register() {}
}
app/Http/Controllers/WelcomeController.php
<?php
namespace App\Http\Controllers;
use App\Providers\MyBindingFacade;
class WelcomeController extends Controller {
public function index()
{
dd(MyBindingFacade::getAll()); // debug items
}
}
When I try to debug MyBinding state in my WelcomeController I'm getting empty item array. However, if I uncomment $manual part from my ResolvingProvider it returns an array containing 'foo' => 'bar'. Does it mean IoC resolution is broken in ServiceProvider::boot() method or am I misusing Laravel functionality?
Laravel version: 5.0.28
UPDATE: Added code sample from WelcomeController.
With this:
$this->app->singleton('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
You're saying: my.binding is a singleton and resolves to an instance of App\Providers\MyBinding.
That doesn't mean that App\Providers\MyBinding is registered as singleton too. What you should do instead is this:
$this->app->singleton('App\Providers\MyBinding');
$this->app->bind('my.binding', function($app) {
return $app->make('App\Providers\MyBinding');
});
Because the Facade binding uses $app->make() you should get the same instance you registered with $this->app->singleton() right above.
In the first example you are not using the Facade, you should be using:
use App\Providers\MyBindingFacade as MyBinding;
Which will in fact call make it using 'my.binding'.

Categories