2

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.

amflare
  • 4,020
  • 3
  • 25
  • 44
  • What you're doing in the `ExtendHasTimestamps` trait seems like a fix, any chance you might override or replace the original trait instead of providing your own ? – Calimero Sep 12 '17 at 17:47
  • Unfortunately they are Laravel vendor files, which means that if I edit either `Eloquent\Model` or `HasTimestamps` it will get undone next time I run `composer update` or the like. – amflare Sep 12 '17 at 17:50
  • 1
    How about forgetting the trait and extending the base model class to implement your fixes ? (and make your model classes extend that new base class instead of Laravel's) – Calimero Sep 12 '17 at 18:30
  • Yeah. That would work. I'll probably end up doing that. Thanks. – amflare Sep 12 '17 at 18:47
  • Sorry mate, I would have provided a full-fledged answer but I have no Laravel setup here to test it properly. Here is the link I found https://laracasts.com/discuss/channels/eloquent/how-to-override-base-model-some-methods-in-laravel-51 – Calimero Sep 12 '17 at 18:49

1 Answers1

1

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

Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
  • Unless I'm missing something, this doesn't allow me to call the original HT method from the EHT method. Short of just copying the code wholesale. – amflare Sep 12 '17 at 21:18
  • I'm unsure what the `bar` method is for. But I need `EHT::setCreatedAt` to be able to call `HT::setCreatedAt` so that after it runs my code, it still does what Laravel thinks it is supposed to be doing – amflare Sep 14 '17 at 13:51
  • @amflare ah ok that makes sense, sorry I misunderstood. yeah, all you have to do is call `parent` within your trait's method. I've updated my answer and demo to demonstrate. – Jeff Puckett Sep 14 '17 at 15:13
  • That is deceptively simple. Thanks. – amflare Sep 14 '17 at 15:46