How to overwrite the laravel DatabaseManager method - laravel

There is a method in vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php
/**
* Parse the connection into an array of the name and read / write type.
*
* #param string $name
* #return array
*/
protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
}
If I want to overwrite this method how would I do it?

Since this method is protected, you have to create a class that extends Illuminate\Database\DatabaseManagerĀ in your appĀ folder and override that function with your own
Then inject your custom class into the container by binding a singleton
$this->app->bind('Illuminate\Database\DatabaseManager', function () {
return new App\CustomClass;
});
Hope this helps

Related

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());
}

Cannot declare class Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator because the name is already in use

I'm having a problem with Spatie Media Library. I created my class to use a different filesystem (specifically a Google bucket). Everything works smooth and I can integrate the filesystem correctly, save and view through the custom url. I created my class and gave what "Spatie" describes in its documentation as a namespace namespace Spatie\MediaLibrary\UrlGenerator;
. However, when I run the "artisan config: cache" command I get the error mentioned above.
Here my Custom Class Code extending BaseUrlGenerator:
namespace Spatie\MediaLibrary\UrlGenerator;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Filesystem\FilesystemManager;
class GcsUrlGenerator extends BaseUrlGenerator
{
/** #var \Illuminate\Filesystem\FilesystemManager */
protected $filesystemManager;
public function __construct(Config $config, FilesystemManager $filesystemManager)
{
$this->filesystemManager = $filesystemManager;
parent::__construct($config);
}
/**
* Get the url for a media item.
*
* #return string
*/
public function getUrl(): string
{
$url = $this->getPathRelativeToRoot();
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
$url = $this->rawUrlEncodeFilename($url);
$url = $this->versionUrl($url);
return config('medialibrary.gcs.domain').'/'.$url;
}
/**
* Get the temporary url for a media item.
*
* #param \DateTimeInterface $expiration
* #param array $options
*
* #return string
*/
public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string
{
return $this
->filesystemManager
->disk($this->media->disk)
->temporaryUrl($this->getPath(), $expiration, $options);
}
/**
* Get the url for the profile of a media item.
*
* #return string
*/
public function getPath(): string
{
return $this->getPathRelativeToRoot();
}
/**
* Get the url to the directory containing responsive images.
*
* #return string
*/
public function getResponsiveImagesDirectoryUrl(): string
{
$url = $this->pathGenerator->getPathForResponsiveImages($this->media);
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
return config('medialibrary.gcs.domain').'/'.$url;
}
}
Here what I included in the published vendor of medialibrary
'custom_url_generator_class' => \Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator::class,
What I'm missing here?
Thanks for helping me
According to the documentation you should implement the Spatie\MediaLibrary\UrlGenerator interface, not the namespace. Alternatively you can extend Spatie\MediaLibrary\UrlGenerator\BaseUrlGenerator which implements that interface itself. So the namespace of your custom class should still adhere to default naming, meaning it should have namespacing according to the folder structure and classname so it gets autoloaded properly.

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

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

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']);
}

Unable to override automatic model find method calls since upgrading to Laravel 5.1

I have a simple trait which I use to always include soft-deleted items for a few things:
trait OverrideTrashedTrait {
public static function find($id, $columns = ['*'])
{
return parent::withTrashed()->find($id, $columns);
}
}
However, since upgrading to Laravel 5.1, this no longer works. Soft-deleted items do not turn up in get() lists, and if I try to access a page where I've used route model bindings, I get the NotFoundHttpException.
Laravel's upgrade documentation states that:
If you are overriding the find method in your own models and calling parent::find() within your custom method, you should now change it to call the find method on the Eloquent query builder:
So I changed the trait accordingly:
trait OverrideTrashedTrait {
public static function find($id, $columns = ['*'])
{
return static::query()->withTrashed()->find($id, $columns);
}
}
But it appears that no matter what I write in there, it doesn't affect the results. I have also tried to put the overriding find() method directly in the model, but that doesn't appear to be working either. The only way anything changes is if I write invalid syntax. Even if I change the $id to a hardcoded id of an item that is not soft-deleted, I get the same result, and absolutely nothing happens if I e.g. try to dd('sdfg'), so I doubt the method is even called.
Edit: If I do trigger the method manually, it works just like intended.
How can I fix this?
Ok here it goes:
short version: Model binding does not use find.
longer version:
/**
* Register a model binder for a wildcard.
*
* #param string $key
* #param string $class
* #param \Closure|null $callback
* #return void
*
* #throws NotFoundHttpException
*/
public function model($key, $class, Closure $callback = null)
{
$this->bind($key, function ($value) use ($class, $callback) {
if (is_null($value)) {
return;
}
// For model binders, we will attempt to retrieve the models using the first
// method on the model instance. If we cannot retrieve the models we'll
// throw a not found exception otherwise we will return the instance.
$instance = new $class;
if ($model = $instance->where($instance->getRouteKeyName(), $value)->first()) {
return $model;
}
// If a callback was supplied to the method we will call that to determine
// what we should do when the model is not found. This just gives these
// developer a little greater flexibility to decide what will happen.
if ($callback instanceof Closure) {
return call_user_func($callback, $value);
}
throw new NotFoundHttpException;
});
}
Line 931 of Illuminate\Routing\Router says it does:
$instance->where($instance->getRouteKeyName(), $value)->first()
It uses the key from the model used in a where and loads the first result.
In Laravel 5.1 find() method can be found in Illuminate\Database\Eloquent\Builder
From your Model class you can override it like following:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* Overrides find() method in Illuminate\Database\Eloquent\Builder.
* Finds only active products.
*
* #param mixed $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
*/
public static function find($id, $columns = ['*']) {
// create builder from the Model
$builder = (new self)->newQuery();
// method customization
if (is_array($id)) {
// findMany() also should be customized
return self::findMany($id, $columns);
}
$builder->getQuery()->where("isActive", '=', 1)->where($builder->getModel()->getQualifiedKeyName(), '=', $id);
return $builder->first($columns);
}
}

Resources