The 4 Pillars
- Encapsulation is about keeping an object's internal state private and exposing a controlled API (methods / controlled property access). In PHP you use private/protected/public, and asymmetric property visibility for read/write control.
class BankAccount { private float $balance = 0.0; public function deposit(float $amount): void { if ($amount <= 0) { throw new InvalidArgumentException('Amount > 0'); } $this->balance += $amount; } public function getBalance(): float { return $this->balance; } } $acc = new BankAccount(); $acc->deposit(100.0); echo $acc->getBalance(); // 100
- Inheritance lets one class extend another and reuse/override behaviour. extends copies public/protected members (private members are not inherited directly). Use parent::method() to call the parent implementation.
class Vehicle { public function move(): void { echo "The vehicle is moving"; } } class Car extends Vehicle { public function move(): void { echo "The car drives on the road"; } } class Boat extends Vehicle { public function move(): void { echo "The boat sails on the water"; } } $vehicle = new Vehicle(); $vehicle->move(); // The vehicle is moving $car = new Car(); $car->move(); // The car drives on the road $boat = new Boat(); $boat->move(); // The boat sails on the water
- Polymorphism describes using a common interface or base class so objects of different concrete types can be used interchangeably (Liskov Substitution Principle).
interface PaymentMethod { public function pay(float $amount): void; } class CreditCardPayment implements PaymentMethod { public function pay(float $amount): void { echo "Paid ${amount} using Credit Card"; } } class PayPalPayment implements PaymentMethod { public function pay(float $amount): void { echo "Paid ${amount} via PayPal"; } } class CryptoPayment implements PaymentMethod { public function pay(float $amount): void { echo "Paid ${amount} in Bitcoin"; } } function processPayment(PaymentMethod $method, float $amount): void { $method->pay($amount); } processPayment(new CreditCardPayment(), 100.0); // Paid $100 using Credit Card processPayment(new PayPalPayment(), 50.5); // Paid $50.5 via PayPal processPayment(new CryptoPayment(), 250.0); // Paid $250 in Bitcoin
- Abstraction: Abstract classes and interfaces declare an API without providing full implementation. Use abstract class for partial implementations, interface for pure contracts.
interface Renderer { public function render(array $data): string; } class HtmlRenderer implements Renderer { public function render(array $data): string { // produce HTML return '<pre>' . htmlspecialchars(json_encode($data, JSON_THROW_ON_ERROR)) . '</pre>'; } }
Properties
- Property Type Declarations
Typed properties enforce runtime type rules and reduce bugs.
class Product { public int $id; public ?string $title = null; // nullable }
- Union Types
You can declare union types:
class Example { public int|string $id; }
- Readonly Properties
readonly properties can be set once (usually in the constructor) and then cannot be reassigned.
class User { public readonly int $id; public function __construct(int $id) { $this->id = $id; } }
- Constructor Property Promotion
You can declare class properties directly in the constructor parameter list:
class DTO { public function __construct( public int $id, private string $name, protected ?DateTimeImmutable $createdAt = null ) {} }
This declares the properties and initialises them - less boilerplate.
- Property Hooks
Property hooks (sometimes called property accessors) let you define get and/or set behavior on a particular property (not via __get/__set magic for all unknowns). Hooks allow virtual properties (no actual storage) and fine control over per-property behavior.
Key points:
class Example { private bool $modified = false; public string $foo = 'default value' { get { if ($this->modified) { return $this->foo . ' (modified)'; } return $this->foo; } set(string $value) { $this->foo = strtolower($value); $this->modified = true; } } } $example = new Example(); $example->foo = 'changed'; print $example->foo; // changed (modified)
Class Constants
It is possible to define constants on a per-class basis remaining the same and unchangeable. The default visibility of class constants is public.
Class constants can have a scalar type such as bool, int, float, string, or even array. When using array, the contents can only be other scalar types.
class Kernel { public const int MAX = 100; public const string NAME = 'app'; } $app = new Kernel(); print $app::NAME;
Autoloading Classes
Modern PHP uses autoloading to load classes on demand. The canonical built-in mechanism is spl_autoload_register(); frameworks use PSR-4 (Composer) for namespace => file mapping.
spl_autoload_register(function (string $class) { $file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php'; if (is_file($file)) require $file; });
Best practice: use Composer + PSR-4 autoloading for app code. __autoload() is deprecated/removed - always use SPL autoload functions.
Constructors and Destructors
PHP allows developers to declare constructor methods for classes. Classes which have a constructor method call this method on each newly-created object, so it is suitable for any initialization that the object may need before it is used.
class BaseClass { public function __construct() { print "In BaseClass constructor"; } } class SubClass extends BaseClass { public function __construct() { parent::__construct(); print "In SubClass constructor"; } } class OtherSubClass extends BaseClass { // inherits BaseClass's constructor } $obj = new BaseClass(); // In BaseClass constructor $obj = new SubClass(); // In BaseClass constructor In SubClass constructor $obj = new OtherSubClass(); // In BaseClass constructor
PHP possesses a destructor concept similar to that of other object-oriented languages, such as C++. The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
class MyDestructableClass { public function __construct() { print "In constructor"; } public function __destruct() { print "Destroying " . __CLASS__; } } $obj = new MyDestructableClass(); // In constructor // Destroying MyDestructableClass
Like constructors, parent destructors will not be called implicitly by the engine. In order to run a parent destructor, one would have to explicitly call parent::__destruct() in the destructor body. Also like constructors, a child class may inherit the parent's destructor if it does not implement one itself.
Visibility
The visibility of a property, a method or a constant can be defined by prefixing the declaration with the keywords public, protected or private.
Class members declared public can be accessed everywhere.
Members declared protected can be accessed only within the class itself and by inheriting and parent classes.
Members declared as private may only be accessed by the class that defines the member.
class MyClass { public $public = 'Public'; protected $protected = 'Protected'; private $private = 'Private'; public function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj = new MyClass(); echo $obj->public; // Works echo $obj->protected; // Fatal Error echo $obj->private; // Fatal Error $obj->printHello(); // Shows Public, Protected and Private class MyClass2 extends MyClass { // We can redeclare the public and protected properties, but not private public $public = 'Public2'; protected $protected = 'Protected2'; public function printHello() { echo $this->public; echo $this->protected; echo $this->private; } } $obj2 = new MyClass2(); echo $obj2->public; // Works echo $obj2->protected; // Fatal Error echo $obj2->private; // Undefined $obj2->printHello(); // Shows Public2, Protected2, Undefined
Object Inheritance
Inheritance is a well-established programming principle, and PHP makes use of this principle in its object model. This principle will affect the way many classes and objects relate to one another.
For example, when extending a class, the subclass inherits all of the public and protected methods, properties and constants from the parent class. Unless a class overrides those methods, they will retain their original functionality.
This is useful for defining and abstracting functionality, and permits the implementation of additional functionality in similar objects without the need to reimplement all of the shared functionality.
Note: It is not allowed to override a read-write property with a readonly property or vice versa.
class A { public int $prop; } class B extends A { public readonly int $prop; } // Fatal error: Cannot redeclare non-readonly property A::$prop as readonly B::$prop
Inheritance Example:
class Foo { public function printItem($string) { echo "Foo: ${string}"; } public function printPHP() { echo "PHP is great"; } } class Bar extends Foo { public function printItem($string) { echo "Bar: ${string}"; } } $foo = new Foo(); $bar = new Bar(); $foo->printItem('baz'); // Foo: baz $foo->printPHP(); // PHP is great $bar->printItem('baz'); // Bar: baz $bar->printPHP(); // PHP is great
Scope Resolution Operator (::)
The Scope Resolution Operator (also called Paamayim Nekudotayim) or in simpler terms, the double colon, is a token that allows access to a constant, static property, or static method of a class or one of its parents. Moreover, static properties or methods can be overriden via late static binding.
class MyClass { public const CONST_VALUE = 'A constant value'; } $classname = 'MyClass'; echo $classname::CONST_VALUE; echo MyClass::CONST_VALUE;
Three special keywords self, parent and static are used to access properties or methods from inside the class definition.
class MyClass { public const CONST_VALUE = 'A constant value'; } class OtherClass extends MyClass { public static $my_static = 'static var'; public static function doubleColon() { echo parent::CONST_VALUE; echo self::$my_static; } } $classname = 'OtherClass'; $classname::doubleColon(); // A constant value static var OtherClass::doubleColon(); // A constant value static var
When an extending class overrides the parent's definition of a method, PHP will not call the parent's method. It's up to the extended class on whether or not the parent's method is called. This also applies to Constructors and Destructors, Overloading, and Magic method definitions.
class MyClass { protected function myFunc() { echo "MyClass::myFunc()"; } } class OtherClass extends MyClass { // Override parent's definition public function myFunc() { // But still call the parent function parent::myFunc(); echo "OtherClass::myFunc()"; } } $class = new OtherClass(); $class->myFunc(); // MyClass::myFunc() OtherClass::myFunc()
class A { public static function who() { echo 'A'; } } class B extends A { public static function who() { echo 'B'; } } B::who(); // B A::who(); // A
Static Keyword
Declaring class properties or methods as static makes them accessible without needing an instantiation of the class. These can also be accessed statically within an instantiated class object.
Calling non-static methods statically was deprecated, and generated an E_DEPRECATED warning.
class Foo { public static function aStaticMethod() { // ... } } Foo::aStaticMethod(); $classname = 'Foo'; $classname::aStaticMethod();
Static properties are accessed using the Scope Resolution Operator (::) and cannot be accessed through the object operator (->).
It's possible to reference the class using a variable. The variable's value cannot be a keyword (e.g. self, parent and static).
class Foo { public static $my_static = 'foo'; public function staticValue() { return self::$my_static; } } class Bar extends Foo { public function fooStatic() { return parent::$my_static; } } print Foo::$my_static; // foo $foo = new Foo(); print $foo->staticValue(); // foo print $foo->my_static; // Warning: Undefined property: Foo::$my_static (Accessing static property as non static) print $foo::$my_static; // foo $classname = 'Foo'; print $classname::$my_static; // foo print Bar::$my_static; // foo $bar = new Bar(); print $bar->fooStatic(); // foo
Class Abstraction
PHP has abstract classes, methods, and properties. Classes defined as abstract cannot be instantiated, and any class that contains at least one abstract method or property must also be abstract. Methods defined as abstract simply declare the method's signature and whether it is public or protected; they cannot define the implementation. Properties defined as abstract may declare a requirement for get or set behavior, and may provide an implementation for one, but not both, operations.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child class, and follow the usual inheritance and signature compatibility rules.
An abstract class may declare an abstract property, either public or protected. A protected abstract property may be satisfied by a property that is readable/writeable from either protected or public scope.
An abstract property may be satisfied either by a standard property or by a property with defined hooks, corresponding to the required operation.
abstract class AbstractClass { // Force extending class to define this method abstract protected function getValue(); abstract protected function prefixValue($prefix); // Common method public function printOut() { print $this->getValue(); } } class ConcreteClass1 extends AbstractClass { protected function getValue() { return "ConcreteClass1"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass1"; } } class ConcreteClass2 extends AbstractClass { public function getValue() { return "ConcreteClass2"; } public function prefixValue($prefix) { return "{$prefix}ConcreteClass2"; } } $class1 = new ConcreteClass1(); $class1->printOut(); // ConcreteClass1 echo $class1->prefixValue('FOO_'); // FOO_ConcreteClass1 $class2 = new ConcreteClass2(); $class2->printOut(); // ConcreteClass2 echo $class2->prefixValue('FOO_'); // FOO_ConcreteClass2
- Abstract property example:
abstract class A { // Extending classes must have a publicly-gettable property abstract public string $readable { get; } // Extending classes must have a protected- or public-writeable property abstract protected string $writeable { set; } // Extending classes must have a protected or public symmetric property abstract protected string $both { get; set; } } class C extends A { // This satisfies the requirement and also makes it settable, which is valid public string $readable; // This would NOT satisfy the requirement, as it is not publicly readable protected string $readable; // This satisfies the requirement exactly, so is sufficient. // It may only be written to, and only from protected scope protected string $writeable { set => $value; } // This expands the visibility from protected to public, which is fine public string $both; }
Object Interfaces
Object interfaces allow you to create code which specifies which methods and properties a class must implement, without having to define how these methods or properties are implemented. Interfaces share a namespace with classes, traits, and enumerations, so they may not use the same name.
Interfaces are defined in the same way as a class, but with the interface keyword replacing the class keyword and without any of the methods having their contents defined.
All methods declared in an interface must be public; this is the nature of an interface.
In practice, interfaces serve two complementary purposes:
Interfaces may define magic methods to require implementing classes to implement those methods.
To implement an interface, the implements operator is used. All methods in the interface must be implemented within a class; failure to do so will result in a fatal error. Classes may implement more than one interface if desired by separating each interface with a comma.
Interfaces can be extended like classes using the extends operator.
It's possible for interfaces to have constants. Interface constants work exactly like class constants. They cannot be overridden by a class/interface that inherits them.
Interfaces may also declare properties. If they do, the declaration must specify if the property is to be readable, writeable, or both. The interface declaration applies only to public read and write access.
A class may satisfy an interface property in multiple ways. It may define a public property. It may define a public virtual property that implements only the corresponding hook. Or a read property may be satisfied by a readonly property. However, an interface property that is settable may not be readonly.
- Interface properties example:
interface I { // An implementing class MUST have a publicly-readable property, // but whether or not it's publicly settable is unrestricted. public string $readable { get; } // An implementing class MUST have a publicly-writeable property, // but whether or not it's publicly readable is unrestricted. public string $writeable { set; } // An implementing class MUST have a property that is both publicly // readable and publicly writeable. public string $both { get; set; } } // This class implements all three properties as traditional, un-hooked // properties. That's entirely valid. class C1 implements I { public string $readable; public string $writeable; public string $both; } // This class implements all three properties using just the hooks // that are requested. This is also entirely valid. class C2 implements I { private string $written = ''; private string $all = ''; // Uses only a get hook to create a virtual property. // This satisfies the "public get" requirement. // It is not writeable, but that is not required by the interface. public string $readable { get => strtoupper($this->writeable); } // The interface only requires the property be settable, // but also including get operations is entirely valid. // This example creates a virtual property, which is fine. public string $writeable { get => $this->written; set { $this->written = $value; } } // This property requires both read and write be possible, // so we need to either implement both, or allow it to have // the default behavior. public string $both { get => $this->all; set { $this->all = strtoupper($value); } } }
- Interface example:
// Declare the interface 'Template' interface Template { public function setVariable($name, $var); public function getHtml($template); } // Implement the interface // This will work class WorkingTemplate implements Template { private $vars = []; public function setVariable($name, $var) { $this->vars[$name] = $var; } public function getHtml($template) { foreach ($this->vars as $name => $value) { $template = str_replace('{' . $name . '}', $value, $template); } return $template; } } // Fatal error: Class BadTemplate contains 1 abstract method // and must therefore be declared abstract or implement the remaining methods class BadTemplate implements Template { private $vars = []; public function setVariable($name, $var) { $this->vars[$name] = $var; } }
- Extendable Interfaces:
interface A { public function foo(); } interface B extends A { public function baz(Baz $baz); } // This will work class C implements B { public function foo() { } public function baz(Baz $baz) { } } // This will not work and result in a fatal error class D implements B { public function foo() { } public function baz(Foo $foo) { } }
Traits
PHP implements a way to reuse code called Traits.
Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies. The semantics of the combination of Traits and classes is defined in a way which reduces complexity, and avoids the typical problems associated with multiple inheritance and Mixins.
A Trait is similar to a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance.
trait TraitA { public function sayHello() { echo 'Hello'; } } trait TraitB { public function sayWorld() { echo 'World'; } } class MyHelloWorld { use TraitA, TraitB; // A class can use multiple traits public function sayHelloWorld() { $this->sayHello(); $this->sayWorld(); } } $myHelloWorld = new MyHelloWorld(); $myHelloWorld->sayHelloWorld(); // HelloWorld
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
To resolve naming conflicts between Traits used in the same class, the insteadof operator needs to be used to choose exactly one of the conflicting methods.
Since this only allows one to exclude methods, the as operator can be used to add an alias to one of the methods. Note the as operator does not rename the method and it does not affect any other method either.
trait A { public function hello() { echo "A"; } } trait B { public function hello() { echo "B"; } } class C { use A, B { B::hello insteadof A; A::hello as helloFromA; } } $obj = new C(); $obj->hello(); // B $obj->helloFromA(); // A
The final modifier can be applied using the as operator to methods imported from traits. This can be used to prevent child classes from overriding the method. However, the class that uses the trait can still override the method.
trait CommonTrait { public function method() { echo 'Hello'; } } class FinalExampleA { use CommonTrait { CommonTrait::method as final; // The 'final' prevents child classes from overriding the method } } class FinalExampleB extends FinalExampleA { public function method() {} } // Fatal error: Cannot override final method
Anonymous Classes
Anonymous classes are useful when simple, one-off objects need to be created.
// Using an explicit class class Logger { public function log($msg) { echo $msg; } } $util->setLogger(new Logger()); // Using an anonymous class $util->setLogger(new class { public function log($msg) { echo $msg; } });
They can pass arguments through to their constructors, extend other classes, implement interfaces, and use traits just like a normal class can:
class SomeClass {} interface SomeInterface {} trait SomeTrait {} var_dump(new class(10) extends SomeClass implements SomeInterface { private $num; public function __construct($num) { $this->num = $num; } use SomeTrait; }); // object(SomeClass@anonymous)#1 (1) { ["num":"SomeClass@anonymous":private]=> int(10) }
- Readonly Anonymous Classes
The readonly modifier can be applied to anonymous classes.
$dto = new readonly class(10) { public function __construct(public readonly int $id) {} }; echo $dto->id; // 10
Overloading
Overloading in PHP provides means to dynamically create properties and methods. These dynamic entities are processed via magic methods one can establish in a class for various action types.
The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope.
All overloading methods must be defined as public.
Note: PHP's interpretation of overloading is different than most object-oriented languages. Overloading traditionally provides the ability to have multiple methods with the same name but different quantities and types of arguments.
- Property Overloading
class PropertyTest { /** Location for overloaded data. */ private $data = []; /** Overloading not used on declared properties. */ public $declared = 1; /** Overloading only used on this when accessed outside the class. */ private $hidden = 2; public function __set($name, $value) { echo "Setting $name to $value"; $this->data[$name] = $value; } public function __get($name) { echo "Getting $name: "; if (array_key_exists($name, $this->data)) { return $this->data[$name]; } $trace = debug_backtrace(); trigger_error( 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } public function __isset($name) { echo "Is $name set? "; return isset($this->data[$name]); } public function __unset($name) { echo "Unsetting $name"; unset($this->data[$name]); } /** Not a magic method, just here for example. */ public function getHidden() { return $this->hidden; } } $obj = new PropertyTest; $obj->a = 1; // Setting a to 1 echo $obj->a; // Getting a: 1 var_dump(isset($obj->a)); // Is a set? bool(true) unset($obj->a); // Unsetting a var_dump(isset($obj->a)); // Is a set? bool(false) echo $obj->declared; // 1 echo $obj->getHidden(); // 2 echo $obj->hidden; // Getting hidden: // Notice: Undefined property via __get()
- Method Overloading
class MethodTest { public function __call($name, $arguments) { // Note: value of $name is case sensitive. echo "Calling object method $name ". implode(', ', $arguments); } public static function __callStatic($name, $arguments) { // Note: value of $name is case sensitive. echo "Calling static method $name ". implode(', ', $arguments); } } $obj = new MethodTest; $obj->runTest('in object context'); // Calling object method runTest in object context MethodTest::runTest('in static context'); // Calling static method runTest in static context
Object Iteration
PHP provides a way for objects to be defined so it is possible to iterate through a list of items, with, for example a foreach statement. By default, all visible properties will be used for the iteration.
class MyClass { public $var1 = 'value 1'; public $var2 = 'value 2'; public $var3 = 'value 3'; protected $protected = 'protected var'; private $private = 'private var'; function iterateVisible() { echo "MyClass::iterateVisible:"; foreach ($this as $key => $value) { print "$key => $value"; } } } $class = new MyClass(); foreach($class as $key => $value) { print "$key => $value"; } // var1 => value 1 // var2 => value 2 // var3 => value 3 $class->iterateVisible(); // MyClass::iterateVisible: // var1 => value 1 // var2 => value 2 // var3 => value 3 // protected => protected var // private => private var
Magic Methods
Magic methods are special methods which override PHP's default's action when certain actions are performed on an object.
All methods names starting with __ are reserved by PHP. Therefore, it is not recommended to use such method names unless overriding PHP's behavior.
The following method names are considered magical:
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), and __debugInfo().
Final Keyword
The final keyword prevents child classes from overriding a method, property, or constant by prefixing the definition with final. If the class itself is being defined final then it cannot be extended.
- Final method example:
class BaseClass { public function test() { echo "BaseClass::test() called"; } final public function moreTesting() { echo "BaseClass::moreTesting() called"; } } class ChildClass extends BaseClass { public function moreTesting() { echo "ChildClass::moreTesting() called"; } } // Fatal error: Cannot override final method BaseClass::moreTesting()
- Final class example:
final class BaseClass { public function test() { echo "BaseClass::test() called\n"; } // As the class is already final, the final keyword is redundant final public function moreTesting() { echo "BaseClass::moreTesting() called\n"; } } class ChildClass extends BaseClass { } // Fatal error: Class ChildClass cannot extend final class BaseClass
- Final property example:
class BaseClass { final protected string $test; } class ChildClass extends BaseClass { public string $test; } // Fatal error: Cannot override final property BaseClass::$test
- Final constant example:
class Foo { final public const X = "foo"; } class Bar extends Foo { public const X = "bar"; } // Fatal error: Bar::X cannot override final constant Foo::X
Object Cloning
Creating a copy of an object with fully replicated properties is not always the wanted behavior. A good example of the need for copy constructors, is if you have an object which represents a GTK window and the object holds the resource of this GTK window, when you create a duplicate you might want to create a new window with the same properties and have the new object hold the resource of the new window. Another example is if your object holds a reference to another object which it uses and when you replicate the parent object you want to create a new instance of this other object so that the replica has its own separate copy.
An object copy is created by using the clone keyword (which calls the object's __clone() method if possible).
$copy_of_object = clone $object;
When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.
Once the cloning is complete, if a __clone() method is defined, then the newly created object's __clone() method will be called, to allow any necessary properties that need to be changed.
class SubObject { static $instances = 0; public $instance; public function __construct() { $this->instance = ++self::$instances; } public function __clone() { $this->instance = ++self::$instances; } } class MyCloneable { public $object1; public $object2; function __clone() { // Force a copy of this->object, // otherwise it will point to same object. $this->object1 = clone $this->object1; } } $obj = new MyCloneable(); $obj->object1 = new SubObject(); $obj->object2 = new SubObject(); $obj2 = clone $obj; print "Original Object"; print_r($obj); // MyCloneable Object ( [object1] => SubObject Object ( [instance] => 1 ) [object2] => SubObject Object ( [instance] => 2 ) ) print "Cloned Object"; print_r($obj2); // MyCloneable Object ( [object1] => SubObject Object ( [instance] => 3 ) [object2] => SubObject Object ( [instance] => 2 ) )
Comparing Objects
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values (values are compared with ==), and are instances of the same class.
When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.
$a = new stdClass(); $a->x = 1; $b = new stdClass(); $b->x = 1; var_dump($a == $b); // true (same properties and values) var_dump($a === $b); // false (different instances) $c = $a; var_dump($a === $c); // true
Late Static Bindings
PHP implements a feature called late static bindings which can be used to reference the called class in a context of static inheritance.
More precisely, late static bindings work by storing the class named in the last "non-forwarding call". In case of static method calls, this is the class explicitly named (usually the one on the left of the :: operator); in case of non static method calls, it is the class of the object. A "forwarding call" is a static one that is introduced by self::, parent::, static::, or, if going up in the class hierarchy, forward_static_call(). The function get_called_class() can be used to retrieve a string with the name of the called class and static:: introduces its scope.
This feature was named "late static bindings" with an internal perspective in mind. "Late binding" comes from the fact that static:: will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It was also called a "static binding" as it can be used for (but is not limited to) static method calls.
- Limitations of self::
Static references to the current class like self:: or __CLASS__ are resolved using the class in which the function belongs, as in where it was defined:
class A { public static function who() { echo __CLASS__; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); // A
- Late Static Bindings' usage
Late static bindings tries to solve that limitation by introducing a keyword that references the class that was initially called at runtime. Basically, a keyword that would allow referencing B from test() in the previous example. It was decided not to introduce a new keyword but rather use static that was already reserved.
class A { public static function who() { echo __CLASS__; } public static function test() { static::who(); // Here comes Late Static Bindings } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); // B
Objects and References
One of the key-points of PHP OOP that is often mentioned is that "objects are passed by references by default". This is not completely true.
A PHP reference is an alias, which allows two different variables to write to the same value. In PHP, an object variable doesn't contain the object itself as value. It only contains an object identifier which allows object accessors to find the actual object. When an object is sent by argument, returned or assigned to another variable, the different variables are not aliases: they hold a copy of the identifier, which points to the same object.
class A { public $foo = 1; } $a = new A(); $b = $a; // $a and $b are copies of the same identifier // ($a) = ($b) = <id> $b->foo = 2; echo $a->foo; // 2 $c = new A(); $d = &$c; // $c and $d are references // ($c, $d) = <id> $d->foo = 2; echo $c->foo; // 2 $e = new A; function foo($obj) { // ($obj) = ($e) = <id> $obj->foo = 2; } foo($e); echo $e->foo; // 2
Important conceptual rules:
Object Serialization
serialize() returns a string containing a byte-stream representation of any value that can be stored in PHP. unserialize() can use this string to recreate the original variable values. Using serialize to save an object will save all variables in an object. The methods in an object will not be saved, only the name of the class.
In order to be able to unserialize() an object, the class of that object needs to be defined. That is, if you have an object of class A and serialize this, you'll get a string that refers to class A and contains all values of variables contained in it. If you want to be able to unserialize this in another file, an object of class A, the definition of class A must be present in that file first. This can be done for example by storing the class definition of class A in an include file and including this file or making use of the spl_autoload_register() function.
class A { public $one = 1; public function showOne() { echo $this->one; } } // page1.php: include "A.php"; $a = new A; $s = serialize($a); // store $s somewhere where page2.php can find it. file_put_contents('store', $s); // page2.php: // this is needed for the unserialize to work properly. include "A.php"; $s = file_get_contents('store'); $a = unserialize($s); // now use the function showOne() of the $a object. $a->showOne();
Covariance and Contravariance
Covariance allows a child's method to return a more specific type than the return type of its parent's method. Contravariance allows a parameter type to be less specific in a child method, than that of its parent.
- Covariance
To illustrate how covariance works, a simple abstract parent class, Animal is created. Animal will be extended by children classes, Cat, and Dog.
abstract class Animal { protected string $name; public function __construct(string $name) { $this->name = $name; } abstract public function speak(); } class Dog extends Animal { public function speak() { echo $this->name . " barks"; } } class Cat extends Animal { public function speak() { echo $this->name . " meows"; } }
Note that there aren't any methods which return values in this example. A few factories will be added which return a new object of class type Animal, Cat, or Dog.
interface AnimalShelter { public function adopt(string $name): Animal; } class CatShelter implements AnimalShelter { public function adopt(string $name): Cat { return new Cat($name); // instead of returning class type Animal, it can return class type Cat } } class DogShelter implements AnimalShelter { public function adopt(string $name): Dog { return new Dog($name); // instead of returning class type Animal, it can return class type Dog } } $kitty = (new CatShelter)->adopt("Ricky"); $kitty->speak(); // Ricky meows $doggy = (new DogShelter)->adopt("Mavrick"); $doggy->speak(); // Mavrick barks
- Contravariance
Continuing with the previous example with the classes Animal, Cat, and Dog, a class called Food and AnimalFood will be included, and a method eat(AnimalFood $food) is added to the Animal abstract class.
class Food {} class AnimalFood extends Food {} abstract class Animal { protected string $name; public function __construct(string $name) { $this->name = $name; } public function eat(AnimalFood $food) { echo $this->name . " eats " . get_class($food); } }
In order to see the behavior of contravariance, the eat method is overridden in the Dog class to allow any Food type object. The Cat class remains unchanged.
class Dog extends Animal { public function eat(Food $food) { echo $this->name . " eats " . get_class($food); } }
The next example will show the behavior of contravariance.
$kitty = (new CatShelter)->adopt("Ricky"); $catFood = new AnimalFood(); $kitty->eat($catFood); // Ricky eats AnimalFood $doggy = (new DogShelter)->adopt("Mavrick"); $banana = new Food(); $doggy->eat($banana); // Mavrick eats Food
But what happens if $kitty tries to eat() the $banana?
$kitty->eat($banana); // Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
- Property Variance
By default, properties are neither covariant nor contravariant, hence invariant. That is, their type may not change in a child class at all. The reason for that is "get" operations must be covariant, and "set" operations must be contravariant. The only way for a property to satisfy both requirements is to be invariant.
It is possible to declare a property that has only a get or set operation. As a result, abstract properties or virtual properties that have only a "get" operation required may be covariant. Similarly, an abstract property or virtual property that has only a "set" operation required may be contravariant.
Once a property has both a get and set operation, however, it is no longer covariant or contravariant for further extension. That is, it is now invariant.
class Animal {} class Dog extends Animal {} class Poodle extends Dog {} interface PetOwner { // Only a get operation is required, so this may be covariant. public Animal $pet { get; } } class DogOwner implements PetOwner { // This may be a more restrictive type since the "get" side // still returns an Animal. However, as a native property // children of this class may not change the type anymore. public Dog $pet; } class PoodleOwner extends DogOwner { // This is NOT ALLOWED, because DogOwner::$pet has both // get and set operations defined and required. public Poodle $pet; } // Fatal error: Type of PoodleOwner::$pet must be Dog (as in class DogOwner)
Source: Orkhan Alishov's notes