I'm adding validation to a BaseModel class which all eloquent models will eventually extend in my application.
The base class resembles:
class BaseModel extends Eloquent {
private $errors;
private $validator;
public function __construct()
{
$this->validator = new BaseValidator(get_class($this));
}
public static function boot()
{
parent::boot();
static::creating(function($model) {
return false;
});
static::saving(function($model)
{
return false;
});
}
a sample concrete that extends the base:
class News extends BaseModel {
protected $guarded = ['audience', 'deleted_at'];
protected $table = 'zbw_news';
//relations, scopes, etc
}
Whenever I create a new object through artisan tinker, the saving closure isn't being triggered (it returns true when it should return false). However, I can bind a closure directly on the model's static (instead of late static binding) and it works as expected:
\News::saving(function($model) { return false; });
Any ideas why the boot method isn't being called properly or what I'm doing wrong here?
Related
In the application I'm working on, the Model part of the MVC stack is designed to work trough singletons; each Model has a __getInstanceMethod which is
protected static $singleton;
public static function __getInstance(): self {
if(self::$singleton === null) {
self::$singleton = __CLASS__;
self::$singleton = new self::$singleton;
}
return self::$singleton;
}
End result is, if __getInstance() is called twice on the same Model class, it returns the same exact object both times.
I tried to reduce code duplication by moving the __getInstance() method to the Model's parent class, BaseModel, by editing it like so.
class BaseModel {
protected static $singleton;
public static function __getInstance(): self {
if (static::$singleton === null) {
static::$singleton = static::class;
static::$singleton = new static::$singleton();
}
return static::$singleton;
}
}
class AModel extends BaseModel {
protected static $singleton;
/** ... */
}
class BModel extends BaseModel {
protected static $singleton;
/** ... */
}
AModel::__getInstance(); // AModel
BModel::__getInstance(); // BModel
Problem is, I need to manually add a $singleton property to each and every Model class, otherwise I'll always get returned the instance of the first Model class I called the method on.
class BaseModel {
protected static $singleton;
public static function __getInstance(): self {
if (static::$singleton === null) {
static::$singleton = static::$class;
static::$singleton = new static::$singleton();
}
return static::$singleton;
}
}
class AModel extends BaseModel {}
class BModel extends BaseModel {}
AModel::__getInstance(); // AModel
BModel::__getInstance(); // Still AModel
Is there a way I can avoid doing that?
You could switch to an "instance map", e.g.:
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
class BaseModel
{
protected static $instances = [];
public static function __getInstance(): self
{
if (!isset(static::$instances[static::class])) {
static::$instances[static::class] = new static();
}
return static::$instances[static::class];
}
}
class AModel extends BaseModel
{
}
class BModel extends BaseModel
{
}
echo get_class(AModel::__getInstance()), "\n";
echo get_class(BModel::__getInstance());
https://3v4l.org/qG0qJ
and with 7.4+ it could be simplified to:
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
class BaseModel
{
private static array $instances = [];
public static function __getInstance(): self
{
return static::$instances[static::class] ??= new static();
}
}
I have created my own DB - Model structure which is similar to Laravel. I have been facing with 2 problems.
I have a Model class which all of my models extend it. For example, my User class extends Model. I want to return that get() method return type of class which is extended.
Is this possible?
Class Model extends DB {
/**
* #return AnyClassThatExtended
*/
function get()
{
}
}
Class User extends Model {
function test() {
$user->get(); // I want it to return User type of object
}
}
You should use
private static $instance;
/**
* return static
*/
public function get() {
if (is_null(self::$instance)) {
self::$instance = new static();
}
return self::$instance;
}
because you are returning current class that you are at (if I understand correctly)
It's possible that PHPStorm does not recognize it
I have some problem and little misunderstanding Laravel SP (ServiceProvider). I have abstract class Repository and her Interface:
abstract class Repository implements RepositoryInterface {
private $model;
private $parser;
public function __construct() {
$this->model = new $this->model_name();
} }
interface RepositoryInterface {
public function create(array $attributes);
public function update($id, array $attributes);
public function delete($id);
public function all();
public function find($id);
public function filter(array $parameters, $query=null);
public function query(array $parameters, $query=null); }
and some child UserRepository for example:
class UserRepository extends Repository implements UserRepositoryInterface {
protected $model_name = "App\Models\User";
public function __construct() {
parent::__construct();
}
public function activation($user_id) {
return "user";
}
public function deactivation($user_id) {
return "user";
} }
and simple ModelParser class:
class ModelParser {
protected $parameters;
protected $model;
public function __construct($model) {
$this->model = $model;
} }
This work fine, but I would pass ModelParser as DI in my construct of abstract Repository with parameter $model. I dont have idea. How should I do it ?
I use it like this:
class UserController extends Controller {
private $repository;
public function __construct(UserRepository $repository) {
$this->repository = $repository;
} }
Well it's kinda complicated since your ModelParser requires a $model as it's parameter. And because this $model may vary depends on its repository, it will be too complicated if we're trying to resolve it using Laravel service container binding.
There's an easier approach, we can make the ModelParser class's constructor receive an optional $model parameter. Then we can add an additional method to set this $model property like so:
namespace App\Models;
class ModelParser
{
protected $parameters;
protected $model;
// Make $model parameter optional by providing default value.
public function __construct($model = null) {
$this->model = $model;
}
// Add setter method for $model.
public function setModel($model)
{
$this->model = $model;
return $this;
}
}
And now you can inject the ModelParser into your abstract Repository class. Laravel will easily resolve this ModelParser parameter
namespace App\Models;
use App\Models\ModelParser;
use App\Models\RepositoryInterface;
abstract class Repository implements RepositoryInterface
{
private $model;
private $parser;
// Pass ModelParser instance to your constructor!
public function __construct(ModelParser $parser)
{
$this->model = new $this->model_name();
// Set the parser's model property.
$this->parser = $parser->setModel($this->model);
}
// Rest of your code.
}
And if you're extending the abstract Repository class, you still have to pass this ModelParser to the constructor like so:
namespace App\Models;
use App\Models\ModelParser;
use App\Models\UserRepositoryInterface;
class UserRepository extends Repository implements UserRepositoryInterface
{
protected $model_name = "App\Models\User";
public function __construct(ModelParser $parser)
{
parent::__construct($parser);
}
}
Actually, if you're not planning to pass another parameter or perform something else during the class instantiation, you can simply remove the __construct() method from UserRepository and rely on its parent (the abstract Repository).
Hope this help!
I have a Model Eloquent called (TicketModel),
I add a global scope for take all tickets for a user , but sometimes , I want to use Ticket without this scope how can do it? how can ignore this scope
this is the model
<?php
class TicketModel extends Eloquent{
public $timestamps = false;
public static function boot()
{
static::addGlobalScope(new TicketScope);
}
}
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ScopeInterface;
class TicketScope implements ScopeInterface {
public function apply(Builder $builder)
{
$builder->where('user_id', '=', Auth::user()->id_user);
}
public function remove(Builder $builder){}
}
What about having a child class for the cases you need the scope?
Here's an example:
class TicketModel extends Eloquent
{
// Your model stuff here
}
class UserTicketModel extends TicketModel
{
public static function boot()
{
static::addGlobalScope(new TicketScope);
}
}
The idea is not to ignore the scope sometimes, it's to use it when you need it.
If you really want the model without the scope to be the exception, let a SimpleTicketModel inherit from TicketModel and override boot() method so that it does not use the scope, like this:
class TicketModel extends Eloquent
{
public static function boot()
{
static::addGlobalScope(new TicketScope);
}
}
class SimpleTicketModel extends TicketModel
{
public static function boot()
{
// Do nothing else
}
}
I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/