What happens if I use a class in two different traits, and both have a method with the same name but different implementations of this method?
Short answer
(c) AbraCadaver
Long answer
Say you have a class Foo that uses traits A and B:
class Foo {
use A, B;
}
Where both traits have a method with a similar name, but different implementation (the implementation doesn't matter, really):
trait A {
public function bar() {
return true;
}
}
trait B {
public function bar() {
return false;
}
Traits work by extending the class horizontally. Simply put - just adding any new contents to the class. And all works fine till there's any doubling in trait methods and properties. Then you have yourself a fatal error if this conflict is not explicitly resolved.
The sweet part is, you can resolve this conflict by specifying which method from which trait to use:
class Foo {
use A, B {
B::bar insteadof A;
}
}
You can also save the other method from oblivion by using alias for it:
class Foo {
use A, B {
B::bar insteadof A;
A::bar as barOfA;
}
}
The manual has traits farely well documented, go check it out.
Related
Since I've just jumped from another language to PHP, I would like to ask you what would be a correct way of adding an extension to a class. My config right now is as follows:
class A extends B {
use C;
public function a(){
}
}
I need to add some additional functions to class A, but logically divide them from it, so I though I would be using a trait C.
So my question is - can I use function a() in my trait C? I know I can define abstract function a() in trait C, however I believe that wouldn't be a good practice at all. Maybe I can somehow inject this into a trait or is it a bad practice as well?
It is possible, the choice is a discretion of the developer, the best solution is based on experience.
trait C {
public function hello() {
parent::a();
echo 'World!';
}
}
class B {
function a() {
echo "hello";
}
}
class A extends B{
use C;
}
(new A())->hello(); // helloWorld!
Let's say:
Class B {
public function a () {
//Does something
}
}
Class A extends B {
//We got access to B's public function. If you want to execute B's a and add some more content, then
public function a() {
parent::a();
//Some more content
}
}
Traits were developed due to PHP's nature of not allowing to inherit from multiple classes (Single inheritance). By creating a trait and apply it to a class, you know inherit it's methods
http://php.net/manual/en/language.oop5.traits.php
It really comes down to your needs.
Q: Do you want to create an interface and make other classes implement certain methods?
A: Create an interface
Q: Do you want to create an abstract class with some implementations and allow other classes to use them? A: Create an abstract class
Q: Do you want a class to inherit from two other classes and they've got different functionalities? A: Create a trait
The best approach is to use the tools at your disposal in order to output your desired result and keeping it organized
Is there any chance there is an equivalent of Objective C Categories in PHP?
If all you want to do is break a huge class definition up over multiple files, and it's your class, then you can do that with traits. Just define some of the methods in a trait in a different file and use it in your class:
trait FooBar_ExtraMethods {
function foo () { return 'qux'; }
}
class FooBar {
use FooBar__ExtraMethods;
function bar () { return 'baz'; }
}
But if you want to add methods to somebody else's class, then there's simply no way to do that with vanilla PHP. Your choices are:
Live with the fact that this isn't possible and just write a function that takes instances of the class instead of extending it.
Use an extension to add the functionality into the language. Right now, the only contender is Dmitry Zenovich and Sara Golemon's Runkit. Zenovich's fork currently seems to be better-maintained and generally superior to Golemon's (although Golemon's is the one hosted on PECL), and the instructions below use Zenovich's fork.
Suppose I have an existing user-defined class Foo...
class Foo {
function methodA($arg) { return 2*$arg; }
}
I can add a method to it like this:
runkit_method_add('Foo', 'methodC', function ($arg) {
return 5 * $this->methodA($arg);
});
and call it like an ordinary method:
$f = new Foo;
echo $f->methodC(2); // 20
A couple of caveats:
You can't add methods to built-in classes. So if you want to use this to extend library-defined classes, you're good, but if you want to extend built-in classes like DateTime, you're out of luck.
This isn't remotely idiomatic - indeed, Runkit's tagline is "For all those things you.... probably shouldn't have been doing anyway.....". I leave it to your judgement whether to let that deter you from doing it.
It looks like a trait.
trait CarMaintenance{
public function needsOilChange(){}
public function changeOil(){}
public function rotateTires(){}
public function jumpBatteryUsingCar(){}
}
class Car {
use CarMaintenance;
public function startEngine() {}
public function drive() {}
public function turnLeft() {}
public function turnRight() {}
}
But traits could be reused in other class and could have their own hierarchy tree.
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.
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.
I'm trying to understand class inheritance.
Suppose I have the following code (PHP):
class A {
public function fire() {
echo "Fire!";
}
}
class B extends A {
public function water() {
echo "Water!";
}
}
class C extends B {
public function lightning() {
echo "Lightning!";
}
}
$cObject = new C();
$cObject->fire();
My questions are
Even though the fire() method is not defined in class C nor class B, $cObject->fire() works. Does class C inherit not only class B's methods, but class A's methods? I'm trying to find out how many levels deep inheritance will go.
Is there a term for calling a property or method that does not exist in the current object instance, but this property or method exists in a parent or ancestor class? EDIT: In other words, fire() is not defined in class C, but $cObject can still call fire(). Is there any particular term/jargon for this concept? (Or is it just part of the definition of "class inheritance")
I'm trying to find out how many levels deep inheritance will go.
infinetely, if you want. no limits
Is there a term for calling a property or method that does not exist in the current object instance, but this property or method exists in a parent or ancestor class?
For class variables: no.
For class methods: yes. declare them as private:
class A {
private function test() {
echo 'test';
}
}
class B extends A {
public function __construct() {
$this->test();
}
}
$b = new B();
Output:
Fatal error: Call to private method A::test() from context 'B' in /home/thorsten/a.php on line 14
1) Class 'B' is an 'A'. Class 'C' is a 'B', therefor, 'C' is an 'A'. Everything that 'B' has rights and privileges to in 'A', 'C' has as well. C++ has a concept of private inheritance that gets a little funky here, but that's the general idea. As said in the other answer, inheritance will go as far as the chain is defined.
2) I think you meant to ask "What is a function called that is not defined in derived class, but instead in the base class. This type of function is called a virtual function. From what I understand, every class function in PHP is a virtual function and can be overridden (reimplemented in a derived class) unless it is declared as final.
Say we've got the following two classes:
abstract class Foo {
public abstract function run(TypeA $object);
}
class Bar extends Foo {
public function run(TypeB $object) {
// Some code here
}
}
The class TypeB extends the class TypeA.
Trying to use this yields the following error message:
Declaration of Bar::run() must be compatible with that of Foo::run()
Is PHP really this broken when it comes to parameter types, or am I just missing the point here?
This answer is outdated since PHP 7.4 (partially since 7.2).
The behavior you describe is called covariance and is simply not supported in PHP. I don't know the internals but I might suspect that PHP's core does not evaluate the inheritance tree at all when applying the so called "type hint" checks.
By the way, PHP also doesn't support contravariance on those type-hints (a feature commonly support in other OOP languages) - most likely to the reason is suspected above. So this doesn't work either:
abstract class Foo {
public abstract function run(TypeB $object);
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
And finally some more info: http://www.php.net/~derick/meeting-notes.html#implement-inheritance-rules-for-type-hints
This seems pretty consistent with most OO principals. PHP isn't like .Net - it doesn't allow you to override class members. Any extension of Foo should slide into where Foo was previously being used, which means you can't loosen constraints.
The simple solution is obviously to remove the type constraint, but if Bar::run() needs a different argument type, then it's really a different function and should ideally have a different name.
If TypeA and TypeB have anything in common, move the common elements to a base class and use that as your argument constraint.
I think this is by design: It is the point of abstract definitions to define the underlying behaviour of its methods.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility.
One could always add the constraint in code:
public function run(TypeA $object) {
assert( is_a($object, "TypeB") );
You'll have to remember or document the specific type limitation then. The advantage is that it becomes purely a development tool, as asserts are typically turned off on production servers. (And really this is among the class of bugs to be found while developing, not randomly disrupt production.)
The code shown in the question is not going to compile in PHP. If it did class Bar would be failing to honour the gurantee made by it's parent Foo of being able to accept any instance of TypeA, and breaching the Liskov Substitution Principle.
Currently the opposite code won't compile either, but in the PHP 7.4 release, expected on November 28 2019, the similar code below will be valid, using the new contravariant arguments feature:
abstract class Foo {
public abstract function run(TypeB $object); // TypeB extends TypeA
}
class Bar extends Foo {
public function run(TypeA $object) {
// Some code here
}
}
All Bars are Foos, but not all Foos are Bars. All TypeBs are TypeAs but not all TypeAs are TypeBs. Any Foo will be able to accept any TypeB. Those Foos that are also Bars will also be able to accept the non-TypeB TypeAs.
PHP will also support covariant return types, which work in the opposite way.
Although you cannot use whole class-hierarchies as type-hinting. You can use the self and parent keywords to enforce something similar in certain situations.
quoting r dot wilczek at web-appz dot de from the PHP-manual comments:
<?php
interface Foo
{
public function baz(self $object);
}
class Bar implements Foo
{
public function baz(self $object)
{
//
}
}
?>
What has not been mentioned by now is that you can use 'parent' as a typehint too. Example with an interface:
<?php
interface Foo
{
public function baz(parent $object);
}
class Baz {}
class Bar extends Baz implements Foo
{
public function baz(parent $object)
{
//
}
}
?>
Bar::baz() will now accept any instance of Baz.
If Bar is not a heir of any class (no 'extends') PHP will raise a fatal error:
'Cannot access parent:: when current class scope has no parent'.