2.10. Proxy
2.10.1. Purpose
To interface to anything that is expensive or impossible to duplicate.
2.10.2. Examples
Doctrine2 uses proxies to implement framework magic (e.g. lazy initialization) in them, while the user still works with his own entity classes and will never use nor touch the proxies
2.10.3. UML Diagram
Alt Proxy UML Diagram2.10.4. Code
You can also find this code on 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. Test
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}