Have a look at the following trait:
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 makes sure that the using class holds a certain model instance and it implements certain methods. This works as long as the implementing class does not override the constructor.
So here is my question: I want to make sure that either the constructor is called or a better solution, such that I can instantiate this model on initialization.
Please make in answer which respects Multiple inheritance as well es Multi-Level inheritance.
I needed a trait for database connection. To avoid using the __construct in a trait, I've used a magic getter instead:
trait WithDatabaseConnection
{
public function __get(string $name)
{
if ($name === 'pdo') {
return App::make(\PDO::class);
}
trigger_error("Property $name does not exist.");
return null;
}
}
class Foo {
use WithDatabaseConnection;
public function save() {
$this->pdo->query('...');
}
}
I think you are trying to make the trait do a job it is not designed for.
Traits are not a form of multiple inheritance, but rather "horizontal reuse" - they're often described as "compiler-assisted copy-and-paste". As such, the job of a trait is to provide some code, so that you don't have to copy it into the class manually. The only relationship it has is with the class where the use
statement occurs, where the code is "pasted". To aid in this role, it can make some basic requirements of that target class, but after that, the trait takes no part in inheritance.
In your example, you are concerned that a sub-class might try to access $primaryModel
without running the constructor code which initialises it, and you are trying to use the trait to enforce that; but this is not actually the trait's responsibility.
The following definitions of class Sub
are completely equivalent:
trait Test {
public function foo() {
echo 'Hello, World!';
}
}
class ParentWithTrait {
use Test;
}
class Sub inherits ParentWithTrait {
}
vs:
class ParentWithMethodDefinition {
public function foo() {
echo 'Hello, World!';
}
}
class Sub inherits ParentWithMethodDefinition {
}
In either case, class Sub
could have its own definition of foo()
, and by-pass the logic you'd written in the parent class.
The only contract that can prevent that is the final
keyword, which in your case would mean marking your constructor as final
. You can then provide an extension point that can be overridden for sub-classes to add their own initialisation:
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 trait can also mark its constructor as final, but this is part of the code being pasted, not a requirement on the class using the trait. You could actually use a trait with a constructor, but then write a new constructor as well, and it would mask the trait's version completely:
trait Test {
final public function __construct() {
echo "Trait Constructor";
}
}
class Noisy {
use Test;
}
class Silent {
use Test;
public function __construct() {
// Nothing
}
}
As far as the trait is concerned, this is like buying a bottle of beer and pouring it down the sink: you asked for its code and didn't use it, but that's your problem.
Crucially, though, you can also alias the methods of the trait, creating a new method with the same code but a different name and/or a different visibility. This means you can mix in code from traits which declare constructors, and use that code in a more complex constructor, or somewhere else in the class altogether.
The target class might 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";
}
}
The trait hasn't forced the Mixed
class to implement this pattern, but it has enabled it, in keeping with its purpose of facilitating code reuse.
Interestingly, the below code doesn't work, because the as
keyword adds an alias, rather than renaming the normal method, so this ends up trying 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";
}
}
Use a base class, this will let you handle the trait 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 are using traits you can also work with properties and getters & setters.
Example: A Storage trait involves that a Storage Engine
will be used. You can add the storageEngine property and its getters and setters. (with or without Type Hinting)
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 trait methods. This way you can use both constructors (from trait 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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With