2.10. Pełnomocnik (Proxy)
2.10.1. Przeznaczenie
Stosowany jest w celu kontrolowanego tworzenia na żądanie kosztownych obiektów oraz kontroli dostępu do nich.
2.10.2. Przykłady
Doctrine2 używa Pełnomocników do zaimplementowania późnej inicjalizacji (ang. lazy initialization), co przekłada się na sposób pracy z encjami. Zamiast otrzymywać obiekt encji, która na przykład nie może być w pełni zainicjalizowana, otrzymujemy obiekt Pośrednika.
2.10.3. Diagram UML

2.10.4. Kod
Ten kod znajdziesz również na GitHub.
BankAccount.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Proxy;
6
7interface BankAccount
8{
9 public function deposit(int $amount);
10
11 public function getBalance(): int;
12}
HeavyBankAccount.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Proxy;
6
7class HeavyBankAccount implements BankAccount
8{
9 /**
10 * @var int[]
11 */
12 private array $transactions = [];
13
14 public function deposit(int $amount)
15 {
16 $this->transactions[] = $amount;
17 }
18
19 public function getBalance(): int
20 {
21 // this is the heavy part, imagine all the transactions even from
22 // years and decades ago must be fetched from a database or web service
23 // and the balance must be calculated from it
24
25 return array_sum($this->transactions);
26 }
27}
BankAccountProxy.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Proxy;
6
7class BankAccountProxy extends HeavyBankAccount implements BankAccount
8{
9 private ?int $balance = null;
10
11 public function getBalance(): int
12 {
13 // because calculating balance is so expensive,
14 // the usage of BankAccount::getBalance() is delayed until it really is needed
15 // and will not be calculated again for this instance
16
17 if ($this->balance === null) {
18 $this->balance = parent::getBalance();
19 }
20
21 return $this->balance;
22 }
23}
2.10.5. Testy
ProxyTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Proxy\Tests;
6
7use DesignPatterns\Structural\Proxy\BankAccountProxy;
8use PHPUnit\Framework\TestCase;
9
10class ProxyTest extends TestCase
11{
12 public function testProxyWillOnlyExecuteExpensiveGetBalanceOnce()
13 {
14 $bankAccount = new BankAccountProxy();
15 $bankAccount->deposit(30);
16
17 // this time balance is being calculated
18 $this->assertSame(30, $bankAccount->getBalance());
19
20 // inheritance allows for BankAccountProxy to behave to an outsider exactly like ServerBankAccount
21 $bankAccount->deposit(50);
22
23 // this time the previously calculated balance is returned again without re-calculating it
24 $this->assertSame(30, $bankAccount->getBalance());
25 }
26}