PHP
Stängning
Sök…
Grundläggande användning av en stängning
En stängning är PHP-ekvivalenten för en anonym funktion, t.ex. en funktion som inte har ett namn. Även om det tekniskt inte är korrekt, förblir beteendet för en stängning detsamma som en funktion, med några extra funktioner.
En stängning är inget annat än ett objekt i Closure-klassen som skapas genom att deklarera en funktion utan namn. Till exempel:
<?php
$myClosure = function() {
echo 'Hello world!';
};
$myClosure(); // Shows "Hello world!"
Kom ihåg att $myClosure
är ett exempel på Closure
så att du är medveten om vad du verkligen kan göra med det (se http://fr2.php.net/manual/en/class.closure.php )
Det klassiska fallet du skulle behöva en stängning är när du måste ge en callable
till en funktion, till exempel usort .
Här är ett exempel där en matris sorteras efter antalet syskon till varje person:
<?php
$data = [
[
'name' => 'John',
'nbrOfSiblings' => 2,
],
[
'name' => 'Stan',
'nbrOfSiblings' => 1,
],
[
'name' => 'Tom',
'nbrOfSiblings' => 3,
]
];
usort($data, function($e1, $e2) {
if ($e1['nbrOfSiblings'] == $e2['nbrOfSiblings']) {
return 0;
}
return $e1['nbrOfSiblings'] < $e2['nbrOfSiblings'] ? -1 : 1;
});
var_dump($data); // Will show Stan first, then John and finally Tom
Använda externa variabler
Det är möjligt, i en stängning, för att använda en extern variabel med den speciella nyckelordet användning. Till exempel:
<?php
$quantity = 1;
$calculator = function($number) use($quantity) {
return $number + $quantity;
};
var_dump($calculator(2)); // Shows "3"
Du kan gå längre genom att skapa "dynamiska" stängningar. Det är möjligt att skapa en funktion som returnerar en specifik kalkylator, beroende på den mängd du vill lägga till. Till exempel:
<?php
function createCalculator($quantity) {
return function($number) use($quantity) {
return $number + $quantity;
};
}
$calculator1 = createCalculator(1);
$calculator2 = createCalculator(2);
var_dump($calculator1(2)); // Shows "3"
var_dump($calculator2(2)); // Shows "4"
Grundläggande stängningsbindning
Som vi tidigare sett är en stängning inget annat än ett exempel på klassen, och olika metoder kan påkallas på dem. En av dem är bindTo
, som med tanke på en stängning kommer att returnera en ny som är bunden till ett givet objekt. Till exempel:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
public $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);
$myBoundClosure(); // Shows "Hello world!"
Stängningsbindande och omfattning
Låt oss överväga detta exempel:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
public $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);
$myBoundClosure(); // Shows "Hello world!"
Försök att ändra property
synlighet till antingen protected
eller private
. Du får ett dödligt fel som indikerar att du inte har tillgång till den här egenskapen. Även om stängningen har varit bunden till föremålet, är omfattningen i vilken stängningen åberopas inte den som behövs för att ha den åtkomsten. Det är vad det andra argumentet för bindTo
är för.
Det enda sättet att få tillgång till en fastighet om den är private
är att den nås från en räckvidd som tillåter den, dvs. klassens omfattning. I det just föregående kodexemplet har omfattningen inte specificerats, vilket innebär att stängningen har åberopats i samma omfattning som den som användes där stängningen har skapats. Låt oss ändra det:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
private $property; // $property is now private
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance, MyClass::class);
$myBoundClosure(); // Shows "Hello world!"
Som just sagt, om denna andra parameter inte används, åberopas stängningen i samma sammanhang som den som användes där stängningen har skapats. Till exempel kommer en stängning som skapas i en metods klass som åberopas i ett objektsammanhang att ha samma räckvidd som metodens:
<?php
class MyClass
{
private $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
public function getDisplayer()
{
return function() {
echo $this->property;
};
}
}
$myInstance = new MyClass('Hello world!');
$displayer = $myInstance->getDisplayer();
$displayer(); // Shows "Hello world!"
Binder en stängning för ett samtal
Sedan PHP7 är det möjligt att binda en stängning bara för ett samtal tack vare call
. Till exempel:
<?php
class MyClass
{
private $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myClosure = function() {
echo $this->property;
};
$myInstance = new MyClass('Hello world!');
$myClosure->call($myInstance); // Shows "Hello world!"
I motsats till bindTo
metoden finns det inget utrymme att oroa sig för. Räckvidden som används för det här samtalet är detsamma som det som används vid åtkomst till eller åkallande av en egendom på $myInstance
.
Använd stängningar för att implementera observatörsmönster
I allmänhet är en iakttagare en klass med en specifik metod som kallas när en handling på det observerade objektet inträffar. I vissa situationer kan stängningar vara tillräckliga för att implementera observatörens designmönster.
Här är ett detaljerat exempel på en sådan implementering. Låt oss först förklara en klass vars syfte är att meddela observatörer när dess egendom ändras.
<?php
class ObservedStuff implements SplSubject
{
protected $property;
protected $observers = [];
public function attach(SplObserver $observer)
{
$this->observers[] = $observer;
return $this;
}
public function detach(SplObserver $observer)
{
if (false !== $key = array_search($observer, $this->observers, true)) {
unset($this->observers[$key]);
}
}
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function getProperty()
{
return $this->property;
}
public function setProperty($property)
{
$this->property = $property;
$this->notify();
}
}
Låt oss sedan förklara klassen som kommer att representera de olika observatörerna.
<?php
class NamedObserver implements SplObserver
{
protected $name;
protected $closure;
public function __construct(Closure $closure, $name)
{
$this->closure = $closure->bindTo($this, $this);
$this->name = $name;
}
public function update(SplSubject $subject)
{
$closure = $this->closure;
$closure($subject);
}
}
Låt oss äntligen testa detta:
<?php
$o = new ObservedStuff;
$observer1 = function(SplSubject $subject) {
echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};
$observer2 = function(SplSubject $subject) {
echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};
$o->attach(new NamedObserver($observer1, 'Observer1'))
->attach(new NamedObserver($observer2, 'Observer2'));
$o->setProperty('Hello world!');
// Shows:
// Observer1 has been notified! New property value: Hello world!
// Observer2 has been notified! New property value: Hello world!
Observera att detta exempel fungerar eftersom observatörerna delar samma karaktär (de är båda "namngivna observatörer.")