How PHP interfaces actually change the behavior of classes
P粉289775043
2023-09-02 23:49:28
<p>According to the PHP documentation, </p>
<blockquote>
<p>Object interfaces allow you to create code that specifies which methods a class must implement without defining how those methods are implemented. </p>
</blockquote>
<p>Thus, an interface is like a class with predefined methods that still need to be accessed using the <code>-></code> notation</p>
<p>However, the ArrayAccess interface provides access to objects as arrays. Objects can be accessed using <code>$object->property</code> and <code>$object["property"]</code></p>
<p>I can't understand how ArrayAccess makes it possible to change the object syntax. I wrote a piece of code to try to replicate the effect of the <code>ArrayAccess</code> method<em>only one</em>, but it throws the error</p>
<pre class="brush:php;toolbar:false;">// Using the PHP ArrayAccess Interface
namespace A {
class myclass implements \ArrayAccess {
public function offsetExists($offset) { return true; }
public function offsetGet($offset) {
// changed behavior
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 behavior
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
}
</pre>
<p>Please help me explain it correctly. Thank you</p>
I'm not saying that interfaces "change the behavior of a class", but that interfaces make extending class functionality easy.
To understand interface, as an object-oriented programming concept, we should first understand what problem it wants to solve.
What problem is "Interface" designed to solve?
An interface is a contract. This is how duck-typing is implemented in PHP. You need to think from 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 ensure that library users know that
$personneeds to have agetName()method, you can create a classPerson> that has agetName()method. Type declarations are then used to detect potential errors when the code is resolved.class Greeter { public function greet(Person $person) { echo "Hello, {$person->getName()}!\n"; } } class Person { public string $name; public function getName(): string { return $this->name; } }Suppose there is another library that feeds 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; } }Consider this...
Now, suppose the user wants to write a
Petclass that can both eat and greet. Users don't want to have to write these features again just toPetthem.How to write
Petto use both theGreeterandFeederlibraries?Maybe this is the case?
class Pet extends Person, Animal { }Unfortunately, PHP does not support multiple inheritance . A class can only
extendone class. The above code is not valid. So in the current situation, users can only use one of the libraries.Additionally, "name" might be a very different concept for different things (e.g., one might use
getName() to return$first_nameand$last_name代码>). There may not be a reasonable default implementation of thegetName()method in your library class.So, as a library writer, you want his/her library to be as flexible as possible for the user. what can you do?
How to solve this problem using "interface" in PHP?
An interface is a declaration of method signature. This is a quick way to declare library requirements without concrete class/inheritance requirements.
Using interfaces you can override both libraries like this:
GreeterLibraryclass Greeter { public function greet(Namer $namer) { echo "Hello, {$namer->getName()}!\n"; } } interface Namer { public function getName(): string; }FeederLibraryclass Feeder { public function feed(Eater $eater, string $food) { $eater->eat($food); } } interface Eater { public function eat(string $food); }No need for a specific class (or parent class inheritance), a class can implement multiple interfaces. So the following
Petclass is completely 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 objects of this
Petclass can be used with theGreeterlibrary and theFeederlibrary.ArrayAccessWhat about the interface?ArrayAccess The interface is not declared by a third-party interface library writer, but is written by a core PHP writer. The core PHP writer provides deeper support for this.
Somewhat like the interface we mentioned before, PHP provides functionality to the classes that implement it. But instead of providing the
GreeterorFeederexamples above, core PHP provides syntax sugar for classes that implement ArrayAccess. This means you can use simpler code when working with classes that implement the 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 implement them, replace these with:
$obj = new Obj; $obj->offsetSet(10, "hello"); $obj->offsetSet(11, "world"); if ($obj->offsetUnset(12)) { $obj->offsetUnset(12); } echo $obj->offsetGet(11);You can use
$objwith array-like syntax to make the code shorter:$obj = new Obj; $obj[10] = "hello"; $obj[11] = "world"; if (isset($obj[12])) { unset($obj[12]); } echo $obj[11];