5

In PHP 5.4.9, the following example triggers the fatal error "B has colliding constructor definitions coming from traits".

trait T {
    public function __construct () {
        echo __CLASS__ . ": constructor called.\n";
    }
}

class A {
    use T;
}

class B extends A {
    use T;
}

There's no problem when the trait contains a different method than the constructor, and no problem when the constructor is actually copied into the classes (without using traits, the "language-assisted copy & paste" feature).

What's so special about the constructor here? Shouldn't PHP be able to figure out that one of them overrides the other? I couldn't find anything about this limitation in the manual.

This related question mentions a way to get around the problem (by using aliases for trait methods), but not what's causing it in the first place.

Community
  • 1
  • 1
Zilk
  • 8,917
  • 7
  • 36
  • 44

1 Answers1

3

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.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • I get a different error message in this case: *"Trait method __construct has not been applied, because there are collisions with other trait methods on A"*. Also, it makes no difference if the imported method is a constructor or not. The example in the question works fine with other methods, only the constructor behaves differently. – Zilk Aug 27 '13 at 23:09
  • @hakre But doesn't explain, why a different method appears to work just fine. Does it? – SteAp Aug 27 '13 at 23:09
  • @Zilk: The error is differently named because the *extends* step is missing, but both errors are telling you about the collision. For *other* methods, try with other *magic* methods as well. I can imagine that there will be some difference for those because you can not alias them. They are dependent on their name which can not change, so no aliasing, so no conflict resoution, so just a collision. – hakre Aug 27 '13 at 23:12
  • @hakre: One of my most-used traits is called Frozen; it only has a magic setter (`__set` method) that always throws an exception. This is to prevent the accidental creation of undeclared properties. It works fine even in a derived class when the base class uses the same trait. – Zilk Aug 27 '13 at 23:18
  • 2
    Alright, this *is* constructor specifically. http://lxr.php.net/xref/PHP_5_4/Zend/zend_compile.c#3650 / http://lxr.php.net/xref/PHP_5_4/Zend/zend_compile.c#3675 - And this *is* magic methods related, it is just that constructor is a sepcial of those magic methods. This is probably for safety so that PHP's runtime doesn't break (not safety for your code). – hakre Aug 27 '13 at 23:23
  • @hakre: Well, I can't argue with the source :) I wish they had added a comment explaining *why* this shouldn't be possible, but I guess that's as close to an answer as I'm going to get. Thanks! – Zilk Aug 27 '13 at 23:33
  • It's a bit late, but you could do some blame in the repro to find out (the history is only via www so you don't need to checkout the whole history your own). Comments are with the commits, e.g. https://github.com/php/php-src/commit/e14354af21c9188582ef454696163cf68c7677ce this one was via blame: https://github.com/php/php-src/blame/PHP-5.4/Zend/zend_compile.c - happy digging, it's a bit late for me :) And I bet if you dig, you find some bugs. – hakre Aug 27 '13 at 23:41
  • Good idea. Looks like the current behavior was the result of this bug fix - https://bugs.php.net/bug.php?id=55554 - which is mostly concerned with conflicting legacy constructors. I think it's possible that the problem I'm having was an unintended consequence of this fix. I'll file an issue on it tomorrow, just in case. – Zilk Aug 27 '13 at 23:58