PHP Get child class name from parent class differences - php

I would like to get child class in parent class construct. The thing is, I found several PHP methods here in SO and don't know which one to use, which one is faster ?
class Parent {
function __construct() {
echo static::class; // Method 1 (PHP 5.5+)
echo get_called_class(); // Method 2 (PHP 5.3+)
echo get_class($this); // Method 3 (PHP 5.2+)
}
}
class Child extends Parent {
function __construct() {
parent::__construct();
}
}
All echos write same result : Child. But why there's 3 different method for same result ? Which one is better, or more optimized ?

The three different versions represent the evolution of PHP handling of static. The earliest one get_class was insufficient to handle scenarios where you needed to distinguish the class as called and the class as defined. Here is an example
So, get_called_class was introduced in PHP 5.3 to resolve the ambiguity. That lasted quite a while, and is still a valid choice, but now we have the pseudo selector ::class. Why?
The pseudo selector provides two nice benefits. The first is that it allows you to replace string class names with compile-time checking of namespace rules. Compare:
namespace Foo\Bar;
class Baz { public static function hi($name) { echo "Hi, $name!"; } }
call_user_func([ Baz::class, 'hi' ], 'bishop');
with:
call_user_func([ '\Foo\Bar\Baz', 'hi' ], 'bishop');
If I fat finger that last one, mistyping it, that will be a run-time error:
call_user_func([ '\Foo\Bar\Bza', 'hi' ], 'bishop'); // error at runtime!
But using the ::class pseudo I get compile-time checking:
call_user_func([ Bza::class, 'hi' ], 'bishop'); // error at compile-time!
I realize in an interpreted language the distinction between compile-time and run-time is thin, but it does matter when it comes to performance. And that's the second benefit: ::class is more performant.

Related

Why is this method called without being asked to?

I came across this very strange behavior.
The following code
class TestClass {
function testClass() {
echo "Don't show me!";
}
}
$testing = new TestClass;
executes its method testClass without it being called!
However, testClass won't run if renamed into anything else like testClass1.
Is there any hidden 'PHP magic' behind this behaviour?
EDIT.
At the end I see this question is trivial to ninjas grown up with PHP. As recent newcomer to PHP, I've learned to use __construct as constructor. With that "relic behaviour" carefully removed from modern tutorials. I am so glad people realized how terrible it was, changing class name and forgetting to change that of the constructor - what a nightmare!
Pre-PHP5, the __construct method was not used as the class constructor. Instead, a method with the same name as the class was used.
From the documentation:
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, and the class did not inherit one from a parent class, it will search for the old-style constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues is if the class had a method named __construct() which was used for different semantics.
Creating a (empty) constructor (method named __construct) will stop the message from being echoed upon class initialization (only needed for < PHP 5.3.3 *):
class TestClass {
function __construct() {
}
function testClass() {
echo "Don't show me!";
}
}
* As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
In older versions of PHP a method with the same name as the classname was considered the constructor.

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.

How to prevent PHP from considering the functions, with the same name of a class, a constructor

Before PHP 5.3.3 the following
class Same {
public function same() { echo 'Not good'; }
}
$c = new Same();
will output Not good.
From 5.3.3+ instead it will not output the string. That's because from PHP 5.3.3 functions with the same name of the class are not considered constructors.
How do I force this behavior even with PHP 5.3.2 or before?
The easiest way is probably just to create an empty constructor:
class Same {
public function same() { echo 'Not good'; }
public function __construct() { }
}
$c = new Same();
That won't echo "Not good" as the __construct() method overrides the "same name as class" method.
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, it will search for the old-style constructor function, by the name of the class. Effectively, it means that the only case that would have compatibility issues is if the class had a method named __construct() which was used for different semantics.
Unlike with other methods, PHP will not generate an E_STRICT level error message when __construct() is overridden with different parameters than the parent __construct() method has.
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
docs:- http://php.net/manual/en/language.oop5.decon.php
So obvious solution is to declare a __constructors method (even is an empty one)
The construct method is named __construct(). Simply call your same()-method inside __construct() if u wish to have the same name.
According to http://php.net/construct php tries to reserve backwards compatibility. In my opinion the "same" name means writing the method name case-sensitive (as the class name). That should work too.

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

PHP interface problem - class not found

Hi I have a very simple class that implements an interface. Both the class and the interface are in the same file.
When I implement the interface I get a fatal error "Class not found", but when I remove the implements and then try to use the class I can use it fine???
Can anyone offer any advice on this?
Sorry here is some code that I am using to test at the moment:
$tester = new TypeOneTester();
$tester->test("Hello");
interface iTestInterface
{
public function test($data);
}
class TypeOneTester implements iTestInterface
{
public function test($data)
{
return $data;
}
}
Create an instance of your class after the class and the interface are defined, not before.
The order of definition in this case should be:
Interface
Class
Instance of Class (objects)
This is a (very poorly) documented limitation:
http://php.net/manual/pl/migration5.incompatible.php
In some cases classes must be declared before use. It only happens if some of the new features of PHP 5 (such as interfaces) are used. Otherwise the behaviour is the old.
I've filed a bug report nonetheless. IMO it should be fixed as it's inconsistent behaviour and the error message is not helpful for anyone who assumes as I did that PHP simply didn't care where you declare functions/classes. Come on, it's been there for over 10 years now...
https://bugs.php.net/bug.php?id=69665
smells like a bug in php. Make sure it's reproducible with the latest version and post to bugs.php.net.
Reproduce code
interface I {}
$a = new A;
$b = new B;
class A {
function __construct() { echo 'A'; }
}
class B implements I {
function __construct() { echo 'B'; }
}
Expected
AB
Actual:
A
Fatal error: Class 'B' not found...
That is because, php loading interface, and instantiate class class class object where there is a certain order and must be in a Php file, if the file is not in accordance with an order of 1. Require_one interface, 2. Require_one class

Categories