What is ReflectionClass::IS_IMPLICIT_ABSTRACT used for? - php

I'm trying to understand the PHP ReflectionClass but have some trouble. In the documentation 3 modifiers for classes are listed: implicit abstract, explicit abstract and final with constant values 16, 32 and 64 respectively. What I wonder is how the implicit and explicit abstract modifiers work? When is a PHP class implicit abstract?
I tried to figure it out by trial and error with a few simple classes and interfaces and using the ReflectionClass::getModifiers() method to see what happened, however I do not understand the result of the getModifiers method. Using the following interface the result from getModifiers is int(144):
interface Animal {
public function isYellow();
}
$a = new ReflectionClass("Animal");
var_dump($a->getModifiers());
With the modifier constants being 16, 32 and 64 respectively I don't get how the method can return 144 at all? Have I misunderstood something about how a bitfield works or why am I getting this result?
UPDATE: 144 as bits are 10010000, so the set bits are 16 and 128. Making interfaces implicit abstract. I would like to know what 128 means though?
I made some testing with classes:
class Horse implements Animal {
public function hasTail() { return true; }
}
abstract class Predator implements Animal {
public function hasTeeth() { return true; }
}
abstract class Fish implements Animal {
public function isYellow() { return true; }
}
The Horse class throws a fatal error but still defines the class. Reflecting Horse and calling getModifiers results in int(524304), which has the implicit abstract bit set. But what does the other set bit mean?
Reflecting the Predator class and calling getModifiers results in int(524336), which has both the implicit and explicit abstract bits set. The last set bit is the same as for the Horse class.
The Fish class is declared abstract but has no abstract methods. The getModifiers result is then int(524320) which as expected has the explicit abstract bit set but not the implicit abstract bit.

There may be additional modifiers used internally that simply don't have a publicly defined constant value, hence the discrepancy in value:
Using
if ($a->getModifiers() & ReflectionClass::IS_IMPLICIT_ABSTRACT)
echo 'IS_IMPLICIT_ABSTRACT',PHP_EOL;
if ($a->getModifiers() & ReflectionClass::IS_EXPLICIT_ABSTRACT)
echo 'IS_EXPLICIT_ABSTRACT',PHP_EOL;
if ($a->getModifiers() & ReflectionClass::IS_FINAL)
echo 'IS_FINAL',PHP_EOL;
will identify which of the defined modifiers apply
A look at the reflection source code reveals the following definitions:
REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_IMPLICIT_ABSTRACT", ZEND_ACC_IMPLICIT_ABSTRACT_CLASS);
REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_EXPLICIT_ABSTRACT", ZEND_ACC_EXPLICIT_ABSTRACT_CLASS);
REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_FINAL", ZEND_ACC_FINAL_CLASS);
so all flags are based on the internally defined ZEND_ACC_* constants, which have a whole range of additional values that may or may not be relevant to reflection but probably still apply to classes

The method getModifiers() is used to determine the class modifiers defined for a class (or interface or trait).
What it returns is an int, which represent different bits that are set.
For example if bit public has value 1 and bit static has value 2 then together they'd be 1 | 2 (bitwise OR), which is 3. And you'd get 3 back for a class that is public static. This can be used for both a class and its methods.

Related

Fatal error: Declaration of .. must be compatible with .. PHP (parameter required: extended class of specification)

I'm working on the next version of my language-constraint reconciliation system. I started with this (the abstract function declaration seems to be the problem):
namespace AAABIT;
abstract class LangPrefSet{
private $LangPrefs;
public function __construct(){$this->LangPrefs=array();}
public function add(LangPref $langpref){$this->LangPrefs[]=$langpref;}
public function langPrefs(){return $this->LangPrefs;}
abstract public function reconcile(LangPrefSet $other);//←Seems to be throwing an error… We don't strictly need this line, but this is a little concerning…
protected static function reconcile_LangPrefSets(LangPrefSet_ForUser $UserLangPrefSet,LangPrefSet_Resource $RsrcLangPrefSet,$maxOptions){//…
}
}
//Following classes are necessary because language similarity is a one-way mapping. Just because resources in Lang A (e.g. Russian) are likely to be readily understandable for speakers/readers of Lang B (e.g. Ukrainian), does not mean that the resources in Lang B (e.g. Ukrainian) are equally intelligible for speakers/readers of Lang A (e.g. Russian)!
class LangPrefSet_For_User extends LangPrefSet{public function reconcile(LangPrefSet_Resource $RsrcLangPrefSet){return self::reconcile_LangPrefSets(self,$RsrcLangPrefSet);}}
class LangPrefSet_Resource extends LangPrefSet{public function reconcile(LangPrefSet_For_User $UserLangPrefSet){return self::reconcile_LangPrefSets($UserLangPrefSet,self);}}
I thought this would work because LangPrefSet_Resource conforms to LangPrefSet; but PHP found this objectionable, throwing the aforementioned error. I thought I might have better luck with an Interface… So I did this:
interface LangPrefSet_Reconcilable{
public function reconcile(LangPrefSet_Reconcilable $other);
}
Then I made the two classes extending LangPrefSet, implements LangPrefSet_Reconcilable, and commented out the abstract function declaration (after first trying to make that require an LangPrefSet_Reconcilable interface-typed parameter, also which didn't work) — the results are:
Fatal error: Declaration of AAABIT\LangPrefSet_For_User::reconcile() must be compatible with AAABIT\LangPrefSet_Reconcilable::reconcile(AAABIT\LangPrefSet_Reconcilable $other)
— This is not a blocker issue for me, since I can just strip out the abstract function and interface and the system will work fine. However I'm concerned that I might not have understood interfaces/ abstract classes properly!
What is wrong with specifying that a class method overriding abstract function a(ObjB $b) or a similar interface specification, takes as a parameter ObjC $c, where ObjC extends ObjB?
Your parameter type must be the same in the interface(or abstract class) and where you are implementing the same. right now the interface is LangPrefSet_Reconcilable
and the implementation says LangPrefSet_Resource and LangPrefSet_For_User.
You can either use a 2nd interface like below, or use the same class.
interface LangPrefSetReconcilableDataInteface {
...
}
interface LangPrefSet_Reconcilable{
public function reconcile(LangPrefSetReconcilableDataInteface $other);
}
class LangPrefSet_Resource implements LangPrefSetReconcilableDataInteface {
...
}
class LangPrefSet_For_User implements LangPrefSetReconcilableDataInteface {
...
}
class LangPrefSet_For_User extends LangPrefSet
{
public function reconcile(LangPrefSetReconcilableDataInteface $RsrcLangPrefSet)
{
return self::reconcile_LangPrefSets(self,$RsrcLangPrefSet);
}
}
class LangPrefSet_Resource extends LangPrefSet
{
public function reconcile(LangPrefSetReconcilableDataInteface $UserLangPrefSet)
{
return self::reconcile_LangPrefSets($UserLangPrefSet,self);
}
}
The issue may to be that in the abstract class declaration, I am specifying that any LangPrefSet must be acceptable to the reconcile() object method:
abstract public function reconcile(LangPrefSet $other);
— Whereas in the actual method declaration, I am specifying in one case that only a LangPrefSet_Resource would be acceptable, and in the other case, that only a LangPrefSet_For_User would be acceptable.
The object method declarations are therefore incompatible with the abstract method declaration. It seems that the proper application of these interface/abstract method declaration techniques is not to broaden the range of permissible parameter type constraints in classes that implement the interface, since we may achieve this simply by declaring the input restrictions as they ought to be for those specific classes/methods; but rather, interface/abstract method declarations are meant to broaden the range of parameters that may actually be passed to a method that specifies these abstract parameter types.

Snookered by abstract - PHP

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.

"Overloading" a private method in PHP

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).

How does a class extension or Interface work?

Have come across this so many times and am not sure why so it got me curious. Some classes work before they are declared and others don't;
Example 1
$test = new TestClass(); // top of class
class TestClass {
function __construct() {
var_dump(__METHOD__);
}
}
Output
string 'TestClass::__construct' (length=22)
Example 2
When a class extends another class or implements any interface
$test = new TestClass(); // top of class
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return json_encode(rand(1, 10));
}
}
Output
Fatal error: Class 'TestClass' not found
Example 3
Let's try the same class above but change the position
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return json_encode(rand(1, 10));
}
}
$test = new TestClass(); // move this from top to bottom
Output
string 'TestClass::__construct' (length=22)
Example 4 ( I also tested with class_exists )
var_dump(class_exists("TestClass")); //true
class TestClass {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return null;
}
}
var_dump(class_exists("TestClass")); //true
as soon as it implements JsonSerializable ( Or any other)
var_dump(class_exists("TestClass")); //false
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return null;
}
}
var_dump(class_exists("TestClass")); //true
Also Checked Opcodes without JsonSerializable
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > SEND_VAL 'TestClass'
1 DO_FCALL 1 $0 'class_exists'
2 SEND_VAR_NO_REF 6 $0
3 DO_FCALL 1 'var_dump'
4 4 NOP
14 5 > RETURN 1
Also Checked Opcodes with JsonSerializable
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > SEND_VAL 'TestClass'
1 DO_FCALL 1 $0 'class_exists'
2 SEND_VAR_NO_REF 6 $0
3 DO_FCALL 1 'var_dump'
4 4 ZEND_DECLARE_CLASS $2 '%00testclass%2Fin%2FaDRGC0x7f563932f041', 'testclass'
5 ZEND_ADD_INTERFACE $2, 'JsonSerializable'
13 6 ZEND_VERIFY_ABSTRACT_CLASS $2
14 7 > RETURN 1
Question
I know Example 3 worked because the class was declared before its initiated but why would Example 1 work in the first place ?
How does this entire process of extending or interface work in PHP to make one valid and the other invalid?
What Exactly is happening in Example 4?
Opcodes was supposed to make things clear but just made it more complex because class_exists was called before TestClass but the reverse is the case.
I can not find a write up on PHP class definitions; however, I imagine it is precisely the same as the User-defined functions which your experiments indicate.
Functions need not be defined before they are referenced, except when a function is conditionally defined as shown in the two examples below. When a function is defined in a conditional manner; its definition must be processed prior to being called.
<?php
$makefoo = true;
/* We can't call foo() from here
since it doesn't exist yet,
but we can call bar() */
bar();
if ($makefoo) {
function foo()
{
echo "I don't exist until program execution reaches me.\n";
}
}
/* Now we can safely call foo()
since $makefoo evaluated to true */
if ($makefoo) foo();
function bar()
{
echo "I exist immediately upon program start.\n";
}
?>
This is true for classes as well:
Example 1 works because the class is not conditional on anything else.
Example 2 fails because the class is conditional upon JsonSerializable.
Example 3 works because the class is correctly defined prior to being called.
Example 4 gets false the first time because the class is conditional but succeeds later because the class has been loaded.
The class is made conditional by either implementing an interface or extending another class from another file (require). I'm calling it conditional because the definition now relies upon another definition.
Imagine the PHP interpreter takes a first look at the code in this file. It sees a non-conditional class and/or function, so it goes ahead and loads them in memory. It sees a few conditional ones and skips over them.
Then the Interpreter begins to parse the page for execution. In example 4, it gets to the class_exists("TestClass") instruction, checks memory, and says nope, I don't have that. If doesn't have it because it was conditional. It continues executing the instructions, see the conditional class and executes the instructions to actually load the class into memory.
Then it drops down to the last class_exists("TestClass") and sees that the class does indeed exist in memory.
In reading your opcodes, the TestClass doesn't get called before class_exist. What you see is the SEND_VAL which is sending the value TestClass so that it is in memory for the next line, which actually calls DO_FCALL on class_exists
You can then see how it is handling the class definition itself:
ZEND_DECLARE_CLASS - this is loading your class definition
ZEND_ADD_INTERFACE - this fetches JsonSerializable and adds that to your class defintion
ZEND_VERIFY_ABSTRACT_CLASS - this verifies everything is sane.
It is that second piece ZEND_ADD_INTERFACE that appears to prevent the PHP Engine from merely loading the class on the initial peak at it.
If you desire a more detailed discussion of how the PHP Interpreter
Compiles and Executes the code in these scenarios, I suggest taking a
look at #StasM answer
to this question, he
provides an excellent overview of it in greater depth than this answer goes.
I think we answered all of your questions.
Best Practice: Place each of your classes in it's own file and then autoload them as needed, as #StasM states in his answer, use a sensible file naming and autoloading strategy - e.g. PSR-0 or something similar. When you do this, you no longer have to be concerned with the order of the Engine loading them, it just handles that for you automatically.
The basic premise is that for class to be used it has to be defined, i.e. known to the engine. This can never be changed - if you need an object of some class, the PHP engine needs to know what the class is.
However, the moment where the engine gains such knowledge can be different. First of all, consuming of the PHP code by the engine consists of two separate processes - compilation and execution. On compilation stage, the engine converts PHP code as you know it to the set of opcodes (which you are already familiar with), on the second stage the engine goes through the opcodes as processor would go through instructions in memory, and executes them.
One of the opcodes is the opcode that defines a new class, which is usually inserted in the same place where the class definition is in the source.
However, when the compiler encounters class definition, it may be able to enter the class into the list of the classes known to the engine before executing any code. This is called "early binding". This can happen if the compiler decides that it already has all the information it needs to create a class definition, and there's no reason to defer the class creation until the actual runtime. Currently, the engine does this only if the class:
has no interfaces or traits attached to it
is not abstract
either does not extend any classes or extends only the class that is already known to the engine
is declared as top statement (i.e. not inside condition, function, etc.)
This behavior can also be modified by compiler options, but those are available only to extensions like APC so should not be a matter of much concern to you unless you are going to develop APC or similar extension.
This also means this would be OK:
class B extends A {}
class A { }
but this would not be:
class C extends B {}
class B extends A {}
class A { }
Since A would be early bound, and thus available for B's definition, but B would be defined only in line 2 and thus unavailable for line 1's definition of C.
In your case, when your class implemented the interface, it was not early bound, and thus became known to the engine at the point when "class" statement was reached. When it was simple class without interfaces, it was early bound and thus became known to the engine as soon as the file compilation was finished (you can see this point as one before the first statement in the file).
In order not to bother with all these weird details of the engine, I would support the recommendation of the previous answer - if your script is small, just declare classes before usage. If you have bigger application, define your classes in individual files, and have sensible file naming and autoloading strategy - e.g. PSR-0 or something similar, as suitable in your case.

Can parameter types be specialized in PHP

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'.

Categories