Is it OK to use properties/methods from parent classes in trait methods?
This code works, but is it good practice?
class Child extends Base{
use ExampleTrait;
public function __construct(){
parent::__construct();
}
public function someMethod(){
traitMethod();
}
}
trait ExampleTrait{
protected function traitMethod(){
// Uses $this->model from Base class
$this->model->doSomething();
}
}
I don't think it's good practice.
Instead you could have a method to fetch your model object, and have that method as an abstract signature in you trait:
trait ExampleTrait {
abstract protected function _getModel();
protected function traitMethod() {
$this->_getModel()->doSomething();
}
}
class Base {
protected $_model;
protected function _getModel() {
return $this->_model;
}
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod();
}
}
Or pass your model as a parameter to your trait method:
trait ExampleTrait {
protected function traitMethod($model) {
$model->doSomething();
}
}
class Base {
protected $_model;
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod($this->_model);
}
}
Both of these approaches let you utilize your IDE's type hinting.
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();
}
}
Given I have an abstract class:
abstract class User{
private function getNumber(){
return 'Get number';
}
}
With the child class
class UserComments extends User{
public function rows(){
return $this->getNumberOfRows();
}
}
Question:
Is there any way to call the getNumber function when I try to call getNumberOfRows method in child class, or when I call getNumberOfRows() in child class I want getNumber to be called instead
?
Due to PHP's Object inheritance you can directly call the specific method. However, in your example the getNumberOfRows() function is missing.
class UserComments extends User {
public function rows() {
return $this->getNumber();
}
}
You can do something like this
abstract class User{
private function getNumber(){
return 'Get number';
}
public function getNumberOfRows(){
return $this->getNumber();
}
}
With the child class
class UserComments extends User{
public function rows(){
return $this->getNumberOfRows(); //Defined in the parent class
}
}
Abstract methods cannot be private, abstract must be implemented by the class that derived it.
You can either use public, or if you do not want it to be visible outside, make it protected, as following:
abstract class User{
abstract protected function getNumber();
}
Once you do this, you can implement the getNumber method in User class:
class User {
protected function getNumber() {
// Do something
}
}
Update: Please note that protected methods are accessible by child classes, you can use "hierarchy":
abstract class User{
abstract protected function getNumber();
}
class UserComment extends User {
protected function comment() {
// Do something
}
protected function getNumber() {
return 3;
}
}
class Post extends UserComment {
public function myMethod() {
echo $this->getNumber();
}
}
Also you can use interfaces, just an example:
interface User {
public function getNumber();
}
class UserComment {
protected function myMethod() {
// Do something
}
}
class Post extends UserComment implements User {
final public function getNumber() {
return 3;
}
public function myMethod() {
echo $this->getNumber();
}
}
$post = new Post();
$post->myMethod();
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
}
}
Warning: might cause TL:DR
I am working with PHP 5.3.10 and have the following problem. I do have an abstract class DataMapper, which is extended for the specific DataModel I want to persist. The following code does this trick:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(IModel $model); // DISCUSSION
/* more helper functions here */
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic ... */ }
public function fetchAll() { /* ...magic ... */ }
public function save(IModel $model) { /* ...magic ... */ } // DISCUSSION
}
interface IModel {
public function setOptions(array $options);
public function toArray();
}
abstract class Model implements IModel {
protected $_fields = array();
protected $_data = array();
public function setOptions(array $options) { /* ...magic ... */ }
public function toArray() { /* ...magic ... */ }
public function __construct(array $options = null) { /* ...magic ... */ }
public function __set($name, $value) { /* ...magic ... */ }
public function __get($name) { /* ...magic ... */ }
}
class PersonModel extends Model {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
}
This works fine, but is really quirky for my feeling.
As you can see, I've used an interface IModel to be able to tell the DataMapper, that it does need a certain set of parameters and methods. However, some Models do have extra methods needed by the corresponding DataMapper - in the example, a checkPassword() method, which is used test a password against the stored hash value. This method may also instruct the DataMapper to rehash the just tested password and update it due to new requirements (e.g. an increased difficulty for a password hash function).
So what I actually want is to change the signature of PersonMapper to PersonMapper::save(PersonModel $model) - and e.g. in another DataMapper toPostMapper::save(PostModel $model), etc. This is due to these DataMappers needing a certain signature. So my ideal solution looks like this:
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public abstract function save(Model $model); // UPDATED
}
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) { /* ...magic... */ } // UPDATED
}
abstract class Model { /* ...unchanged... */ }
class PersonModel extends Model { /* ...unchanged... */ }
Notice the Update save-Methods in the abstract class and its implementation. Since PersonModel is inherited from Model, thus obviously having a common base set of signatures, I would expect this to work just fine. But it doesn't - PHP complains about a changed interface in the childclass PersonMapper
My Questions:
Is there another solution working with PHP 5.3.10 that expresses the relationship better?
Does it work in a later version of PHP, so that it might be worth upgrading the server?
You might try using interfaces instead.
interface OtherModel {
public function getThis();
}
interface OtherOtherModel {
public function getThat();
}
Your Model Class might implement one or more interfaces...
class PersonModel extends Model implements OtherModel {
protected $_fields = array('id', 'name', 'passhash', /*...*/);
public function setId($value) {
/* ...Validation happening... */
$this->_data['id'] = $value;
return $this;
}
public function checkPassword($password) { /* ...magic... */ }
public function getThis() {
// ...
}
}
Your concrete Mapper Class can use the instanceof to check if this Model does what it should.
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(Model $model) {
// verify that certain methods are implemented...
// throw an exception or reacting accordingly
print ($model instanceof PersonModel)? 'yes' : 'no';
print ($model instanceof OtherOtherModel)? 'yes' : 'no';
}
}
Another possible approach might be the following:
<?php
abstract class DataMapper {
public abstract function findById($id);
public abstract function fetchAll();
public function save(Model $model) {
throw new Exception('You have to implement this!');
}
}
Throw an Exception if the save method is not overriden in an inheriting class.
Now you can really use a different typehint.
This will work:
class PersonMapper extends DataMapper {
public function findById($id) { /* ...magic... */ }
public function fetchAll() { /* ...magic... */ }
public function save(PersonModel $model) {
// do something
}
}
I could think of another possible approach, by using interfaces to define the implementation.
Like for example:
interface PersonModelAware {
public function save(PersonModel $model);
}
interface OtherModelAware {
public function save(OtherModel $model);
}
etc. Your abstract method might have a default save method or no save method at all. The inheriting class will implement the interface it needs.
To sum it up, making your type more specific will not work as the abstract method clearly states it expects a Model.