How to Overload Class Constructor Within Traits in PHP >= 5.4

How to overload class constructor within traits in PHP = 5.4

I think for now the only way to do what you want is:

class MyHelloWorld extends Base {

use SayWorld {
SayWorld::__construct as private __swConstruct;
}

public function __construct($a, $b, $c = 0)
{
$this->__swConstruct($a, $b, $c);
}
}

Edit 2:

My advice, based on over a year of dealing with traits in PHP, is: avoid writing constructors in traits at all, or if you must - at least make them parameterless. Having them in traits goes against the idea of constructors in general, which is: constructors should be specific to a class to which they belong. Other, evolved high-level languages don't even support implicit constructor inheritance. This is because constructors have far more stronger relation to the class then other methods. In fact they have so strong relation, that even the LSP does not apply to them. The traits in Scala language (a very mature and SOLID-friendly successor of Java), can't have a constructor with parameters.

Edit 1:

There was a bug in PHP 5.4.11, which actually allowed to alias a superclass method. But this was considered a no-no by the PHP developers, so we are still stuck with that cumbersome solution which I presented above. But that bug raised a discussion about what can be done with this, and I'm hoping it will be targeted in future releases.

Meanwhile I came across the same problem over and over again. My irritation raised exponentially with the number of parameters and lines of docblock which had to be repeated a lot of times in order to use the trait. So I came up with the following pattern in order to stick to the DRY rule as much as I could:

Instead of repeating entire set of parameters like this:

trait SayWorld {

/**
* This is a valid docblock.
*
* @param int $a Doc comment.
* @param int $b Doc comment.
*/
public function __construct($a, $b) {
echo (int)$c * ($a+$b);
}
}

class MyHelloWorld extends Base {

use SayWorld {
SayWorld::__construct as private __swConstruct;
}

/**
* Repeated and unnecessary docblock.
*
* @param int $a Doc comment.
* @param int $b Doc comment.
* @param int $c Doc comment.
*/
public function __construct($a, $b, $c = 0)
{
$this->__swConstruct($a, $b);
}
}

I write a class much like a tuple (concept familiar to C# and Python users), and use it instead of an endless list of parameters:

class SayWorldConstructTuple
{
public $a;

public $b;

public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}

class MyHelloWorld extends Base {

use SayWorld {
SayWorld::__construct as private __swConstruct;
}

/**
* New and valid docblock.
*
* @param SayWorldConstructTuple $Tuple
* @param int $c Additional parameter.
*/
public function __construct(SayWorldConstructTuple $Tuple, $c = 0)
{
$this->__swConstruct($Tuple->a, $Tuple->b);
$this->c = $c;
}
}

Note: this pattern is of course more useful with a larger amount of tuple's constructor parameters, and more classes using the tuple.

It can be automated further with the use of PHP's dynamic nature.

Execute constructor in trait

Try it like this (test):

trait test{
public function __construct()
{
echo 'test';
}
}

class myClass{
use test {
test::__construct as private __tConstruct;
}
public function __construct(){
$this->__tConstruct();
}
}
new myClass();

PHP Class Has Colliding Constructor Definitions Coming From Traits

Although, it's not posted here, I'm guessing that class/Database.class.php has a constructor in it as well. Since PHP doesn't really do function overloading, there can only be one constructor - you have two, the inherited one from Database and the other from generalFunctions.

What you'll need to do is alias your trait constructor function with the use statement

class User extends Database {
use generalFunctions {
generalFunctions::__construct as private traitConstructor;
}

public function trialEncrypt ($data) {
// some code here which need encryptData function from General class
}
}

Another Example: How to overload class constructor within traits in PHP >= 5.4

Also for reference: PHP Traits

PHP Trait colliding constructor

you can resolve the constructor collision like this.

trait MyTrait {

use BaseSomeTrait{
BaseSomeTrait::__construct as private __otherConstruct;
}

public function __construct(/* maybe params here*/)
{
// maybe other code
$this->__otherConstruct(/* maybe params here*/);
// maybe other code
}
}

if MyClass has a constructor as well you need to do it there additionally, or maybe only there if MyTrait has no constructor...

How can constructor definitions from traits collide?

Try what happens with the following code:

class A {
use T;
use T;
}

Because this is what you effectively wrote by extending from A and then using T again for B.

If you need to use trait T in base and subclasses, use it only in the base-class.

If you need it in subclasses only, use it only in the leaf subclasses.

Laravel pass arguments in trait constructor

Defining __constructor within trait is actually wrong. Or just a bad design. Constructors should be specific to a class to which they belong, not traits. Another issue then, you're importing trait in Model class, that means you should specifically follow its rule about how a trait in a model is loaded.

At the booting stage of a model, it searches imported traits recursively within class and automagically call a method that is using boot{TraitNameHere} naming convention, statically. That proves traits in model does not involved in Laravel's dependency injection cycle.

To make it happen, you can use Laravel global helper to load stored instance inside container, like the facade App::make(DefinedKeyHere). Then store the assigned instance into a static property to make it retained until the runtime ends and also because the recalling method is static.

trait TimezoneTrait
{
protected static $userRepository;

protected static function bootTimezoneTrait()
{
static::$userRepository = \App::make(UserRepositoryInterface::class);
}
}

If you're currently trying to avoid using global helper, listening to the model booting event is also helpful. Example inside EventServiceProvider,

Event::listen('eloquent.booting:*', function (Model $model) {
$model->setUserRepository($this->app[UserRepositoryInterface::class]);
});

Then the trait would be,

trait TimezoneTrait
{
protected static $userRepository;

public function static setUserRepository(UserRepositoryInterface $userRepository)
{
static::$userRepository = $userRepository;
}
}

Take a note that I defined setUserRepository as static, but you can also make it non-static, too.

And to expand a little bit about model event, model has several events to fire whenever it's doing its related action.

Example events from Laravel 5.5,

public function getObservableEvents()
{
return array_merge(
[
'creating', 'created', 'updating', 'updated',
'deleting', 'deleted', 'saving', 'saved',
'restoring', 'restored',
],
$this->observables
);
}

And other two default events that are fired when its instantiated (also unserialized) which are booting and booted. And the method which is use to fire the event, notice the event name.

protected function fireModelEvent($event, $halt = true)
{
// ...

return ! empty($result) ? $result : static::$dispatcher->{$method}(
"eloquent.{$event}: ".static::class, $this
);
}

Traits; parent & self type-hints in PHP 5.4

Well, I've confirmed it is in fact as I had hoped for and expected:

class MyClass
{

use MyTrait;

}

$myObject = new MyClass();

$myObject->myMethod($myObject); // ok

$myObject->myMethod('foobar'); // Catchable fatal error, argument must be instance etc

So, good news for all then.

Best way to do multiple constructors in PHP

I'd probably do something like this:

<?php

class Student
{
public function __construct() {
// allocate your stuff
}

public static function withID( $id ) {
$instance = new self();
$instance->loadByID( $id );
return $instance;
}

public static function withRow( array $row ) {
$instance = new self();
$instance->fill( $row );
return $instance;
}

protected function loadByID( $id ) {
// do query
$row = my_awesome_db_access_stuff( $id );
$this->fill( $row );
}

protected function fill( array $row ) {
// fill all properties from array
}
}

?>

Then if i want a Student where i know the ID:

$student = Student::withID( $id );

Or if i have an array of the db row:

$student = Student::withRow( $row );

Technically you're not building multiple constructors, just static helper methods, but you get to avoid a lot of spaghetti code in the constructor this way.



Related Topics



Leave a reply



Submit