Order of PHP class extensions in a single file - php

In some cases defining PHP class extensions out of order causes a fatal error, and in some cases it doesn't. I'm trying to understand the underlying behavior.
For example, both
<?php
class BaseClass {}
class FirstExt extends BaseClass {}
and
<?php
class FirstExt extends BaseClass {}
class BaseClass {}
are fine, so it's not the case that simply defining subclasses out of order causes a problem.
However, errors arise when there are three classes involved, but only in one specific case, namely when the chain of classes is defined in reverse order. That is, the following code results in a fatal error:
<?php
class SecondExt extends FirstExt {}
class FirstExt extends BaseClass {}
class BaseClass {}
If you try to run this from the command line (say as main.php), you get
PHP Fatal error: Class 'FirstExt' not found in /path/to/main.php on line 2
However, any of the other five orderings of the three classes run with no errors. I was quite surprised that even
<?php
class SecondExt extends FirstExt {}
class BaseClass {}
class FirstExt extends BaseClass {}
works fine. The distinguishing factor is that all three possible pairs of classes are out of order in the case that gives an error, whereas in all the other cases at most two of the three pairs are out of order.
What is going on under the hood to produce this behavior?

The behavior isn't intuitive, but I don't think it's a bug, it's just an effect of the way PHP loads classes.
In order for a class to extend a parent class, the parent class must already be defined when the child class is defined.
Based on my observations, it appears that after the file is parsed and execution begins, the following classes are defined:
built-in classes
all user-defined classes defined before the file was parsed
user-defined base classes in that file
user-defined classes in that file that extend another class already defined, either earlier in that file or before that file was parsed
Basically any class that can be defined at compile time will be, and any other classes not defined at that point will be (attempted to be) defined at run time.
So in this example:
<?php
echo class_exists('A') ? "Yes" : "No"; // No
echo class_exists('B') ? "Yes" : "No"; // Yes
echo class_exists('C') ? "Yes" : "No"; // Yes
class A extends B {}
class C {}
class B extends C {}
class B is defined when class A tries to extend it, because it was defined when the file was parsed, because class C was defined before it in the file.
But in this example:
<?php
echo class_exists('A') ? "Yes" : "No"; // No
echo class_exists('B') ? "Yes" : "No"; // No
echo class_exists('C') ? "Yes" : "No"; // Yes
class A extends B {}
class B extends C {}
class C {}
class B is not defined when class A tries to extend it, because it was not defined when the file was parsed, because class C was not defined before it in the file.
PHP tries to find it, but it's not going to check in the same file again, it's going to try to autoload it. That's when you get "not found".
Add a fourth class and you can see it doesn't only happen when the classes are defined in reverse order:
echo class_exists('A') ? "Yes" : "No"; // No
echo class_exists('B') ? "Yes" : "No"; // No
echo class_exists('C') ? "Yes" : "No"; // Yes
echo class_exists('D') ? "Yes" : "No"; // Yes
class A extends B {}
class D {}
class B extends C {}
class C extends D {}

Related

PHP namespace syntax

I am new to PHP and would appreciate some help with namespaces.
I have a class and it's declared as:
namespace P3;
class CardstreamCodingStandard_Sniffs_Classes_ClassDeclarationSniff {
}
Now i want to implement an interface called CodeSniffer_Sniff. So i amended the class declaration as:
namespace P3;
class CardstreamCodingStandard_Sniffs_Classes_ClassDeclarationSniff extends CodeSniffer_Sniff {
}
But when I run the code I get
Fatal error: Interface 'P3\CodeSniffer_Sniff' not found in /root/qa/CardstreamCodingStandard/Sniffs/Classes/ClassDeclarationSniff.php
Please can someone explain what is going on? and what the correct syntax should be?
Thanks
Ok some more details
I have inherited this code that implements a sniff for phpcs
My class is declared as
namespace CardstreamCodingStandard\Sniffs\Classes;
class CardstreamCodingStandard_Sniffs_Classes_ClassDeclarationSniff implements PHP_CodeSniffer_Sniff {
}
when I run phpcs with this sniff then I get the error
PHP Fatal error: Interface 'CardstreamCodingStandard\Sniffs\Classes\PHP_CodeSniffer_Sniff' not found in /root/qa/CardstreamCodingStandard/Sniffs/Classes/ClassDeclarationSniff.php on line 23
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpcs:0
PHP 2. PHP_CodeSniffer_CLI->runphpcs() /usr/bin/phpcs:25
PHP 3. PHP_CodeSniffer_CLI->process() /usr/share/pear/PHP/CodeSniffer/CLI.php:113
PHP 4. PHP_CodeSniffer->initStandard() /usr/share/pear/PHP/CodeSniffer/CLI.php:956
PHP 5. PHP_CodeSniffer->registerSniffs() /usr/share/pear/PHP/CodeSniffer.php:594
PHP 6. include_once() /usr/share/pear/PHP/CodeSniffer.php:1409
I hope this makes things clearer
Some details on how to use namespaces can be found at the php manual.
In your case it seems you are using the same namespace.
To correctly use an interface, you have to implement it into your class.
The correct use would be the following:
Class Apple implements Fruit { ... }
More on interfaces and how to use them can be found here.
You are using the extends keyword which is used for creating child classes of a parent class.
The php manual on parent and child classes (called inheritance) has been linked here
you can follow this role :
file1:
<?php
namespace foo;
class Cat {
public function says(){
echo 'meoow';
}
}
?>
file2:
<?php
include 'file1.php';
use foo;
class bar extends foo{
//......
}
more details: http://php.net/manual/en/language.namespaces.php
You have to implement an interface, not extend. Try this:
class CardstreamCodingStandard_Sniffs_Classes_ClassDeclarationSniff implements CodeSniffer_Sniff {
}

PHP class definition order

I have a file created with classpreloader and some custom code which compresses a bunch of class files into a single file for distribution:
https://raw.githubusercontent.com/jnvsor/kint/1efd147f9831ade2e03921f7111ced07428556ab/build/kint.php
According to travis this fails on everything except nightly, with an error:
Fatal error: unknown class Kint_Renderer_Text in /home/travis/build/jnvsor/kint/build/kint.php on line 315
As you can see on line 315 is a class Kint_Renderer_Plain extends Kint_Renderer_Text which is defined later in the file with class Kint_Renderer_Text extends Kint_Renderer on line 418.
One would assume that this means class order in a single file is significant.
But when I sort() the source files before building the release file, travis says that everything went perfectly smoothly, despite the fact that the new file also has similar cases:
https://raw.githubusercontent.com/jnvsor/kint/1a657f06cd693b3f9db8f9458f900ef7bb378b53/build/kint.php
For example, class Kint_Object_Closure extends Kint_Object_Instance on line 1249 and class Kint_Object_Instance extends Kint_Object on line 1352
So the question becomes: What exactly are the ordering requirements for classes in PHP?
What exactly are the ordering requirements for classes in PHP
Class must exists to be extended. So if class A extends class B and B is unknown at the moment of instantiation of A your code will fail. You can use autoloader to solve that yet it will defeat the purpose of keeping all in one file (which is wrong idea anyway).
And once again I end up answering my own question because no-one on SO can be bothered to read for more than 10 seconds...
Turns out PHP only checks for dependencies in the same file once, not recursively, and then fails to update the error message. As a result you get things like this:
<?php
class A extends B {} // boom: <b>Fatal error</b>: Class 'B' not found...
class B extends C {}
class C {}
Which work fine when the dependencies dependency is reordered:
<?php
class A extends B {}
class C {}
class B extends C {}
This behaves the same way with more than 2 extends. If it's the 3rd class that can't find its extend it will still report the second couldn't be found:
<?php
class A extends B {} // boom: <b>Fatal error</b>: Class 'B' not found...
class C extends D {}
class B extends C {}
class D {}
This is fixed in 7.2 (Still pre-release) where it will recursively check, or at least check deeper - since as I mentioned in the question this error doesn't occur in nightly.

PHP: why not found class

=== test.php ===
<?php
var_dump(class_exists('Base'));
var_dump(class_exists('A'));
var_dump(class_exists('B'));
class A extends Base {}
class B extends Base {}
class Base
{
public static function e()
{
static $number = 0;
$number++;
var_dump('number is: '.$number);
}
}
run it, result is:
bool(true)
bool(false)
bool(false)
class A and class B extends class Base.
php found class Base.
why class A and class B not found?
Thanks.
You defined a class after var_dump function. Put var_dump below into the class. then it will return true.
Classes in PHP are only bound at compile-time when the information is already available.
So, Base can be bound as it has no dependencies, but A and B depend on Base, which isn't yet bound at the time A and B are defined. So their binding is delayed to run-time. (means the class only exists after the line they're defined on has been executed).
Try putting the Base class before the definitions of A and B and these will be compile-time bound too.
It's the order of the dumps, put them bellow the class code. It's advised that you first put the base class, Base, then afterwards declare the other classes, A and B. And only after they were made, you can actually, var_dump their existance.
So the code should look something like:
<?php
class Base
{
public static function e()
{
static $number = 0;
$number++;
var_dump('number is: '.$number);
}
}
class A extends Base {}
class B extends Base {}
var_dump(class_exists('Base'));
var_dump(class_exists('A'));
var_dump(class_exists('B'));
Just tested in case it's something else and it returns:
bool(true) bool(true) bool(true)

Class not found (in PHP)

This code works without problems:
<?php
namespace NamespaceA;
class A extends \NamespaceB\B {}
namespace NamespaceB;
class B {}
But why the following code cause Fatal error: Class 'NamespaceB\B' not found in ...file?
<?php
namespace NamespaceA;
class A extends \NamespaceB\B {}
namespace NamespaceB;
class B extends \NamespaceC\C {}
namespace NamespaceC;
class C {}
And this code also works without problems:
<?php
namespace NamespaceA;
class A extends \NamespaceB\B {}
namespace NamespaceC;
class C {}
namespace NamespaceB;
class B extends \NamespaceC\C {}
UPD:
Without any namespace, also Fatal error: Class 'B' not found in ...file:
<?php
class A extends B {}
class B extends C {}
class C {}
Works without problems:
<?php
class A extends B {}
class B {}
http://php.net/manual/en/keyword.extends.php
Classes must be defined before they are used. If you want the class A to extend the class B, you will have to define the class B first. The order in which the classes are defined is important.
Edit:
Found more:
Fatal error when extending included class
After some research, it became clear, that actually you can use a class before declaring it. But, declaration of the class and all parent classes must be in the same file.
So if you declare a parent class in one file and a child class in another, it won't work.
Also, you must declare parent classes first. After that you can extend them.
Edit Number 2:
Okay so I did some more research on the issue. There is probably some internal implementation detail that currently allows for the one case to work (my guess would be something regarding auto-loading) however this is something that could change at any time and should never be relied upon.
First use include_once() to add all the files in your index file and when your are extends to any class, instantiate that parent class first.Example:
index.php-->
<?php
include_once('parentClass.php');
include_once('childClass.php');
$parentObj = new parent();
$childObj = new child();
?>
child.php-->
<?php
class child extends parent{
function __construct(){
}
}
?>

is it a php bug? (about extends)

class A extends B {}
class B extends C{}
class C {}
result
PHP Fatal error: class 'B' not found ...
if the order is like this
class A extends B {}
class C {}
class B extends C{}
everything is ok.
PS: if I remove class C {}
class A extends B {}
class B extends C{}
php tells me class 'B' is not found, why?
php version 5.3.4
The PHP manual clearly mentions:
Classes must be defined before they
are used! If you want the class
Named_Cart to extend the class Cart,
you will have to define the class Cart
first. If you want to create another
class called Yellow_named_cart based
on the class Named_Cart you have to
define Named_Cart first. To make it
short: the order in which the classes
are defined is important.
clearly a parser bug
this works
class A extends B {}
class B {}
this doesn't
class C extends D {}
class D extends E {}
class E {}
consider reporting on bugs.php.net
Class order matters in PHP definitions.
Does the order of class definition matter in PHP?
This is why you don't have visibility of the class defined after the one you are defining (in this case class A cant see class B because it is defined after).
Because php is interpreted rather than compiled the order of declaration must be valid. In this example class B doesn't exist for A to extend.
Obivously class B is not defined in the moment you trying to extend it, because it occurs after extends B. Its not a bug, its the way the world works: You can only use, what exists ;)

Categories