PHP traits: how to create constructors or force them to call?

Look at the following line:

trait PrimaryModelRest { use RestController; protected $primaryModel; public function __construct() { $mc = $this->getPrimaryModelClass(); try { $this->primaryModel = new $mc(); if(!($this->primaryModel instanceof Model)) { throw new ClassNotFoundException("Primary Model fatal exception: The given Class is not an instance of Illuminate\Database\Eloquent\Model"); } } catch (Exception $e) { throw new WrongImplementationException("Primary Model Exception: Class not found."); } } /** * @return string: Classname of the primary model. */ public abstract function getPrimaryModelClass(); // various functions here } 

As you can see, the trait ensures that the using class contains a specific instance of the model and implements certain methods. This works until the implementation class overrides the constructor .

So, here is my question: I want to make sure that either the constructor is called, or the best solution, so that I can instantiate this model during initialization.

Please indicate in your response that you respect Multiple inheritance as well as Multi-level inheritance .

+7
inheritance constructor php traits
source share
3 answers

I think you are trying to make a trait work for which it is not intended.

Traits are not a form of multiple inheritance, but rather "horizontal reuse" - they are often called "copy and paste using the compiler." Thus, the task is to provide some code, so you do not need to manually copy it to the class. Its only relation is to the class in which the use statement is executed, where the code is "inserted". To help in this role, he can make some basic requirements of this target class, but after that the trait does not take part in inheritance .

In your example, you are worried that a subclass might try to access $primaryModel without running the constructor code that initializes it, and you are trying to use this flag to provide this; but in reality this is not the main responsibility.

The following Sub class definitions are completely equivalent:

 trait Test { public function foo() { echo 'Hello, World!'; } } class ParentWithTrait { use Test; } class Sub inherits ParentWithTrait { } 

against

 class ParentWithMethodDefinition { public function foo() { echo 'Hello, World!'; } } class Sub inherits ParentWithMethodDefinition { } 

In any case, the Sub class can have its own definition of foo() and bypass the logic written in the parent class.

The only contract that can prevent this is the final keyword, which in your case means marking your constructor as final . You can then specify an extension point that can be overridden for subclasses to add their own initialization:

 class Base { final public function __construct() { important_things(); // Always run this! $this->onConstruct(); // Extension point } protected function onConstruct() { // empty default definition } } class Sub { protected function onConstruct() { stuff_for_sub(); // Runs after mandatory important_things() } } 

A distinguishing feature may also mark its constructor as final, but it is part of the inserted code, and not a requirement for the class using the attribute. You could use the trait with the constructor, but then write a new constructor, and also completely mask the version of the trait:

 trait Test { final public function __construct() { echo "Trait Constructor"; } } class Noisy { use Test; } class Silent { use Test; public function __construct() { // Nothing } } 

As for this feature, it is like buying a bottle of beer and pouring it into the sink: you asked for its code and did not use it, but that is your problem.

However, you can also use the methods of this attribute by creating a new method with the same code, but with a different name and / or with a different visibility. This means that you can mix code with attributes declaring constructors and use this code in a more complex constructor or somewhere else in the class.

The target class may also use the final + hook pattern:

 trait TestOne { final public function __construct() { echo "Trait TestOne Constructor\n"; } } trait TestTwo { final public function __construct() { echo "Trait TestTwo Constructor\n"; } } class Mixed { final public function __construct() { echo "Beginning\n"; $this->testOneConstructor(); echo "Middle\n"; $this->testTwoConstructor(); echo "After Traits\n"; $this->onConstruct(); echo "After Sub-Class Hook\n"; } use TestOne { __construct as private testOneConstructor; } use TestTwo { __construct as private testTwoConstructor; } protected function onConstruct() { echo "Default hook\n"; } } class ChildOfMixed extends Mixed { protected function onConstruct() { echo "Child hook\n"; } } 

This feature did not force the Mixed class to implement this template, but it allowed it, in accordance with its purpose, to facilitate code reuse.

Interestingly, the following code does not work because the as keyword adds an alias rather than renaming a regular method, so it tries to override the final constructor from Mixed :

 class ChildOfMixed extends Mixed { use TestTwo { __construct as private testTwoConstructor; } protected function onConstruct() { $this->testTwoConstructor(); echo "Child hook\n"; } } 
+11
source share

Use a base class, this will allow to treat this attribute as a parent.

 <?php trait StorageTrait { public function __construct() { echo "Storage Trait"; } } class StorageAttempt { use StorageTrait; public function __construct() { parent::__construct(); echo " - Storage Attempt"; } } abstract class StorageBase { use StorageTrait; } class MyStorage extends StorageBase { public function __construct() { parent::__construct(); echo ' - My Storage'; } } new StorageAttempt(); // won't work - will trigger error new MyStorage(); // will display "Storage Trait - My Storage" 

Also, if you use traits, you can also work with properties, getters, and setters.

Example. The storage element includes the use of the Storage Engine . You can add the storageEngine property and its recipients and setters. (with or without type prompt)

 interface StorageEngineInterface{} trait StorageTrait { /** * @var StorageEngineInterface */ protected $storageEngine; /** * @return StorageEngineInterface */ public function getStorageEngine(): StorageEngineInterface { return $this->storageEngine; } /** * @param StorageEngineInterface $storageEngine */ public function setStorageEngine(StorageEngineInterface $storageEngine) { $this->storageEngine = $storageEngine; return $this; } } 

Note: this is just an explanation, so you can better understand how Traits work

UPDATE

To avoid conflict, you can use aliases for feature methods. That way you can use both constructors (from attribute and from extended class), you can do the following

 class DifferentStorage { public function __construct() { echo ' diff '; } } class MyDifferentStorage extends DifferentStorage { use StorageTrait { StorageTrait::__construct as otherConstructor; } public function __construct() { parent::__construct(); self::otherConstructor(); } } 
+2
source share

You can use the interface implementation template: implement the iPrimaryModelRest interface in the same class that uses the PrimaryModelRest attribute:

 interface iPrimaryModelRest { public function init(); public abstract function getPrimaryModelClass(); } 

A class that uses the woud tag is as follows:

 class cMyClass implements iPrimaryModelRest { use PrimaryModelRest; } 

Then, whenever a class instance (not only automatically loads), you can call a special factory initialization function, like this:

 class cMyApp { public function start() { /** @var cMyClass $oClass */ // enlighten IDE $oClass = $this->init(new cMyClass); } public function init($oClass) { if ($oClass instanceof iPrimaryModelRest) {$oClass->init();} if ($oClass instanceof whateverinterface) { // pass optional stuff, like database connection } } } 

The interface is used to determine the capabilities of the class and sets data / performs the corresponding functions. If I am not mistaken, then this template is called Service Locator .

+2
source share

All Articles