PHP class definition order - php

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.

Related

Order of PHP class extensions in a single file

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 {}

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 {
}

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(){
}
}
?>

PHP: Interfaces and inheritance in a name spaced environment

I am trying to use a interface or child class to work in a situation where I am using class hints on a method argument and keep getting the following warning:
$print_r(class_parents($listing));
$propertyTable -> getPhotos($listing);
Array ( [Tools\Object\Property] => Tools\Object\Property )
Catchable fatal error: Argument 1 passed to Tools\Db\PhotoTable::getPhotos() must be an
instance of Tools\Db\Property, instance of Tools\Object\Listing given, called in ...
and defined in ...
This is strange as you can see when tested listing extends property (see below). Why would I get this error?
I have set up a very basic test case and have figured out that type hints should accept a child class or a class that implements the required class where that class is an interface. However, in a name spaced Zend Framework 2 environment I cannot get this to work.
My code for the various classes looks like this:
namespace Tools\Db;
class PhotoTable
{
public function getPhotos(Property $propertyObject )
{
//code goes here
}
}
namespace Tools\Object;
use Tools\Object\PhotoInterface as PhotoInterface;
class property //implements photoInterface
{
public function getUrl(){ code goes here}
public function getPhotos(){ code goes here}
}//end class
use Tools\Object\PhotoInterface as PhotoInterface;
class Listing extends Property implements PhotoInterface
{
//code goes here
}
namespace Tools\Object;
interface PhotoInterface
{
public function getUrl();
public function getPhotos();
}
I can get the code above to work if I copy this all into a single file and eliminate the namespaces. Basically:
if I require property in PhotoTable I can pass Listing as it extends property.
If I require PhotoInterface I can pass Listing as it implements this interface.
But I get this weird error when I have essentially the same classes in a different files in a name spaced Zend Framework 2 environment.
Is there an extra complication in a name spaced environment I need to take into account of or am I missing something really basic.
It appears that the issue involved the name-spaced files included in the head of the script. Specifically, it appears you need to include not only the class being am passed, but also the specified parent classes or any Interface classes if they are specified in the type hint.
To give an example if my class type hint suggests I need 'property' class, then if I am sending it the 'Listing' object, it seems I need to include property in the header like this:
namespace Tools\Object;
use Tools\Object\Property; //the parent class
use Tools\Object\Listing; //the child class
This eliminates the fatal error, but it seems strange that PHP cannot automatically determine the class automatically. I assume it is something to do with the complexities of a name spaced environment.

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