According to PHP documentation,
Object Interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are implemented.
Hence, an interface is like a class with predefined method which still needs to be accessed with the ->
symbol
However, the ArrayAccess Interface provides accessing of objects as arrays. Making it possible for an object to be accessed with both $object->property
and $object["property"]
I cannot understand how the ArrayAccess
makes it possible to change an object syntax. I've written out a code to try replicating the effect of just one of the ArrayAccess
method but it throws an error
// Using the PHP ArrayAccess Interface
namespace A {
class myclass implements \ArrayAccess {
public function offsetExists($offset) { return true; }
public function offsetGet($offset) {
// changed behaviour
return $this->{$offset} ?? null;
}
public function offsetSet($offset, $value) {}
public function offsetUnset($offset) {}
}
$myclass = new myclass();
$myclass->access = 'Interface';
echo $myclass['access']; // "Interface"
};
//Trying to implement my own ArrayAccess Interface
namespace B {
interface MyArrayAccess {
public function offsetGet($offset);
}
class myclass implements MyArrayAccess {
public function offsetGet($offset) {
// change behaviour
return $this->{$offset} ?? null;
}
}
$myclass = new myclass();
$myclass->access = 'Interface';
echo $myclass['access']; // Fatal error: Uncaught Error: Cannot use object of type B\myclass as array
}
Please help me explain this properly. Thanks
Rather than to say that interface "change the behaviour of a class", I'd say interface makes it easy to expand a class functionality.
To understand interface, as an object-oriented programming concept, we should first understand what problem it is here to solve.
Interfaces are kind of contract. It is the way to achieve duck-typing in PHP. You need to think in the perspective of a library writer, who wants to expose functionality to others. For example,
class Greeter
{
public function greet($person)
{
echo "Hello, {$person->getName()}!\n";
}
}
To make sure that the library user knows that a $person
needs to have a getName()
method, you may create a class Person
that has a getName()
method. Then use Type Declaration to detect potential error when the code is parsed.
class Greeter
{
public function greet(Person $person)
{
echo "Hello, {$person->getName()}!\n";
}
}
class Person
{
public string $name;
public function getName(): string
{
return $this->name;
}
}
Let's assume there is another library to feed things with food:
class Feeder {
public function feed(Eater $eater, string $food) {
$eater->eat($food);
}
}
class Animal {
private $stomach = [];
public function eat(string $food) {
$stomach = $food;
}
}
Now, let's say a user want to write a class Pet
that can both eat and be greet. The user don't want to write those features again just for Pet
.
How to write Pet
so both Greeter
and Feeder
library can be used on it?
Perhaps something like this?
class Pet extends Person, Animal {
}
Unfortunately, PHP DO NOT support multiple inheritance. A class can only extend
one single class. The above code is not valid. So in the current situation, user can only use either one of the libraries.
Also for different thing, "name" might be a very different concept (e.g. a person might return both $first_name
and $last_name
with getName()
). There might not be a sensible default implementation of getName()
method in your library class to begin with.
So you, as a library writer who want his / her library to be as flexible for user as possible. What can you do?
Interface is declaration of method signatures. It is a shortcut to declare library requirement without a concrete class / inheritance requirement.
With interface, you may rewrite both library like so:
Greeter
libraryclass Greeter {
public function greet(Namer $namer) {
echo "Hello, {$namer->getName()}!\n";
}
}
interface Namer {
public function getName(): string;
}
Feeder
libraryclass Feeder {
public function feed(Eater $eater, string $food) {
$eater->eat($food);
}
}
interface Eater {
public function eat(string $food);
}
Instead of requiring a concrete class (or parent class inheritance), a class is allowed to implement multiple interfaces. So the below Pet
class is totally valid in PHP:
class Pet implements Namer, Eater {
private array $stomach = [];
private string $name = '';
public function __construct(string $name)
{
$this->name = $name;
}
/**
* Implements Namer.
*/
public function getName(): string
{
return $this->name;
}
/**
* Implements Eater.
*/
public function eat(string $food)
{
$this->stomach[] = $food;
}
}
$greeter = new Greeter();
$feeder = new Feeder();
$pet = new Pet('Paul');
$greeter->greet($pet);
$feeder->feed($pet, 'a biscuit');
Now, an object of this Pet
class can work both with Greeter
library and the Feeder
library.
ArrayAccess
interface?The ArrayAccess interface is an interface declared not by 3rd party library writers, but by core PHP writers. Core PHP writer provides even more profound support to it.
A bit like interface we mentioned before, PHP provides feature to class that implements it. But instead of our Greeter
or Feeder
examples above, core PHP provides Syntactic sugar to class that implements ArrayAccess. That means you may use cleaner code to work with classes implemented AccessAccess interface.
In the official example,
<?php
class Obj implements ArrayAccess {
private $container = array();
public function __construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
If you implemented them, then instead of these:
$obj = new Obj;
$obj->offsetSet(10, "hello");
$obj->offsetSet(11, "world");
if ($obj->offsetUnset(12)) {
$obj->offsetUnset(12);
}
echo $obj->offsetGet(11);
You may use $obj
with array-like syntax to make your code shorter:
$obj = new Obj;
$obj[10] = "hello";
$obj[11] = "world";
if (isset($obj[12])) {
unset($obj[12]);
}
echo $obj[11];