amflare
amflare

Reputation: 4113

Extending a trait conflicts with itself

I'm trying to extend a trait. By that I mean create a trait that redefines a couple methods for a different trait.

This is my code (PHP 5.6 and Laravel 5.4):

namespace App\Traits;

use Illuminate\Database\Eloquent\Concerns\HasTimestamps;

trait ExtendHasTimestamps
{
    use HasTimestamps {
        HasTimestamps::setCreatedAt as parentSetCreatedAt;
        HasTimestamps::setUpdatedAt as parentSetUpdatedAt;
    }

    public function setCreatedAt($value) {
        if ($value !== '') return $this;
        return $this->parentSetCreatedAt($value);
    }

    public function setUpdatedAt($value) {
        if ($value !== '') return $this;
        return $this->parentSetUpdatedAt($value);
    }
}

The issue comes in when I use ExtendHasTimestamps in a Model, it conflicts with HasTimestamps because Eloquent\Model has use HasTimestamps. Before PHP 7, traits throw a fatal error if you try to define the same property twice. So since I'm defining $timestamps both in HasTimestamps and again through ExtendHasTimestamps by virtue of use HasTimestamps, it breaks.

In short:

trait HasTimestamps {
    protected $timestamps = true; //problematic property
}

trait ExtendHasTimestamps {
    use HasTimestamps;
    // add validation then call HasTimestamps method
}

class Model {
    use HasTimestamps;
}

class MyModel extends Model {
    use ExtendHasTimestamps; //everything breaks
}

Is there a way to either convince PHP that it is really the same thing and it can stop conflicting with itself, or to inform the class I'm working with (an extension of Eloquent\Model) to stop using HasTimestamps in favor of my trait?


This question does not answer mine because I'm trying to use a trait to overwrite another trait rather than just adding methods to each class where I need to use this code.

Upvotes: 2

Views: 1463

Answers (1)

Jeff Puckett
Jeff Puckett

Reputation: 40861

It seems to me that you don't really need to extend the trait in the first place. I would simply override the methods in a trait used in your child class, then call the parent methods within the trait.

Demo: https://3v4l.org/2DqZQ

<?php

trait HasTimestamps
{
    protected $timestamps = true;

    public function setCreatedAt($value)
    {
        echo 'HasTimestamps::setCreatedAt'.PHP_EOL;
        return $this;
    }

    public function setUpdatedAt($value)
    {
        echo 'HasTimestamps::setUpdatedAt'.PHP_EOL;
        return $this;
    }
}

trait ExtendHasTimestamps
{
    public function setCreatedAt($value)
    {
        echo 'ExtendHasTimestamps::setCreatedAt'.PHP_EOL;

        if ($value !== '') return $this;

        return parent::setCreatedAt($value);
    }

    public function setUpdatedAt($value)
    {
        echo 'ExtendHasTimestamps::setUpdatedAt'.PHP_EOL;

        if ($value !== '') return $this;

        return parent::setUpdatedAt($value);
    }
}

class Model
{
    use HasTimestamps;
}

class MyModel extends Model
{
    use ExtendHasTimestamps;
}

(new MyModel)->setCreatedAt('');

echo PHP_EOL;

(new MyModel)->setCreatedAt('time');

ExtendHasTimestamps::setCreatedAt
HasTimestamps::setCreatedAt

ExtendHasTimestamps::setCreatedAt

Upvotes: 1

Related Questions