I know I cannot overload methods in PHP. And, as far as I know, private methods in a class are invisible to classes that extend the base class. So why this does not work?
class Base {
private function foo($arg) {
print "Base $arg";
}
}
class Child extends Base {
public function foo() {
print "Child";
}
}
$c = new Child;
print $c->foo();
The error:
PHP Strict Standards: Declaration of Child::foo() should be compatible with Base::foo($arg) in /var/www/boludo.php on line 17
I assumed that foo($arg) method is invisible in Child class because is private. So, I'm not overloading foo, I'm just creating a method called foo.
To fix the Notice, simply change foo() in the Child to
public function foo($arg = null) {
As to the question "why does this not work":
Visibility in PHP is strictly about runtime access. It doesn't affect how you can extend/compose/overload classes and methods. Loosening the visibility of a private method from a Supertype in a Subtype will add a separate method in the subtype with no access to the same named method in the supertype. However, PHP will assume a parent-child relationship for these. That didn't cause the Notice though. At least, not on it's own.
The reason why you get the Notice, is that you are then also trying to change the method signature. Your foo() does no longer require $arg to be passed to it. When you assume a parent-child relationship between the methods, this is a problem because the Liskov Substitution Principle states that "if S is a subtype of T, then objects of type T may be replaced with objects of type S" without breaking the program. In other words: if you have code that uses Base, you should be able to replace Base with Child and the program should still work as if it was using Base.
Assume your Base also has a public method bar().
class SomeClientUsingBase
{
public function doSomethingWithBase(Base $base)
{
$result = $base->bar();
// …
Now imagine Child changes bar() to require an argument. If you then pass Child for Base into the client, you will break the client, because the client calls $base->bar(); without an argument.
Obviously, you could change the client to pass an argument, but then the code really depends on how Child defined the method, so the Typehint is wrong. In fact, Child is not a Base then, because it doesn't behave like a Base. It's broken inheritance then.
Now the funny thing is, if you remove that $arg from foo(), you are technically not violating LSP, because the client would still work. The Notice is wrong here. Calling $base->foo(42) in a client that previously used Base will still work with a Child because the Child can simply ignore the argument. But PHP wants you to make the argument optional then.
Note that LSP also applies to what a method may return. PHP just doesn't include the return type in the signature, so you have take that into account yourself. Your methods have to return what the Supertype returned or something that is behaviorally equivalent.
You can do function overloading in PHP using __call function: http://www.php.net/manual/en/language.oop5.overloading.php#object.call
Apart from that, your problem is that in that way, you violate the Substitutability principle:
http://en.wikipedia.org/wiki/Liskov_substitution_principle
Something that PHP uses. In that way, if you replace an object of Base class type to one with a Child class type, Substitutability is violated. You are changing the interface of the base class in the derived one, removing the argument of method foo(...) and in this way, objects of Base class type can not be replaced with objects of Child class type without breaking the program, thus violating Liskov's Substitutability Principle (LSP).
Related
I am trying to understand inheritance in php. I have two classes ShopProduct and CdProduct. ShopProduct class have one method getName. CdProduct class inherit (extends) ShopProduct class and have method DoWork. In index.php file I create a one CdProduct object and pass it to the method that takes one parameter like this function Work(ShopProduct $item){...}. Now, I can understand that I can call getName method using $item parameter, and I know that I can call DoWork method using $item, but I can't understand how this is possible... I hope I was clear... :-)
It's possible because the object passed as parameter happens to contain the DoWork method. It's risky calling this sort of methods (belonging to the inheriting class) without type-checking.
Why does it let you give a CdProduct parameter when the mentioned one is ShopProduct? Well any class derived from the ShopProduct will contain all its attributes and methods, thus not affecting functionality in the method body.
What can be achieved through this is having a Work method that can receive as a parameter any type derived from ShopProduct(CdProduct, BookProduct, CandyProduct) without needing a separate function for each type [WorkCd(CdProduct $item) or WorkBook(BookProduct $item)] that actually does the same thing.
If you really need access to derived-type specific methods and attributes you can do something like a switch on object class name:
switch(get_class($item))
{
case "CdProduct":
//CdProduct object specific code here
break;
case "BookProduct":
//BookProduct object specific here
break;
//And so and so forth...
}
The reason why it works is because PHP 5 introduces type hinting: http://php.net/manual/en/language.oop5.typehinting.php .
So indeed CdProduct inherits all the public and protected methods and attributed declared in ShopProduct and thus you can call the getName() method. When you pass the CdProduct instance to the Work function which declares the first parameter to be ShopProduct (and not CdProduct) you are forcing the parameter to be casted to ShopProduct.
So if you pass as parameter something different from CdProduct instance, like a string as an example, you have an error, but because of the inheritance this doesn't fail (it should however print a notice).
As you can read from the comments in the link posted above:
p.s. do note: when used in inherited classes a STRICT-notice comes up, because the function definition of the inherited class doesn't match the parent's definition (different type hinting) - but it works great!
I have an abstract class I am inheriting from:
abstract class Test
{
public function GetTests()
{
}
}
and I have a concrete that I have been using the abstract classes implementation for most of the time:
class Concrete extends Test
{
// No problemmos
}
I recently had to implement a different version of the GetTests method, and in fact I wanted to overwrite it as it's built into all of my routing:
class Concrete extends Test
{
public function GetTests( $newArgument )
{
// notice $newArgument
}
}
However I get this error message:
Declaration of Concrete::GetTests() should be compatible with Test::GetTests()
Apart from copying the entirety of the functions from the abstract class for this concrete, even though I only need to implement this one method differently... Is there a way of getting around this?
I do understand that I could have:
abstract class Test
{
abstract public function GetTests();
}
But this is why I am snookered, because I no longer have the ability to modify how the underlying Test class is implemented... doh!... Unless I really have to..
Thanks to all great answers...
I have decided to de snooker myself (it's going to hurt but it's going to be worth it) and I will instantiate the Test class inside the Concrete class, implement concrete versions of all the Test class methods, and then inside them just call the instantiated Test class... This means in the future (or indeed now) I can simply not call that feature...
For context:
/* no longer abstract */ class UnitOfWorkController
{
public function GetUnits()
{
// Implementation
return View::make(...);
}
}
and...
class SomethingController /* no longer extends the UnitOfWorkController */
{
private $unitOfWorkController;
public function __Construct()
{
$this->unitOfWorkController = new UnitOfWorkController();
}
public function GetUnits()
{
return $this->unitOfWorkController->GetUnits();
// or I could just implement my own junk
}
}
Your concrete subclass is in violation of the Liskov Substitution Principle, which to cut a long story short says that if an object of class X can be processed by a given piece of code, then every possible subclass of X must also be able to be processed by the same piece of code.
Say I wanted to make another subclass of Test and wanted to implement my own GetTests method. The base class method doesn't accept any arguments at all, so that suggests that, if my subclass is to be substitutable for its superclass, my implementation of that method cannot take any arguments either. If I give my implementation arguments, then it no longer conforms to the specification as laid down by the superclass.
If I have code that does:
$object = new Test;
$test -> GetTests ();
then I can't substitute my subclass of Test without also changing the calling code to pass in an argument. Likewise if I do change it, then I have another subclass of Test that doesn't require an argument for GetTests then the code would have to change again. In fact the same code simply can't be used as is with both subclasses without having to jump through some hoops to determine the actual class and using the appropriate calling convention which means needing to know things about the class I'm about to use that I shouldn't need to know.
PHP is less strict than most OO languages about subclass method signitures matching their superclass, but it will issue a warning if they don't match. The only way to fix the warning is to have all subclasses have the same method signatures as the superclass they inherit from.
Child methods must have the same signature as the same method in a parent class. This includes required parameters and their typecasting.
For example, a child class of the following method must also have one parameter, and the parameter must cast to the ArgumentType class or a child of thereof.
public function something(ArgumentType $Argument)
{
}
You can, however, make the parameter optional by setting it to null or any other value:
public function something(ArgumentType $Argument = null)
{
}
In this case, child methods may omit this parameter.
From the PHP docs, see http://php.net/manual/en/language.oop5.abstract.php:
[…] Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. For example, if the child class defines an optional argument, where the abstract method's signature does not, there is no conflict in the signature.
The method signature of Concrete::GetTests() has a variable while Test::GetTests() does not. Since you have already defined this method within Test, it is now being inherited. The inherited version is not compatible with your overridden version.
Here are your options:
Add $newArgument to the parameters list in Test::GetTests().
Remove $newArgument from the parameters list in Concrete::GetTests().
Rename Concrete::GetTests() to something else.
PHP does not support this, as the error message says. If you want to override the function, it has to have the same footprint, which in your case it doesn't
What you could do is use a magic method: http://php.net/manual/en/language.oop5.overloading.php#object.call
the parameter array is a separate entity, so you 'decide' in your code (which you can override) what to do with which parameter.
I wanted to link a blog I read about this, but couldn't find the one I was thinking of. There is this rather strangely formatted one, not sure if it any good, but it does touch on some of the issues.
You could obviously add the argument to the parent, but this is 'leaking' upwards. If other childeren want even more, you'd get a big party of random paramteres that all can be nulled.
I want to clarify a problem i am having
I have a base DataBase class that will be inherited by a bunch of other classes.The constructor looks like this:
public function __construct ($table)
{
$this->table = $table;
$this->db = new Database();
$this->db->connect();
}
I will call from this constructor from children as following:
public function __construct ($something)
{
parent::__construct("planets_games");
}
My problem is that php doesn't allow me to make the child's constructor without the $something parameter
i get the following:
Fatal error: Declaration of planetsGames::__construct() must be compatible with that of IScaffold::__construct()
I am currently bypassing this by instantiating an object like this:
$pg = new planetsGames('uselessStringHereThatHasNoUtilityAtAll');
I think i am missing something very important in my basic php knowledge
Thank you very much for the help in advance
This error message refers to the liskov substitution principle.
It applies to every IS-A relationship (which is the meaning of using inheritance (extends)) and states that every subtype should be fully replacable for the super type.
But this doesn´t apply to constructors! Which php version you are using?
It seems the base class has marked the constructor as abstract. That´s the only way
this error can appear.
You should never mark constructors abstract, final or put them in interfaces!
In most languages this isn´t even possible.
What you should take away from this is that the best-practice is that
each concrete object has a constructor with a signature that best
represents how a consumer should fully instantiate that particular
object. In some cases where inheritance is involved, “borrowing” the
parents constructor is acceptable and useful. Furthermore, it is
encouraged that when you subclass a particular type, that your new
type should, when appropriate, have its own constructor that makes the
most sense to the new subtype.
http://ralphschindler.com/2012/03/09/php-constructor-best-practices-and-the-prototype-pattern
A few years late to the party....
The problem is your constructor is needing a value. You can prevent the fatal error by setting a default value such as an empty string.
public function __construct($something = "")
{
parent::__construct("planets_games");
}
Then just instantiate the class like normal
$pg = new planetsGames();
At first I was confused why both of the method calls in the constructor work, but now I think I understand. The extending classes inherit the parent's methods as if they were declared in the class itself, AND the methods exist in the parent, so both should work.
Now I'm wondering if there is a preferred way (i.e. best practice) of calling the method (via parent or this), and whether or not these are truly identical ways of executing the same code, or if there are any caveats when using one over the other.
Sorry, I'm probably over thinking this.
abstract class Animal {
function get_species() {
echo "test";
}
}
class Dog extends Animal {
function __construct(){
$this->get_species();
parent::get_species();
}
}
$spike = new Dog;
There are three scenarios (that I can think of) where you would call a method in a subclass where the method exists in the parent class:
Method is not overwritten by subclass, only exists in parent.
This is the same as your example, and generally it's better to use $this->get_species(); You are right that in this case the two are effectively the same, but the method has been inherited by the subclass, so there is no reason to differentiate. By using $this you stay consistent between inherited methods and locally declared methods.
Method is overwritten by the subclass and has totally unique logic from the parent.
In this case, you would obviously want to use $this->get_species(); because you don't want the parent's version of the method executed. Again, by consistently using $this, you don't need to worry about the distinction between this case and the first.
Method extends parent class, adding on to what the parent method achieves.
In this case, you still want to use $this->get_species(); when calling the method from other methods of the subclass. The one place you will call the parent method would be from the method that is overwriting the parent method. Example:
abstract class Animal {
function get_species() {
echo "I am an animal.";
}
}
class Dog extends Animal {
function __construct(){
$this->get_species();
}
function get_species(){
parent::get_species();
echo "More specifically, I am a dog.";
}
}
The only scenario I can imagine where you would need to call the parent method directly outside of the overriding method would be if they did two different things and you knew you needed the parent's version of the method, not the local. This shouldn't be the case, but if it did present itself, the clean way to approach this would be to create a new method with a name like get_parentSpecies() where all it does is call the parent method:
function get_parentSpecies(){
parent::get_species();
}
Again, this keeps everything nice and consistent, allowing for changes/modifications to the local method rather than relying on the parent method.
Unless I am misunderstanding the question, I would almost always use $this->get_species because the subclass (in this case dog) could overwrite that method since it does extend it. If the class dog doesn't redefine the method then both ways are functionally equivalent but if at some point in the future you decide you want the get_species method in dog should print "dog" then you would have to go back through all the code and change it.
When you use $this it is actually part of the object which you created and so will always be the most up-to-date as well (if the property being used has changed somehow in the lifetime of the object) whereas using the parent class is calling the static class method.
I always thought I understood how OOP works (and I have been using it for years), but sometimes I realize some concepts are still not so clear to me.
I just came across this question about method visibility in PHP. The accepted answer explains that a private method cannot be overridden by a child class in PHP. Okay, that makes sense. However, the example made me think about the internal inheritance mechanism in PHP, and the way $this behaves on inherited methods.
Consider this code (example from the PHP Manual, also included in the question mentioned above):
class Bar
{
public function test() {
$this->testPrivate();
$this->testPublic();
}
public function testPublic() {
echo "Bar::testPublic\n";
}
private function testPrivate() {
echo "Bar::testPrivate\n";
}
}
class Foo extends Bar
{
public function testPublic() {
echo "Foo::testPublic\n";
}
private function testPrivate() {
echo "Foo::testPrivate\n";
}
}
$myFoo = new foo();
$myFoo->test();
/*
Output:
Bar::testPrivate
Foo::testPublic
*/
Now consider this excerpt from the PHP Manual:
The pseudo-variable $this is available when a method is called from within an object context. $this is a reference to the calling object (usually the object to which the method belongs, but possibly another object, if the method is called statically from the context of a secondary object).
The explanation states that "$this is a reference to the calling object", which is $myFoo. So I expected that $myFoo->test() would always invoke Foo::testPrivate, and never Bar::testPrivate (unless $myFoo were an instance of Bar). I tested $this with get_class, and it always returns Foo, even from inside Bar::testPrivate and Bar::test. However, $this behaves like an instance of Bar when Bar::test calls $this->testPrivate().
That's really confusing, and I am trying to understand why it works that way!
I thought inherited methods (public or protected) were somehow copied from the base to the child class. Private methods would not be copied at all. But this example indicates that it doesn't work like this. It looks like the instance of Foo keeps an internal instance of Bar, and delegates method calls to it when necessary.
I am trying to learn something here, and I only learn when things make sense to me. This one does not. After writing all this, I think I can summarize it with two questions:
Could someone briefly explain how inheritance works internally in PHP? Or at least point me to an article or documentation about that?
Is the behavior or $this discussed here present on other OO languages as well, or is it particular to PHP?
Inheritance in PHP works the same way it does in most object-oriented languages.
When you have a "virtual" method, the method is not bound directly to the caller. Instead, every class contains a little lookup table which says "this method name is bound to that implementation". So, when you say $this->testPublic(), what actually happens is that PHP:
Gets the virtual table for the current class
Looks up the virtual table entry for testPublic in that table
Invokes the method to which that lookup points
Since Foo overrides testPublic, its virtual table contains an entry for testPublic pointing to Foo::testPublic.
Now, with the private methods, the behavior is different. Since, as you correctly read, private methods cannot be overridden, calling a private method never results in a virtual table lookup. That is to say, private methods cannot be virtual and must always be defined in the class which uses them.
So, the effect is that the name is bound at the time of declaration: all Foo methods will call Foo::testPrivate when they say $this->testPrivate, and all Bar methods will call Bar::testPrivate.
To sum up, saying that "inherited methods are copied to the child" is not correct. What actually happens is that the child begins with its method-name-lookup-table being populated with its parent class' entries, and then adds its own functions and replaces any overridden entries. When you call $this->something, this lookup table is consulted for the current object's class. So if $this is an instance of Foo, and Foo overrides testPublic, you get Foo::testPublic. If $this is an instance of Bar, you will get Bar::testPublic.
Well, private methods and properties are exactly that - private. For all intents and purposes, you can consider them "internal", meaning internal to the class they're defined in. This means that they're never inherited, and can never be overridden.
Thus, when using $this in combination with a private method or property, it will always be the method or property within the same class as the reference to $this. This happens because $this called within a parent class cannot access private methods or properties in another class (because they're private), even from child classes.
Hope this helps.