├── .gitignore ├── Creational ├── Prototype.php ├── Singleton.php ├── Builder.php ├── BuilderExt.php ├── FactoryMethod.php ├── AbstractFactory.php └── PrototypeExt.php ├── LICENSE ├── Behavioral ├── TemplateMethod.php ├── State.php ├── Command.php ├── TemplateMethodExt.php ├── Memento.php ├── Mediator.php ├── Strategy.php ├── Iterator.php ├── Observer.php ├── CommandExt.php ├── IteratorExt.php ├── ChainOfResponsibility.php └── Visitor.php ├── Structural ├── Bridge.php ├── Adapter.php ├── Decorator.php ├── Proxy.php ├── Facade.php ├── Composite.php └── Flyweight.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea/ 3 | 4 | # Win specific 5 | Thumbs.db 6 | Desktop.ini 7 | 8 | # Mac specific 9 | .DS_Store -------------------------------------------------------------------------------- /Creational/Prototype.php: -------------------------------------------------------------------------------- 1 | title = $title; 12 | } 13 | 14 | /** 15 | * Cloning method, each object should implement how it will be cloned himself 16 | */ 17 | public function getClone(): self 18 | { 19 | return new static($this->title); 20 | } 21 | 22 | public function getName() 23 | { 24 | return $this->title; 25 | } 26 | 27 | public function __toString() 28 | { 29 | return $this->title; 30 | } 31 | } 32 | 33 | # Client code example 34 | $page = new Page('Page Title'); 35 | 36 | echo $pageClone = $page->getClone(); 37 | /* Output: Page Title */ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sauron918 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Behavioral/TemplateMethod.php: -------------------------------------------------------------------------------- 1 | create(); 15 | $this->init(); 16 | $this->test(); 17 | $this->deploy(); 18 | } 19 | 20 | protected function create() 21 | { 22 | echo 'Creating an application..' . PHP_EOL; 23 | } 24 | 25 | protected function init() 26 | { 27 | echo 'Initialization..' . PHP_EOL; 28 | } 29 | 30 | protected function test() 31 | { 32 | echo 'Running tests..' . PHP_EOL; 33 | } 34 | 35 | abstract protected function deploy(); 36 | } 37 | 38 | /** 39 | * Concrete classes override some operations from the template method algorithm 40 | */ 41 | class AndroidBuilder extends Builder 42 | { 43 | protected function deploy() 44 | { 45 | echo 'Deploying Android application!' . PHP_EOL; 46 | } 47 | } 48 | 49 | class iOSBuilder extends Builder 50 | { 51 | protected function deploy() 52 | { 53 | echo 'Deploying iOS application!' . PHP_EOL; 54 | } 55 | } 56 | 57 | 58 | # Client code example 59 | (new AndroidBuilder)->build(); 60 | /* Output: 61 | Creating an application.. 62 | Initialization.. 63 | Running tests.. 64 | Deploying Android application! */ 65 | 66 | (new iOSBuilder)->build(); 67 | /* Output: 68 | Creating an application.. 69 | Initialization.. 70 | Running tests.. 71 | Deploying iOS application! */ -------------------------------------------------------------------------------- /Creational/Singleton.php: -------------------------------------------------------------------------------- 1 | init(); 26 | } 27 | 28 | /** 29 | * Some initialization can be here 30 | */ 31 | protected function init() 32 | { 33 | } 34 | 35 | // Cloning and unserialization are not permitted for singletons 36 | final private function __clone() 37 | { 38 | } 39 | 40 | final private function __wakeup() 41 | { 42 | } 43 | } 44 | 45 | class Application 46 | { 47 | use Singleton; 48 | 49 | protected function init() 50 | { 51 | echo 'Application is initialized once ...'; 52 | } 53 | } 54 | 55 | // there is the only one way to get an application instance 56 | $app = Application::getInstance(); 57 | // every call will give the same instance 58 | assert(Application::getInstance() === $app); 59 | 60 | /* Output: Application is initialized once ... */ 61 | 62 | /* Next calls will produce errors: 63 | $app = new Application(); 64 | $app = clone $app; 65 | $app = unserialize(serialize($app)); */ -------------------------------------------------------------------------------- /Structural/Bridge.php: -------------------------------------------------------------------------------- 1 | theme = $theme; 16 | } 17 | 18 | abstract public function getContent(): string; 19 | } 20 | 21 | class HomePage extends WebPage 22 | { 23 | public function getContent(): string 24 | { 25 | return "Home page in " . $this->theme->getColor(); 26 | } 27 | } 28 | 29 | class AboutPage extends WebPage 30 | { 31 | public function getContent(): string 32 | { 33 | return "About page in " . $this->theme->getColor(); 34 | } 35 | } 36 | 37 | 38 | /** 39 | * Separate themes hierarchy. 40 | * You can extend themes hierarchy independently from pages classes 41 | */ 42 | interface Theme 43 | { 44 | public function getColor(); 45 | } 46 | 47 | /** 48 | * Themes follow the common interface. It makes them 49 | * compatible with all types of pages. 50 | */ 51 | class DarkTheme implements Theme 52 | { 53 | public function getColor() 54 | { 55 | return 'Dark colors' . PHP_EOL; 56 | } 57 | } 58 | 59 | class LightTheme implements Theme 60 | { 61 | public function getColor() 62 | { 63 | return 'White colors'. PHP_EOL; 64 | } 65 | } 66 | 67 | # Client code example 68 | $darkTheme = new DarkTheme(); 69 | $homePage = new HomePage($darkTheme); 70 | echo $homePage->getContent(); // Output: Home page in Dark colors 71 | 72 | $lightTheme = new LightTheme(); 73 | $aboutPage = new AboutPage($lightTheme); 74 | echo $aboutPage->getContent(); // Output: About page in White colors -------------------------------------------------------------------------------- /Structural/Adapter.php: -------------------------------------------------------------------------------- 1 | kindle = $kindle; 54 | } 55 | 56 | public function open() 57 | { 58 | return $this->kindle->turnOn(); 59 | } 60 | 61 | public function turnPage() 62 | { 63 | return $this->kindle->pressNextButton(); 64 | } 65 | } 66 | 67 | # Client code example 68 | $book = new Book(); 69 | echo $book->open(); 70 | echo $book->turnPage(); 71 | 72 | // transform Kindle e-book to the 'simple book' interface 73 | $book = new KindleAdapter(new Kindle()); 74 | echo $book->open(); 75 | echo $book->turnPage(); 76 | 77 | /* Output: 78 | Open the book.. 79 | Go to the next page.. 80 | 81 | Turn on the Kindle.. 82 | Press next button on Kindle.. */ -------------------------------------------------------------------------------- /Behavioral/State.php: -------------------------------------------------------------------------------- 1 | state = $state; 16 | } 17 | 18 | /** 19 | * Allows changing the editor state at runtime 20 | * @param State $state 21 | */ 22 | public function setState(State $state) 23 | { 24 | $this->state = $state; 25 | } 26 | 27 | /** 28 | * Just printing words based on current state 29 | * @param string $words 30 | */ 31 | public function type(string $words) 32 | { 33 | $this->state->write($words); 34 | } 35 | } 36 | 37 | /** 38 | * State interface declares methods that all Concrete State should implement 39 | */ 40 | interface State 41 | { 42 | public function write(string $words); 43 | } 44 | 45 | 46 | class DefaultState implements State 47 | { 48 | public function write(string $words) 49 | { 50 | echo $words . PHP_EOL; 51 | } 52 | } 53 | 54 | class UpperCase implements State 55 | { 56 | public function write(string $words) 57 | { 58 | echo strtoupper($words) . PHP_EOL; 59 | } 60 | } 61 | 62 | class LowerCase implements State 63 | { 64 | public function write(string $words) 65 | { 66 | echo strtolower($words) . PHP_EOL; 67 | } 68 | } 69 | 70 | # Client code example 71 | $editor = new TextEditor(new DefaultState()); 72 | $editor->type('First line'); 73 | 74 | $editor->setState(new UpperCase()); 75 | $editor->type('Second line'); 76 | 77 | $editor->setState(new LowerCase()); 78 | $editor->type('Third line'); 79 | 80 | /* Output: 81 | First line 82 | SECOND LINE 83 | third line */ 84 | 85 | 86 | -------------------------------------------------------------------------------- /Creational/Builder.php: -------------------------------------------------------------------------------- 1 | title = $builder->title; 19 | $this->header = $builder->header; 20 | $this->content = $builder->content; 21 | $this->footer = $builder->footer; 22 | } 23 | 24 | public function show(): string 25 | { 26 | return $this->title . $this->header . $this->content . $this->footer; 27 | } 28 | } 29 | 30 | class PageBuilder 31 | { 32 | public $title; 33 | 34 | public $header = ''; 35 | public $content = ''; 36 | public $footer = ''; 37 | 38 | public function __construct(string $title) 39 | { 40 | $this->title = $title; 41 | } 42 | 43 | public function addHeader(string $header) 44 | { 45 | $this->header = $header; 46 | return $this; 47 | } 48 | 49 | public function addContent(string $content) 50 | { 51 | $this->content = $content; 52 | return $this; 53 | } 54 | 55 | public function addFooter(string $footer) 56 | { 57 | $this->footer = $footer; 58 | return $this; 59 | } 60 | 61 | public function build(): Page 62 | { 63 | return new Page($this); 64 | } 65 | } 66 | 67 | # Client code example 68 | $page = (new PageBuilder('

Home page

')) 69 | ->addHeader('
') 70 | ->addContent('
content
'); 71 | 72 | // some time letter .. 73 | $page->addFooter(''); 74 | 75 | echo $page->build()->show(); 76 | /* Output: 77 |

Home page

content
*/ -------------------------------------------------------------------------------- /Behavioral/Command.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; 15 | } 16 | 17 | public abstract function execute(); 18 | } 19 | 20 | /** 21 | * Concrete command, doesn't do all the work by self and only passes the call to the receiver 22 | */ 23 | class TurnOnCommand extends Command 24 | { 25 | public function execute() 26 | { 27 | $this->receiver->turnOn(); 28 | } 29 | } 30 | 31 | class TurnOffCommand extends Command 32 | { 33 | public function execute() 34 | { 35 | $this->receiver->turnOff(); 36 | } 37 | } 38 | 39 | /** 40 | * Invoker of commands 41 | */ 42 | class Invoker 43 | { 44 | /** 45 | * @var []Command Queue of commands 46 | */ 47 | protected $commands = []; 48 | 49 | public function pushCommand(Command $command) 50 | { 51 | $this->commands[] = $command; 52 | } 53 | 54 | /** 55 | * Executes all commands from queue 56 | */ 57 | public function execute() 58 | { 59 | foreach ($this->commands as $key => $command) { 60 | $command->execute(); 61 | unset($this->commands[$key]); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Receiver of commands, contains some business logic 68 | */ 69 | class Receiver 70 | { 71 | public function turnOn() 72 | { 73 | echo "Receiver: Turning on something..\n"; 74 | } 75 | 76 | public function turnOff() 77 | { 78 | echo "Receiver: Turning off something..\n"; 79 | } 80 | } 81 | 82 | # Client code example 83 | $invoker = new Invoker(); 84 | $receiver = new Receiver(); 85 | 86 | $invoker->pushCommand(new TurnOnCommand($receiver)); 87 | $invoker->pushCommand(new TurnOffCommand($receiver)); 88 | $invoker->execute(); 89 | 90 | /* Output: 91 | Receiver: Turning on something.. 92 | Receiver: Turning off something.. */ -------------------------------------------------------------------------------- /Creational/BuilderExt.php: -------------------------------------------------------------------------------- 1 | title; 18 | $result .= $this->header; 19 | $result .= $this->content; 20 | $result .= $this->footer; 21 | 22 | return $result; 23 | } 24 | } 25 | 26 | class Director 27 | { 28 | protected $builder; 29 | 30 | public function __construct(Builder $builder) 31 | { 32 | $this->builder = $builder; 33 | } 34 | 35 | /** 36 | * Tells the builder what to do (desired sequence) 37 | */ 38 | public function construct() 39 | { 40 | $this->builder->addHeader('header'); 41 | $this->builder->addContent('content'); 42 | $this->builder->addFooter('footer'); 43 | } 44 | } 45 | 46 | abstract class Builder 47 | { 48 | abstract public function addHeader(string $header); 49 | 50 | abstract public function addContent(string $content); 51 | 52 | abstract public function addFooter(string $footer); 53 | } 54 | 55 | class HTMLPageBuilder extends Builder 56 | { 57 | /** 58 | * @var Page 59 | */ 60 | private $page; 61 | 62 | public function __construct(Page $page) 63 | { 64 | $this->page = $page; 65 | } 66 | 67 | public function addHeader(string $header) 68 | { 69 | $this->page->header = '
' . $header . '
'; 70 | } 71 | 72 | public function addContent(string $content) 73 | { 74 | $this->page->content = '
' . $content . '
'; 75 | } 76 | 77 | public function addFooter(string $footer) 78 | { 79 | $this->page->footer = ''; 80 | } 81 | } 82 | 83 | # Client code example 84 | $page = new Page(); 85 | $director = new Director(new HTMLPageBuilder($page)); 86 | $director->construct(); 87 | 88 | echo $page->show(); 89 | /* Output: 90 |
header
content
*/ -------------------------------------------------------------------------------- /Structural/Decorator.php: -------------------------------------------------------------------------------- 1 | coffee = $coffee; 36 | } 37 | 38 | // change, "decorate" the default behavior 39 | public function getCost() 40 | { 41 | return $this->coffee->getCost() + 2; 42 | } 43 | 44 | public function getDescription() 45 | { 46 | return $this->coffee->getDescription() . ', with milk'; 47 | } 48 | } 49 | 50 | class VanillaCoffee implements Coffee 51 | { 52 | protected $coffee; 53 | 54 | public function __construct(Coffee $coffee) 55 | { 56 | $this->coffee = $coffee; 57 | } 58 | 59 | public function getCost() 60 | { 61 | return $this->coffee->getCost() + 3; 62 | } 63 | 64 | public function getDescription() 65 | { 66 | return $this->coffee->getDescription() . ', with vanilla'; 67 | } 68 | } 69 | 70 | # Client code example 71 | $coffee = new SimpleCoffee(); 72 | echo $coffee->getDescription() . ' - ' . $coffee->getCost() . PHP_EOL; 73 | 74 | // apply the Decorator for the $coffee object 75 | $milkCoffee = new MilkCoffee($coffee); 76 | echo $milkCoffee->getDescription() . ' - ' . $milkCoffee->getCost() . PHP_EOL; 77 | 78 | // we also can use chain calls of Decorators 79 | $vanillaMilkCoffee = new VanillaCoffee(new MilkCoffee(new SimpleCoffee())); 80 | echo $vanillaMilkCoffee->getDescription() . ' - ' . $vanillaMilkCoffee->getCost() . PHP_EOL; 81 | 82 | /** Output: 83 | Coffee - 10 84 | Coffee, with milk - 12 85 | Coffee, with milk, with vanilla - 15 */ -------------------------------------------------------------------------------- /Structural/Proxy.php: -------------------------------------------------------------------------------- 1 | token = $token; 24 | } 25 | 26 | public function getWeather(string $location): string 27 | { 28 | $json = file_get_contents('http://api.openweathermap.org/data/2.5/weather?q=' . $location 29 | . '&APPID=' . $this->token); 30 | $data = json_decode($json, true); 31 | 32 | return 'weather: ' . $data['weather'][0]['description'] ?? 'no data' . PHP_EOL; 33 | } 34 | } 35 | 36 | /** 37 | * Proxy has an interface identical to the Subject and allows us caching the results 38 | */ 39 | class WeatherProxy implements WeatherClient 40 | { 41 | /** @var WeatherClient Real third party client */ 42 | protected $client; 43 | private $cache = []; 44 | 45 | public function __construct(WeatherClient $client) 46 | { 47 | $this->client = $client; 48 | } 49 | 50 | public function getWeather(string $location): string 51 | { 52 | if (!isset($this->cache[$location])) { 53 | echo "- cache: MISS\n"; 54 | $this->cache[$location] = $this->client->getWeather($location); 55 | } else { 56 | echo "+ cache: HIT, retrieving result from cache..\n"; 57 | } 58 | 59 | return $this->cache[$location]; 60 | } 61 | } 62 | 63 | # Client code example 64 | $weather = new Weather('177b4a1be7dfd10e0d30e8fdeabe0ea9'); 65 | $proxy = new WeatherProxy($weather); 66 | echo $proxy->getWeather('Kiev'); 67 | echo $proxy->getWeather('Lviv'); 68 | echo $proxy->getWeather('Kiev'); 69 | 70 | /* Output example: 71 | - cache: MISS 72 | weather: clear sky 73 | - cache: MISS 74 | weather: scattered clouds 75 | + cache: HIT, retrieving result from cache.. 76 | weather: clear sky */ -------------------------------------------------------------------------------- /Creational/FactoryMethod.php: -------------------------------------------------------------------------------- 1 | createFormatter(); 15 | $this->data = $formatter->wrapData($data); 16 | } 17 | 18 | /** 19 | * Factory Method, usually abstract 20 | * but it can also return a certain standard product (formatter) 21 | */ 22 | abstract function createFormatter(): Formatter; 23 | 24 | public function __toString() 25 | { 26 | return $this->data; 27 | } 28 | } 29 | 30 | /** 31 | * Concrete factories extend that method to produce different kinds of response 32 | */ 33 | class JSONResponse extends Response 34 | { 35 | public function createFormatter(): Formatter 36 | { 37 | return new JSONFormatter(); 38 | } 39 | } 40 | 41 | class HTMLResponse extends Response 42 | { 43 | public function createFormatter(): Formatter 44 | { 45 | return new HTMLFormatter(); 46 | } 47 | } 48 | 49 | /** 50 | * Formatter interface declares behaviors of various types of response 51 | */ 52 | interface Formatter 53 | { 54 | public function wrapData(string $data): string; 55 | } 56 | 57 | class HTMLFormatter implements Formatter 58 | { 59 | public function wrapData(string $data): string 60 | { 61 | return '' . $data . ''; 62 | 63 | } 64 | } 65 | 66 | class JSONFormatter implements Formatter 67 | { 68 | public function wrapData(string $data): string 69 | { 70 | return '{"code": 200, "response": "' . $data . '"}'; 71 | } 72 | } 73 | 74 | # Client code example 75 | $data = 'some input data'; 76 | $responseFormat = 'json'; // taken from configuration for example 77 | $response = ''; 78 | 79 | switch ($responseFormat) { 80 | case 'json': 81 | $response = new JsonResponse($data); // will use JSONFormatter 82 | break; 83 | case 'html': 84 | $response = new HTMLResponse($data); // will use HTMLFormatter 85 | break; 86 | } 87 | 88 | echo $response; 89 | /* Output: {"code": 200, "response": "some input data"} */ -------------------------------------------------------------------------------- /Behavioral/TemplateMethodExt.php: -------------------------------------------------------------------------------- 1 | beforeSteps(); 16 | $this->openFile(); 17 | $this->validate(); 18 | $this->makeConversion(); 19 | $this->closeFile(); 20 | $this->afterSteps(); 21 | } 22 | 23 | /** 24 | * Default implementations of some steps 25 | */ 26 | protected function openFile() 27 | { 28 | echo "Step1. Read from file..\n"; 29 | } 30 | 31 | protected function closeFile() 32 | { 33 | echo "Step4. Close file descriptor..\n"; 34 | 35 | } 36 | 37 | /** 38 | * These steps have to be implemented in subclasses 39 | */ 40 | abstract protected function validate(); 41 | 42 | abstract protected function makeConversion(); 43 | 44 | /** 45 | * Optional methods provide additional extension points 46 | */ 47 | protected function beforeSteps() 48 | { 49 | } 50 | 51 | protected function afterSteps() 52 | { 53 | } 54 | } 55 | 56 | /** 57 | * Concrete class implements all abstract operations of the abstract class. 58 | * They also overrides some operations with a default implementation 59 | */ 60 | class PDFFileConverter extends AbstractFileConverter 61 | { 62 | protected function validate() 63 | { 64 | echo "Step2. Validate PDF file..\n"; 65 | } 66 | 67 | protected function makeConversion() 68 | { 69 | echo "Step3. Convert PDF file..\n"; 70 | } 71 | } 72 | 73 | /** 74 | * Another concrete class with self implementation of some steps 75 | */ 76 | class CSVFileConverter extends AbstractFileConverter 77 | { 78 | protected function validate() 79 | { 80 | echo "Step2. Validate CSV file..\n"; 81 | } 82 | 83 | protected function makeConversion() 84 | { 85 | echo "Step3. Convert CSV file..\n"; 86 | } 87 | } 88 | 89 | # Client code example 90 | (new PDFFileConverter())->convert(); 91 | 92 | /* Output: 93 | Step1. Read from file.. 94 | Step2. Validate PDF file.. 95 | Step3. Convert PDF file.. 96 | Step4. Close a file descriptor.. */ -------------------------------------------------------------------------------- /Structural/Facade.php: -------------------------------------------------------------------------------- 1 | to = $mailAddress; 38 | return $this; 39 | } 40 | 41 | public function subject($mailSubject) 42 | { 43 | $this->subject = $mailSubject; 44 | return $this; 45 | } 46 | 47 | public function send() 48 | { 49 | // sending of mail .. 50 | echo "Email to {$this->to} with subject '{$this->subject}' was sent..\n"; 51 | } 52 | } 53 | 54 | class SignUpFacade 55 | { 56 | private $validator; 57 | private $userService; 58 | private $mailService; 59 | 60 | public function __construct() 61 | { 62 | $this->validator = new Validator(); 63 | $this->userService = new User(); 64 | $this->mailService = new Mail(); 65 | } 66 | 67 | /** 68 | * Facade hides the complexity 69 | */ 70 | public function signUpUser($userName, $userPass, $userMail) 71 | { 72 | if (!$this->validator->isValidMail($userMail)) { 73 | throw new \Exception('Invalid email'); 74 | } 75 | 76 | $this->userService->create($userName, $userPass, $userMail); 77 | $this->mailService->to($userMail)->subject('Welcome')->send(); 78 | } 79 | } 80 | 81 | # Client code example 82 | // We simple call signUpUser() method and don't care about details of registration process 83 | try { 84 | (new SignUpFacade())->signUpUser('Sergey', '123456', 'test@mail.com'); 85 | } catch (\Exception $e) { 86 | echo "User registration error"; 87 | } 88 | 89 | /* Output: 90 | User 'Sergey' was crated.. 91 | Email to test@mail.com with subject 'Welcome' was sent.. */ -------------------------------------------------------------------------------- /Behavioral/Memento.php: -------------------------------------------------------------------------------- 1 | content = $this->content . ' ' . $words; 16 | } 17 | 18 | public function getContent(): string 19 | { 20 | return $this->content; 21 | } 22 | 23 | /** 24 | * Saves the current state inside a memento snapshot 25 | */ 26 | public function save(): EditorMemento 27 | { 28 | return new EditorMemento($this->content); 29 | } 30 | 31 | /** 32 | * Restores the editor's state from a memento object 33 | * @param EditorMemento $memento 34 | */ 35 | public function restore(EditorMemento $memento) 36 | { 37 | $this->content = $memento->getContent(); 38 | } 39 | } 40 | 41 | /** 42 | * The Memento interface provides a way to retrieve the memento's metadata, such as creation date 43 | */ 44 | interface Memento 45 | { 46 | public function getDate(); 47 | } 48 | 49 | /** 50 | * Concrete Memento contains the infrastructure for storing the editor's state. 51 | * Tip: in real life you probably prefer to make a copy of an object's state easier 52 | * by simply using a PHP serialization. 53 | */ 54 | class EditorMemento implements Memento 55 | { 56 | protected $content; 57 | protected $date; 58 | 59 | public function __construct(string $content) 60 | { 61 | $this->content = $content; 62 | $this->date = date('Y-m-d'); 63 | } 64 | 65 | public function getContent() 66 | { 67 | return $this->content; 68 | } 69 | 70 | public function getDate() 71 | { 72 | return $this->date; 73 | } 74 | } 75 | 76 | 77 | # Client code example 78 | $editor = new Editor(); 79 | $editor->type('This is the first sentence.'); 80 | $editor->type('This is second.'); 81 | // make a snapshot 82 | $memento = $editor->save(); 83 | 84 | $editor->type('And this is third.'); 85 | echo $editor->getContent() . PHP_EOL; 86 | /* Output: This is the first sentence. This is second. And this is third. */ 87 | 88 | // restore the state from snapshot 89 | $editor->restore($memento); 90 | echo $editor->getContent() . PHP_EOL; 91 | /* Output: This is the first sentence. This is second. */ -------------------------------------------------------------------------------- /Behavioral/Mediator.php: -------------------------------------------------------------------------------- 1 | name}: $data->message\n"; 28 | } elseif ($sender instanceof Bot && $event == 'banUser') { 29 | echo "User {$data->name} was banned by {$sender->name}"; 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * Concrete components are not directly related. 36 | * There is only one channel of communication - by sending notifications to the mediator. 37 | */ 38 | class User 39 | { 40 | public $name; 41 | protected $mediator; 42 | 43 | public function __construct(string $name, Mediator $mediator) 44 | { 45 | $this->name = $name; 46 | $this->mediator = $mediator; 47 | } 48 | 49 | public function sendMessage(string $message) 50 | { 51 | $data = new \stdClass(); 52 | $data->message = $message; 53 | $this->mediator->notify($this, 'sendMessage', $data); 54 | } 55 | } 56 | 57 | class Bot 58 | { 59 | public $name = 'Bot'; 60 | protected $mediator; 61 | 62 | public function __construct(Mediator $mediator) 63 | { 64 | $this->mediator = $mediator; 65 | } 66 | 67 | public function banUser(User $user) 68 | { 69 | $this->mediator->notify($this, 'banUser', $user); 70 | } 71 | } 72 | 73 | # Client code example 74 | $chat = new ChatMediator(); 75 | 76 | $john = new User('John', $chat); 77 | $jane = new User('Jane', $chat); 78 | $bot = new Bot($chat); 79 | 80 | // every chat member interacts with mediator, 81 | // but not with with each other directly 82 | $john->sendMessage("Hi!"); 83 | $jane->sendMessage("What's up?"); 84 | $bot->banUser($john); 85 | 86 | /* Output: 87 | John: Hi! 88 | Jane: What's up? 89 | User John was banned by Bot */ -------------------------------------------------------------------------------- /Behavioral/Strategy.php: -------------------------------------------------------------------------------- 1 | sortStrategy = $sortStrategy; 56 | } 57 | 58 | /** 59 | * But also provides a setter to change strategy at runtime 60 | * @param SortStrategy $sortStrategy 61 | */ 62 | public function setStrategy(SortStrategy $sortStrategy) 63 | { 64 | $this->sortStrategy = $sortStrategy; 65 | } 66 | 67 | /** 68 | * Client delegates some work to the Strategy object instead of 69 | * implementing multiple versions of the algorithm on its own 70 | * @param array $data 71 | * @return array 72 | */ 73 | public function sortArray(array $data): array 74 | { 75 | return $this->sortStrategy->sort($data); 76 | } 77 | } 78 | 79 | # Client code example 80 | $data = [4, 2, 1, 5, 9]; 81 | 82 | // for small amount of data the "Bubble Sort" algorithm will be used 83 | // and for large amounts - the "Quick Sort" algorithm 84 | if (count($data) < 10) { 85 | $sorter = new Sorter(new BubbleSortStrategy()); 86 | $sorter->sortArray($data); 87 | } else { 88 | $sorter = new Sorter(new QuickSortStrategy()); 89 | $sorter->sortArray($data); 90 | } 91 | 92 | /* Output: Sorting using bubble sort.. */ 93 | 94 | 95 | -------------------------------------------------------------------------------- /Behavioral/Iterator.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 25 | } 26 | 27 | /** 28 | * Rewind the Iterator to the first element 29 | */ 30 | public function rewind() 31 | { 32 | $this->position = count($this->collection->getItems()) - 1; 33 | } 34 | 35 | /** 36 | * Return the current element 37 | */ 38 | public function current() 39 | { 40 | return $this->collection->getItems()[$this->position]; 41 | } 42 | 43 | /** 44 | * Return the key of the current element 45 | */ 46 | public function key() 47 | { 48 | return $this->position; 49 | } 50 | 51 | /** 52 | * Move forward to next element 53 | */ 54 | public function next() 55 | { 56 | $this->position = $this->position - 1; 57 | } 58 | 59 | /** 60 | * Checks if current position is valid 61 | */ 62 | public function valid() 63 | { 64 | return isset($this->collection->getItems()[$this->position]); 65 | } 66 | } 67 | 68 | /** 69 | * Concrete Collection provides one or several methods for working with collection 70 | * and also implements a built-in \IteratorAggregate interface which guarantee what we can fetch 'right' iterator 71 | */ 72 | class SimpleCollection implements \IteratorAggregate 73 | { 74 | private $items = []; 75 | 76 | public function addItem($item) 77 | { 78 | $this->items[] = $item; 79 | return $this; 80 | } 81 | 82 | public function getItems() 83 | { 84 | return $this->items; 85 | } 86 | 87 | public function getIterator(): \Iterator 88 | { 89 | return new ReverseIterator($this); 90 | } 91 | } 92 | 93 | 94 | # Client code example 95 | $collection = (new SimpleCollection())->addItem('1st item') 96 | ->addItem('2nd item') 97 | ->addItem('3rd item'); 98 | 99 | // Go through collection in reverse order 100 | foreach ($collection->getIterator() as $item) { 101 | echo $item . PHP_EOL; 102 | } 103 | 104 | /* Output: 105 | 3rd item 106 | 2nd item 107 | 1st item */ -------------------------------------------------------------------------------- /Creational/AbstractFactory.php: -------------------------------------------------------------------------------- 1 | {$title}'; 58 | } 59 | } 60 | 61 | class BladeHeader implements Header 62 | { 63 | public function render(): string 64 | { 65 | return '

{{ $title }}

'; 66 | } 67 | } 68 | 69 | /** 70 | * Another products family 71 | */ 72 | interface Body 73 | { 74 | public function render(): string; 75 | } 76 | 77 | class SmartyBody implements Body 78 | { 79 | public function render(): string 80 | { 81 | return '
{$content}
'; 82 | } 83 | } 84 | 85 | class BladeBody implements Body 86 | { 87 | public function render(): string 88 | { 89 | return '
{{ $content }}
'; 90 | } 91 | } 92 | 93 | # Client code example 94 | // the factory is selected based on the environment or configuration parameters 95 | $templateEngine = 'blade'; 96 | switch ($templateEngine) { 97 | case 'smarty': 98 | $templateFactory = new SmartyTemplateFactory(); 99 | break; 100 | case 'blade': 101 | $templateFactory = new BladeTemplateFactory(); 102 | break; 103 | } 104 | 105 | // we will have header and body as either Smarty or Blade template, but never mixed 106 | echo $templateFactory->createHeader()->render(); 107 | echo $templateFactory->createBody()->render(); 108 | /* Output:

{{ $title }}

{{ $content }}
*/ -------------------------------------------------------------------------------- /Creational/PrototypeExt.php: -------------------------------------------------------------------------------- 1 | title = $title; 22 | $this->body = $body; 23 | $this->author = $author; 24 | $this->author->addToPage($this); 25 | $this->date = new DateTime(); 26 | } 27 | 28 | public function addComment($comment) 29 | { 30 | $this->comments[] = $comment; 31 | } 32 | 33 | /** 34 | * Magic method creates a copy of an object. 35 | * Here we can control what data should be copied to the cloned object 36 | */ 37 | public function __clone() 38 | { 39 | $this->title = $this->title . '(copy)'; 40 | $this->author->addToPage($this); 41 | $this->comments = []; 42 | $this->date = new \DateTime(); 43 | } 44 | 45 | public function render(): string 46 | { 47 | return "Title: {$this->title}\n" 48 | . "Body: {$this->body}\n" 49 | . "Author: {$this->author->name} Date: {$this->date->format('Y-m-d')}\n" 50 | . "Comments: " . implode(', ', $this->comments) . "\n"; 51 | } 52 | 53 | public function __toString() 54 | { 55 | return $this->title; 56 | } 57 | } 58 | 59 | class Author 60 | { 61 | public $name; 62 | /** @var Page[] */ 63 | private $pages = []; 64 | 65 | public function __construct($name) 66 | { 67 | $this->name = $name; 68 | } 69 | 70 | public function addToPage(Page $page) 71 | { 72 | $this->pages[] = $page; 73 | } 74 | 75 | public function getPages(): string 76 | { 77 | return 'Pages: ' . implode(', ', $this->pages); 78 | } 79 | } 80 | 81 | # Client code example 82 | // Example shows how to clone a complex Page object using the Prototype pattern 83 | $author = new Author('John Doe'); 84 | $page = new Page('Article', 'Some text.', $author); 85 | $page->addComment('1st comment'); 86 | echo $page->render(); 87 | /* Output: 88 | Title: Article 89 | Body: Some text. 90 | Author: John Doe Date: 2018-10-01 91 | Comments: 1st comment */ 92 | 93 | // Prototype pattern is available in PHP out of the box, 94 | // we can use the `clone` keyword to create an exact copy of an object 95 | $pageCopy = clone $page; 96 | echo $pageCopy->render(); 97 | /* Output: 98 | Title: Article(copy) 99 | Body: Some text. 100 | Author: John Doe Date: 2018-10-01 101 | Comments: */ 102 | 103 | echo $author->getPages(); 104 | /* Output: Pages: Article, Article(copy) */ -------------------------------------------------------------------------------- /Structural/Composite.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 12 | } 13 | 14 | public function getParent(): CartItem 15 | { 16 | return $this->parent; 17 | } 18 | 19 | public function add(CartItem $cartItem) 20 | { 21 | } 22 | 23 | public function remove(CartItem $cartItem) 24 | { 25 | } 26 | 27 | public function isComposite(): bool 28 | { 29 | return false; 30 | } 31 | 32 | abstract public function getPrice(): float; 33 | } 34 | 35 | class Product extends CartItem 36 | { 37 | protected $name; 38 | protected $price; 39 | 40 | public function __construct(string $name, float $price) 41 | { 42 | $this->name = $name; 43 | $this->price = $price; 44 | } 45 | 46 | public function getPrice(): float 47 | { 48 | return $this->price; 49 | } 50 | } 51 | 52 | class CompositeProduct extends CartItem 53 | { 54 | protected $name; 55 | 56 | /** @var CartItem[] */ 57 | protected $children = []; 58 | 59 | public function __construct(string $name) 60 | { 61 | $this->name = $name; 62 | } 63 | 64 | public function add(CartItem $cartItem) 65 | { 66 | if (in_array($cartItem, $this->children, true)) { 67 | return; 68 | } 69 | $this->children[] = $cartItem; 70 | $cartItem->setParent($this); 71 | } 72 | 73 | public function remove(CartItem $cartItem) 74 | { 75 | $this->children = array_filter($this->children, function ($child) use ($cartItem) { 76 | return $child == $cartItem; 77 | }); 78 | $cartItem->setParent(null); 79 | } 80 | 81 | public function getPrice(): float 82 | { 83 | $totalPrice = 0; 84 | foreach ($this->children as $child) { 85 | $totalPrice += $child->getPrice(); 86 | } 87 | 88 | return $totalPrice; 89 | } 90 | 91 | public function isComposite(): bool 92 | { 93 | return true; 94 | } 95 | } 96 | 97 | # Client code example 98 | $shoppingCart[] = new Product('Bike', 200); 99 | 100 | $motorcycle = new CompositeProduct('Motorcycle'); 101 | $motorcycle->add(new Product('Motor', 700)); 102 | $motorcycle->add(new Product('Wheels', 300)); 103 | 104 | $frame = new CompositeProduct('Frame'); 105 | $frame->add(new Product('Steering', 200.00)); 106 | $frame->add(new Product('Seat', 100)); 107 | $motorcycle->add($frame); 108 | $shoppingCart[] = $motorcycle; 109 | 110 | 111 | // calculate a total price of shopping cart 112 | $totalPrice = 0; 113 | foreach ($shoppingCart as $cartItem) { 114 | /** @var CartItem $cartItem */ 115 | $totalPrice += $cartItem->getPrice(); 116 | } 117 | echo $totalPrice; // Output: 1500 -------------------------------------------------------------------------------- /Behavioral/Observer.php: -------------------------------------------------------------------------------- 1 | observers[] = $observer; 29 | } 30 | 31 | /** 32 | * Detach an observer 33 | * @param SplObserver $observer 34 | */ 35 | public function detach(SplObserver $observer) 36 | { 37 | foreach ($this->observers as $key => $obs) { 38 | if ($obs === $observer) { 39 | unset($this->observers[$key]); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Notify an observer 46 | */ 47 | public function notify() 48 | { 49 | foreach ($this->observers as $observer) { 50 | $observer->update($this); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Shopping cart owns some important state and notifies observers when the state changes. 57 | */ 58 | class Cart implements SplSubject 59 | { 60 | use Observable; 61 | 62 | /** 63 | * Some business logic state 64 | * @var int 65 | */ 66 | protected $balance = 0; 67 | 68 | // Business logic 69 | 70 | /** 71 | * Changes state and notifies all subscribers about it 72 | * @param int $balance 73 | */ 74 | public function setBalance(int $balance) 75 | { 76 | if ($this->balance !== $balance) { 77 | $this->balance = $balance; 78 | $this->notify(); 79 | } 80 | } 81 | 82 | /** 83 | * Return current state 84 | */ 85 | public function getBalance() 86 | { 87 | return $this->balance; 88 | } 89 | } 90 | 91 | /** 92 | * Concrete observers react to the updates generated by the subject they had been attached to 93 | */ 94 | class LoggingListener implements SplObserver 95 | { 96 | /** 97 | * Receive update from subject 98 | * @param SplSubject $subject 99 | */ 100 | public function update(SplSubject $subject) 101 | { 102 | if (!$subject instanceof Cart) { 103 | return; 104 | } 105 | 106 | echo 'Notification: balance of the shopping cart was changed to ' . $subject->getBalance() . PHP_EOL; 107 | } 108 | } 109 | 110 | # Client code example 111 | $cart = new Cart(); // subject 112 | $cart->attach(new LoggingListener()); // attach an Observer 113 | $cart->setBalance(10); // trigger an event 114 | 115 | /* Output: 116 | Notification: balance of the shopping cart was changed to 10 */ -------------------------------------------------------------------------------- /Behavioral/CommandExt.php: -------------------------------------------------------------------------------- 1 | receiver = $receiver; 21 | $this->params = $params; 22 | } 23 | 24 | /** 25 | * We should have possibility to execute command and rollback if necessary 26 | */ 27 | public abstract function execute(); 28 | 29 | public abstract function rollback(); 30 | } 31 | 32 | /** 33 | * Concrete command, doesn't do all the work by self and only passes the call to the receiver 34 | */ 35 | class TurnOnCommand extends CommandExtended 36 | { 37 | public function execute() 38 | { 39 | $this->receiver->turnOn($this->params); 40 | } 41 | 42 | public function rollback() 43 | { 44 | $this->receiver->turnoff($this->params); 45 | } 46 | } 47 | 48 | /* 49 | * Invoker of commands 50 | */ 51 | 52 | class Invoker 53 | { 54 | /** 55 | * @var []Command Queue of commands 56 | */ 57 | protected $commands = []; 58 | 59 | public function pushCommand(CommandExtended $command) 60 | { 61 | return array_push($this->commands, $command); 62 | } 63 | 64 | /** 65 | * Executes last command 66 | */ 67 | public function executeCommand() 68 | { 69 | /** @var CommandExtended $lastCommand */ 70 | if ($lastCommand = array_pop($this->commands)) { 71 | return $lastCommand->execute(); 72 | } 73 | 74 | return false; 75 | } 76 | 77 | /** 78 | * Rollbacks last command 79 | */ 80 | public function rollbackCommand() 81 | { 82 | /** @var CommandExtended $lastCommand */ 83 | if ($lastCommand = array_pop($this->commands)) { 84 | return $lastCommand->rollback(); 85 | } 86 | 87 | return false; 88 | } 89 | } 90 | 91 | /** 92 | * Receiver of commands, contains some business logic 93 | */ 94 | class Receiver 95 | { 96 | public function turnOn($params) 97 | { 98 | echo "Receiver: Turning on something with params: " . implode(', ', $params) . PHP_EOL; 99 | } 100 | 101 | public function turnOff($params) 102 | { 103 | echo "Receiver: Turning off something with params: " . implode(', ', $params) . PHP_EOL; 104 | } 105 | } 106 | 107 | # Client code example 108 | $invoker = new Invoker(); 109 | $receiver = new Receiver(); 110 | 111 | $invoker->pushCommand(new TurnOnCommand($receiver, 'some_param')); 112 | $invoker->executeCommand(); 113 | $invoker->pushCommand(new TurnOnCommand($receiver, 'kill', -9)); 114 | $invoker->rollbackCommand(); 115 | 116 | /* Output: 117 | Receiver: Turning on something with params: some_param 118 | Receiver: Turning off something with params: kill, -9 */ -------------------------------------------------------------------------------- /Structural/Flyweight.php: -------------------------------------------------------------------------------- 1 | products[] = ProductFactory::getProduct($title, $price, $brandName, $brandLogo); 21 | } 22 | 23 | public function getProducts() 24 | { 25 | return $this->products; 26 | } 27 | } 28 | 29 | /** 30 | * Contains main part of product state, 31 | * brandName and brandLogo will be shared between many products 32 | */ 33 | class Product 34 | { 35 | protected $title; 36 | protected $price; 37 | /** @var ProductBrand */ 38 | protected $brand; 39 | 40 | public function __construct($title, $price, ProductBrand $brand) 41 | { 42 | $this->title = $title; 43 | $this->price = $price; 44 | $this->brand = $brand; 45 | } 46 | } 47 | 48 | /** 49 | * This Flyweight class contains a portion of a state of a product. 50 | */ 51 | class ProductBrand 52 | { 53 | private $brandName; 54 | private $brandLogo; 55 | 56 | public function __construct($brandName, $brandLogo) 57 | { 58 | $this->brandName = $brandName; 59 | $this->brandLogo = $brandLogo; 60 | } 61 | } 62 | 63 | /** 64 | * Factory decides when to create a new object, 65 | * and when it is possible to do with the existing one. 66 | * It saves memory (especially when we need to store 67 | * a large list of products in memory) 68 | */ 69 | class ProductFactory 70 | { 71 | public static $brandTypes = []; 72 | 73 | /** 74 | * Returns new Product instance 75 | */ 76 | public static function getProduct($title, $price, $brandName, $brandLogo): Product 77 | { 78 | $brand = static::getBrand($brandName, $brandLogo); 79 | 80 | return new Product($title, $price, $brand); 81 | } 82 | 83 | /** 84 | * Checks if we already have brand instance or create a new one if not 85 | */ 86 | protected static function getBrand($brandName, $brandLogo): ProductBrand 87 | { 88 | if (isset(static::$brandTypes[$brandName])) { 89 | return static::$brandTypes[$brandName]; 90 | } 91 | 92 | return static::$brandTypes[$brandName] = new ProductBrand($brandName, $brandLogo); 93 | } 94 | } 95 | 96 | # Client code example 97 | $shoppingCart = new ShoppingCart(); 98 | $shoppingCart->addProduct('Sports shoes', 120, 'Nike', 'Nike.png'); 99 | $shoppingCart->addProduct('Kids shoes', 100, 'Nike', 'Nike.png'); 100 | $shoppingCart->addProduct('Women shoes', 110, 'Nike', 'Nike.png'); 101 | $shoppingCart->addProduct('Running shoes', 140, 'Asics', 'Asics.jpg'); 102 | $shoppingCart->addProduct('Everyday shoes', 90, 'Adidas', 'Adidas.svg'); 103 | 104 | echo count($shoppingCart->getProducts()); // 5 products in basket 105 | echo count(ProductFactory::$brandTypes); // and only 3 unique brands instances in memory -------------------------------------------------------------------------------- /Behavioral/IteratorExt.php: -------------------------------------------------------------------------------- 1 | file = fopen($filePath, 'rb'); 47 | $this->delimiter = $delimiter; 48 | } 49 | 50 | /** 51 | * Resets the file pointer 52 | */ 53 | public function rewind() 54 | { 55 | rewind($this->file); 56 | $this->rowCounter = 0; 57 | } 58 | 59 | /** 60 | * Returns the current CSV row 61 | * @return array The current CSV row as a 2-dimensional array 62 | */ 63 | public function current() 64 | { 65 | $this->currentRow = fgetcsv($this->file, 4096, $this->delimiter); 66 | $this->rowCounter++; 67 | 68 | return $this->currentRow; 69 | } 70 | 71 | /** 72 | * Returns the current row number 73 | * @return int The current row number 74 | */ 75 | public function key() 76 | { 77 | return $this->rowCounter; 78 | } 79 | 80 | /** 81 | * Checks if the end of file has been reached 82 | * @return boolean Returns true on EOF reached, false otherwise 83 | */ 84 | public function next() 85 | { 86 | if (is_resource($this->file)) { 87 | return !feof($this->file); 88 | } 89 | 90 | return false; 91 | } 92 | 93 | /** 94 | * This method checks if the next row is a valid row 95 | * @return boolean If the next row is a valid row 96 | */ 97 | public function valid() 98 | { 99 | if (!$this->next()) { 100 | if (is_resource($this->file)) { 101 | fclose($this->file); 102 | } 103 | 104 | return false; 105 | } 106 | 107 | return true; 108 | } 109 | } 110 | 111 | 112 | # Client code example 113 | // some CSV data 114 | $csvContent = << $row) { 129 | echo $key . ':' . implode(', ', $row) . PHP_EOL; 130 | } 131 | } catch (\Exception $e) { 132 | echo $e->getMessage(); 133 | } 134 | 135 | /* Output: 136 | 1:Name, Value, isActive 137 | 2:First, 10, true 138 | 3:Second, 20, false 139 | 4:Third, 30, true */ -------------------------------------------------------------------------------- /Behavioral/ChainOfResponsibility.php: -------------------------------------------------------------------------------- 1 | nextHandler = $handler; 33 | 34 | // it will let us link handlers in a convenient way like: $handler->setNext()->setNext() 35 | return $handler; 36 | } 37 | 38 | /** 39 | * Calls next handler if present 40 | * @param $message 41 | * @return bool 42 | */ 43 | public function handle($message) 44 | { 45 | if ($this->nextHandler) { 46 | return $this->nextHandler->handle($message); 47 | } 48 | 49 | return false; 50 | } 51 | } 52 | 53 | /** 54 | * Logs message to database if possible and execute next handler 55 | */ 56 | class DBLogger extends AbstractLogger 57 | { 58 | /** 59 | * Stub method 60 | * @return bool 61 | */ 62 | public function canSave() 63 | { 64 | // you can play with true/false value 65 | return false; 66 | } 67 | 68 | public function handle($message) 69 | { 70 | if ($this->canSave()) { 71 | echo "Save message to database..\n"; 72 | } 73 | 74 | return parent::handle($message); 75 | } 76 | } 77 | 78 | /** 79 | * Sends message by mail if possible and executes next handler 80 | */ 81 | class MailLogger extends AbstractLogger 82 | { 83 | /** 84 | * Stub method 85 | * @return bool 86 | */ 87 | public function canMail() 88 | { 89 | return true; 90 | } 91 | 92 | public function handle($message) 93 | { 94 | if ($this->canMail()) { 95 | echo 'Send message by email..' . PHP_EOL; 96 | } 97 | 98 | return parent::handle($message); 99 | } 100 | } 101 | 102 | /** 103 | * Logs message to log file if possible and executes next handler if present 104 | */ 105 | class FileLogger extends AbstractLogger 106 | { 107 | /** 108 | * Stub method 109 | * @return bool 110 | */ 111 | public function canWrite() 112 | { 113 | return true; 114 | } 115 | 116 | public function handle($message) 117 | { 118 | if ($this->canWrite()) { 119 | echo "Save message to log file..\n"; 120 | } 121 | 122 | return parent::handle($message); 123 | } 124 | } 125 | 126 | # Client code example 127 | 128 | /** 129 | * Simple logger 130 | */ 131 | class Logger 132 | { 133 | public static function log($message) 134 | { 135 | // build the chain 136 | $logger = new DBLogger(); 137 | $logger->setNext(new MailLogger()) 138 | ->setNext(new FileLogger()); 139 | 140 | // call the first handler 141 | $logger->handle($message); 142 | } 143 | } 144 | 145 | Logger::log('Message text'); 146 | /* Output: 147 | Send message by email.. 148 | Save message to log file.. */ -------------------------------------------------------------------------------- /Behavioral/Visitor.php: -------------------------------------------------------------------------------- 1 | header . $template->title . $template->content . $template->footer); 25 | } 26 | 27 | public function visitReport(Report $report) 28 | { 29 | return json_encode($report->title . $report->diagram . $report->content); 30 | } 31 | } 32 | 33 | /** 34 | * Concrete visitor, extends documents by export function in XML. 35 | * Contains implementation of XML export for Template and Report classes 36 | */ 37 | class XMLExportVisitor implements Visitor 38 | { 39 | 40 | public function visitTemplate(Template $template) 41 | { 42 | $xml = new \SimpleXMLElement(''); 43 | $xml->addChild('header', $template->header) 44 | ->addChild('title', $template->title) 45 | ->addChild('content', $template->content) 46 | ->addChild('footer', $template->footer); 47 | 48 | return $xml->asXML(); 49 | } 50 | 51 | public function visitReport(Report $report) 52 | { 53 | $xml = new \SimpleXMLElement(''); 54 | $xml->addChild('title', $report->title) 55 | ->addChild('diagram', $report->diagram) 56 | ->addChild('content', $report->content); 57 | 58 | return $xml->asXML(); 59 | } 60 | } 61 | 62 | /** 63 | * Declares an accept() method that takes the base visitor interface as an argument 64 | */ 65 | interface Visitable 66 | { 67 | public function accept(Visitor $visitor); 68 | } 69 | 70 | /** 71 | * Base class for all documents. 72 | * Document hierarchy should only know about the basic interface of visitors 73 | */ 74 | abstract class Document implements Visitable 75 | { 76 | public $title; 77 | public $content; 78 | 79 | public function __construct(string $title, string $content) 80 | { 81 | $this->title = $title; 82 | $this->content = $content; 83 | } 84 | } 85 | 86 | /** 87 | * Concrete documents should implement the accept() method in such a way 88 | * that it calls the visitor's method corresponding to the document's class 89 | */ 90 | class Template extends Document 91 | { 92 | public $header = '{header}'; 93 | public $footer = '{footer}'; 94 | 95 | public function accept(Visitor $visitor) 96 | { 97 | return $visitor->visitTemplate($this); 98 | } 99 | } 100 | 101 | class Report extends Document 102 | { 103 | public $diagram = ' {diagram} '; 104 | 105 | public function accept(Visitor $visitor) 106 | { 107 | return $visitor->visitReport($this); 108 | } 109 | } 110 | 111 | # Client code example 112 | $report = new Report('report_title', 'report_content'); 113 | $template = new Template('template_title', ' template_content'); 114 | 115 | echo $report->accept(new JSONExportVisitor()) . PHP_EOL; 116 | echo $report->accept(new XMLExportVisitor()) . PHP_EOL; 117 | echo $template->accept(new JSONExportVisitor()) . PHP_EOL; 118 | 119 | /* Output: 120 | "report_title {diagram} report_content" 121 | report_title<diagram>.. 122 | "{header}template_title template_content{footer}" */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns Cookbook 2 | Коллекция самых популярных Шаблонов Проектирования (Design Patterns) с описанием и примерами на PHP. 3 | 4 | 5 | ## 1. Порождающие шаблоны (Creational Patterns) 6 | 7 | ## Одиночка (Singleton) 8 | #### Суть паттерна 9 | Гарантирует, что существует только один объект данного типа в приложении, и предоставляет к нему глобальную точку доступа. 10 | 11 | #### Какие проблемы решает 12 | * Гарантирует наличие единственного экземпляра класса 13 | * Предоставляет глобальную точку доступа 14 | 15 | #### Пример 16 | ```php 17 | // there is the only one way to get an application instance 18 | $app = Application::getInstance(); 19 | // every call will give us the same instance 20 | assert(Application::getInstance() === $app); 21 | 22 | /* Next calls will produce errors: 23 | $app = new Application(); 24 | $app = clone $app; 25 | $app = unserialize(serialize($app)); */ 26 | ``` 27 | [Полный пример](Creational/Singleton.php) 28 | 29 | 30 | ## Прототип (Prototype) 31 | #### Суть паттерна 32 | Паттерн описывает процесс создания объектов-клонов на основе имеющегося объекта-прототипа. Другими словами, паттерн описывает способ организации процесса клонирования. Обычно операция клонирования происходить через метод `getClone()`, который описан в базовом классе. 33 | 34 | В PHP возможность клонирования объектов [встроена](http://php.net/manual/en/language.oop5.cloning.php), при помощи ключевого слова `clone` вы можете сделать точную копию объекта. Чтобы добавить поддержку клонирования в класс, необходимо реализовать метод `__clone()`. 35 | 36 | #### Какие проблемы решает 37 | * Позволяет копировать объекты, не вдаваясь в подробности их реализации 38 | 39 | #### Пример 40 | ```php 41 | class Page 42 | { 43 | public function __construct(string $title) 44 | { 45 | $this->title = $title; 46 | } 47 | 48 | // cloning method, each object should implement how he will be cloned himself 49 | public function getClone() 50 | { 51 | return new static($this->title); 52 | } 53 | } 54 | 55 | $page = new Page('Page Title'); 56 | echo $pageClone = $page->getClone(); // Page Title 57 | ``` 58 | [Полный пример](Creational/Prototype.php) | [Дополнительный пример](Creational/PrototypeExt.php) 59 | 60 | 61 | ## Строитель (Builder) 62 | #### Суть паттерна 63 | Позволяет создавать сложные объекты пошагово, а также дает возможность использовать один и тот же код строительства для получения разных представлений объектов. 64 | 65 | #### Какие проблемы решает 66 | * Определяет процесс поэтапного построения сложного продукта 67 | * Позволяет избавиться от "телескопических" конструкторов `__construct($param1, $param2, ..., $paramN)` 68 | 69 | #### Пример 70 | ```php 71 | $page = (new PageBuilder('<h1>Home page</h1>')) 72 | ->addHeader('<header></header>') 73 | ->addContent('<article>content</article>'); 74 | 75 | // some time letter ... 76 | $page->addFooter('<footer></footer>'); 77 | 78 | echo $page->build()->show(); 79 | /* Output: 80 | <h1>Home page</h1><header></header><article>content</article><footer></footer> */ 81 | ``` 82 | [Полный пример](Creational/Builder.php) | [Дополнительный пример](Creational/BuilderExt.php) 83 | 84 | 85 | ## Фабричный метод (Factory Method) 86 | #### Суть паттерна 87 | Определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов. 88 | Предлагает создавать объекты не напрямую, используя оператор `new`, а через вызов особого фабричного метода. Объекты все равно будут создаваться при помощи new, но делать это будет фабричный метод. Чтобы эта система заработала, все возвращаемые объекты должны иметь общий интерфейс. 89 | 90 | #### Какие проблемы решает 91 | * Позволяет использовать наследование и полиморфизм, чтобы инкапсулировать создание конкретных экземпляров 92 | * Решает проблему создания объектов, без указания конкретных классов 93 | * Применяется когда система должна оставаться легко расширяемой, путем добавления объектов новых типов 94 | 95 | #### Пример 96 | ```php 97 | abstract class Response {} 98 | class JSONResponse extends Response {} 99 | class HTMLResponse extends Response {} 100 | 101 | interface Formatter {} 102 | class HTMLFormatter implements Formatter {} 103 | class JSONFormatter implements Formatter {} 104 | 105 | // type can be taken from configuration for instance, 106 | // JsonResponse will use JSONFormatter (see full example) 107 | echo $response = new JsonResponse('some input data'); 108 | 109 | /* Output: {"code": 200, "response": "some input data"} */ 110 | ``` 111 | [Полный пример](Creational/FactoryMethod.php) 112 | 113 | 114 | ## Абстрактная фабрика (Abstract Factory) 115 | #### Суть паттерна 116 | Позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов. Или другими словами - предусматривает интерфейс для создания семейства связанных или зависимых объектов без указания конкретных классов. 117 | Обычно программа создает конкретный объект фабрики при запуске, причем тип фабрики выбирается, исходя из параметров окружения или конфигурации. 118 | 119 | #### Какие проблемы решает 120 | * Скрывает от клиентского кода подробности того, как и какие конкретно объекты будут созданы 121 | * Решает проблему несовместимости набора связанных объектов, при их создании 122 | 123 | #### Пример 124 | ```php 125 | // the factory is selected based on the environment or configuration parameters 126 | $templateEngine = 'blade'; 127 | switch ($templateEngine) { 128 | case 'smarty': 129 | $templateFactory = new SmartyTemplateFactory(); 130 | break; 131 | case 'blade': 132 | $templateFactory = new BladeTemplateFactory(); 133 | break; 134 | } 135 | 136 | // we will have header and body as either Smarty or Blade template, but never mixed 137 | echo $templateFactory->createHeader()->render(); 138 | echo $templateFactory->createBody()->render(); 139 | /* Output: <h1>{{ $title }}</h1><main>{{ $content }}</main> */ 140 | ``` 141 | [Полный пример](Creational/AbstractFactory.php) 142 | 143 | 144 | ## 2. Структурные шаблоны (Structural Patterns) 145 | 146 | ## Адаптер (Adapter) 147 | #### Суть паттерна 148 | Адаптирует существующий код к требуемому интерфейсу (является переходником). Например, у вас есть класс и его интерфейс не совместим с кодом вашей системы, в этом случае мы не изменяем код этого класса, а пишем для него адаптер - "оборачиваем" объект так, чтобы хранить ссылку на него, и непосредственно конвертируем интерфейс объекта к требуемому. 149 | Мы можем создавать адаптер в любом направлении, как для какой-то старой системы чтобы использовать ее функционал с новым интерфейсом, так и любой новый интерфейс в соответствии с тем что ожидает уже существующий объект. 150 | 151 | #### Какие проблемы решает 152 | * Позволяет использовать сторонний класс, если его интерфейс не совместим с существующим кодом 153 | * Когда нужно использовать несколько существующих подклассов, но в них не хватает какой-то общей функциональности (и расширить суперкласс мы не можем) 154 | 155 | #### Пример 156 | ```php 157 | $book = new Book(); 158 | $book->open(); 159 | $book->turnPage(); 160 | 161 | // transform Kindle e-book to the 'simple book' interface 162 | $book = new KindleAdapter(new Kindle()); 163 | echo $book->open(); 164 | echo $book->turnPage(); 165 | 166 | /* Output: 167 | Open the book.. 168 | Go to the next page.. 169 | 170 | Turn on the Kindle.. 171 | Press next button on Kindle.. */ 172 | ``` 173 | [Полный пример](Structural/Adapter.php) 174 | 175 | 176 | ## Декоратор (Decorator) 177 | #### Суть паттерна 178 | Позволяет динамически добавлять объектам новую функциональность, не изменяя их интерфейс. 179 | 180 | #### Какие проблемы решает 181 | * Помогает расширить класс каким-то определенным действием, не изменяя интерфейс 182 | * Позволяет добавлять другие декораторы "в цепочке" 183 | * Применяется когда нельзя расширить обязанности объекта с помощью наследования 184 | 185 | #### Пример 186 | ```php 187 | $coffee = new SimpleCoffee(); 188 | // apply the Decorator for the $coffee object 189 | $milkCoffee = new MilkCoffee($coffee); 190 | // we also can use chain calls of Decorators 191 | $vanillaMilkCoffee = new VanillaCoffee(new MilkCoffee(new SimpleCoffee())); 192 | 193 | /** Output: 194 | Coffee 195 | Coffee, with milk 196 | Coffee, with milk, with vanilla */ 197 | ``` 198 | [Полный пример](Structural/Decorator.php) 199 | 200 | 201 | ## Фасад (Facade) 202 | #### Суть паттерна 203 | Предоставляет упрощенный интерфейс к сложной системе вызовов или системе классов. 204 | 205 | #### Какие проблемы решает 206 | * Представляет простой или урезанный интерфейс к сложной подсистеме 207 | * Помогает произвести декомпозицию сложной подсистемы на "подсистемы" 208 | 209 | #### Пример 210 | ```php 211 | class SignUpFacade 212 | { 213 | public function signUpUser($userName, $userPass, $userMail) 214 | { 215 | $this->validator->isValidMail($userMail); 216 | $this->userService->create($userName, $userPass, $userMail); 217 | $this->mailService->to($userMail)->subject('Welcome')->send(); 218 | } 219 | } 220 | 221 | // we make an abstraction above user registration process 222 | $facade = new SignUpFacade(); 223 | $facade->signUpUser('Sergey', '123456', 'test@mail.com'); 224 | ``` 225 | [Полный пример](Structural/Facade.php) 226 | 227 | 228 | ## Компоновщик (Composite) 229 | #### Суть паттерна 230 | Позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней так, как будто это единичный объект. Паттерн предлагает хранить в составных объектах ссылки на другие простые или составные объекты. Те, в свою очередь, тоже могут хранить свои вложенные объекты и так далее. 231 | 232 | Клиентский код работает со всеми объектами через общий интерфейс и не знает, что перед ним — простой или составной объект. Это позволяет клиентскому коду работать с деревьями объектов любой сложности, не привязываясь к конкретным классам объектов, формирующих дерево. 233 | 234 | #### Какие проблемы решает 235 | * Упрощает работу с любыми древовидными рекурсивными структурами 236 | * Позволяет единообразно трактовать простые и составные объекты 237 | 238 | #### Пример 239 | ```php 240 | $shoppingCart[] = new Product('Bike', 200); 241 | 242 | $motorcycle = new CompositeProduct('Motorcycle'); 243 | $motorcycle->add(new Product('Motor', 700)); 244 | $motorcycle->add(new Product('Wheels', 300)); 245 | 246 | $frame = new CompositeProduct('Frame'); 247 | $frame->add(new Product('Steering', 200.00)); 248 | $frame->add(new Product('Seat', 100)); 249 | $motorcycle->add($frame); 250 | 251 | $shoppingCart[] = $motorcycle; 252 | 253 | // calculate a total price of shopping cart 254 | $totalPrice = 0; 255 | foreach ($shoppingCart as $cartItem) { 256 | $totalPrice += $cartItem->getPrice(); 257 | } 258 | echo $totalPrice; // Output: 1500 259 | ``` 260 | [Полный пример](Structural/Composite.php) 261 | 262 | 263 | ## Мост (Bridge) 264 | #### Суть паттерна 265 | Разделяет один или несколько классов на две отдельные иерархии — абстракцию и реализацию, позволяя изменять их независимо друг от друга. 266 | 267 | Паттерн предлагает заменить наследование делегированием. Когда класс нужно расширять в двух независимых плоскостях, паттерн предлагает выделить одну из таких плоскостей в отдельную иерархию классов, храня ссылку на один из ее объектов в первоначальном классе. 268 | 269 | #### Какие проблемы решает 270 | * Полезен в ситуации, когда класс нужно расширять в двух независимых плоскостях 271 | * Позволяет разделить монолитный класс, который содержит несколько различных реализаций на более специализированные реализации 272 | * Сохраняет возможность подмены реализации во время выполнения программы 273 | 274 | #### Пример 275 | ```php 276 | // some 'abstraction' hierarchy 277 | abstract class WebPage 278 | { 279 | // ... 280 | public function __construct(Theme $theme) 281 | { 282 | $this->theme = $theme; 283 | } 284 | } 285 | class HomePage extends WebPage {} 286 | class AboutPage extends WebPage {} 287 | 288 | // separate 'implementation' hierarchy 289 | interface Theme {} 290 | class DarkTheme implements Theme {} 291 | class LightTheme implements Theme {} 292 | 293 | $homePage = new HomePage(new DarkTheme()); 294 | echo $homePage->getContent(); // Output: Home page in Dark colors 295 | 296 | $lightTheme = new LightTheme(); 297 | $aboutPage = new AboutPage($lightTheme); 298 | echo $aboutPage->getContent(); // Output: About page in White colors 299 | ``` 300 | [Полный пример](Structural/Bridge.php) 301 | 302 | 303 | ## Заместитель (Proxy) 304 | #### Суть паттерна 305 | Позволяет подставлять вместо реальных объектов специальные объекты "заменители". Эти объекты перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова методов оригинала. 306 | 307 | Заместитель предлагает создать новый класс "дублер", имеющий тот же интерфейс, что и оригинальный объект. Мы можем поместить в класс заместителя какую-то промежуточную логику, которая будет выполняться до или после вызовов этих же методов в настоящем объекте. А благодаря одинаковому интерфейсу, объект-заместитель можно передать в любой код, ожидающий оригинальный объект. 308 | 309 | #### Какие проблемы решает 310 | * Полезен в реализации "ленивой загрузки" 311 | * Полезен при добавлении дополнительного поведения (проверок, логирования, кэширования и т.д.), сохраняет при этом интерфейс оригинального объекта 312 | 313 | #### Пример 314 | ```php 315 | class WeatherProxy implements WeatherClient 316 | { 317 | // ... 318 | public function getWeather(string $location): string 319 | { 320 | if (!isset($this->cache[$location])) { 321 | echo "cache: MISS\n"; 322 | $this->cache[$location] = $this->client->getWeather($location); 323 | } else { 324 | echo "cache: HIT\n"; 325 | } 326 | 327 | return $this->cache[$location]; 328 | } 329 | } 330 | 331 | # Client code example 332 | $weather = new Weather('177b4a1be7dfd10e0d30e8fdeabe0ea9'); 333 | $proxy = new WeatherProxy($weather); 334 | echo $proxy->getWeather('Kiev'); 335 | echo $proxy->getWeather('Lviv'); 336 | echo $proxy->getWeather('Kiev'); 337 | 338 | /* Output example: 339 | cache: MISS, weather: clear sky 340 | cache: MISS, weather: scattered clouds 341 | cache: HIT, weather: clear sky */ 342 | ``` 343 | [Полный пример](Structural/Proxy.php) 344 | 345 | 346 | ## Легковес (Flyweight) 347 | #### Суть паттерна 348 | Экономит память, разделяя общее состояние объектов между собой, вместо хранения одинаковых данных в каждом объекте. 349 | 350 | Легковес применяется в программе, имеющей большое количество одинаковых объектов. Паттерн разделяет данные этих объектов на две части — "легковесы" и "контексты". Теперь, вместо хранения повторяющихся данных во всех объектах, отдельные объекты будут ссылаться на несколько общих объектов, хранящих эти данные. Клиент работает через фабрику, которая скрывает от него сложность организации общих данных. 351 | 352 | > Реальное применение паттерна на PHP встречается довольно редко. Это связано с однопоточным характером PHP, где вы не должны хранить ВСЕ объекты вашего приложения в памяти одновременно в одном потоке 353 | 354 | #### Какие проблемы решает 355 | * Позволяет вместить большее количество объектов в отведенную оперативную память 356 | 357 | #### Пример 358 | ```php 359 | $shoppingCart = new ShoppingCart(); 360 | $shoppingCart->addProduct('Sports shoes', 120, 'Nike'); 361 | $shoppingCart->addProduct('Kids shoes', 100, 'Nike'); 362 | $shoppingCart->addProduct('Women shoes', 110, 'Nike'); 363 | $shoppingCart->addProduct('Running shoes', 140, 'Asics'); 364 | $shoppingCart->addProduct('Everyday shoes', 90, 'Adidas'); 365 | 366 | echo count($shoppingCart->getProducts()); // 5 products in basket 367 | echo count(ProductFactory::$brandTypes); // and only 3 unique brands instances in memory 368 | ``` 369 | [Полный пример](Structural/Flyweight.php) 370 | 371 | 372 | ## 3. Поведенческие шаблоны (Behavioral Patterns) 373 | 374 | ## Цепочка обязанностей (Chain of Responsibility) 375 | #### Суть паттерна 376 | Позволяет передавать запросы/вызовы последовательно по цепочке обработчиков. Каждый последующий обработчик решает, может ли он обработать запрос сам и стоит ли передавать запрос дальше по цепочке. 377 | Паттерн предлагает связать объекты обработчиков в одну цепь. Каждый из них будет иметь ссылку на следующий обработчик в цепи и сможет не только сам что-то с ним сделать, но и передать обработку следующему объекту в цепочке. 378 | 379 | #### Какие проблемы решает 380 | * Запускать обработчиков последовательно один за другим в том порядке, в котором они находятся в цепочке 381 | * Когда заранее неизвестно, какие конкретно запросы будут приходить и какие обработчики для них понадобятся 382 | 383 | #### Пример 384 | ```php 385 | // build the chain 386 | $logger = new DBLogger(); 387 | $logger->setNext(new MailLogger()) 388 | ->setNext(new FileLogger()); 389 | 390 | $logger->handle($message); 391 | /* Output: 392 | Save to database.. 393 | Send by email.. 394 | Save to log file.. */ 395 | ``` 396 | [Полный пример](Behavioral/ChainOfResponsibility.php) 397 | 398 | 399 | ## Команда (Command) 400 | #### Суть паттерна 401 | Превращает операции в объекты, и такие объекты заключают в себя само действие и его параметры. Этот объект теперь можно логировать, хранить историю, отменять, передавать во внешние сервисы и так далее. 402 | 403 | #### Какие проблемы решает 404 | * Превращает операции в объекты, которые можно логировать, отменять, добавлять в очереди и т.д. 405 | * Предоставляет механизм отделения клиента от получателя 406 | 407 | #### Пример 408 | ```php 409 | $invoker = new Invoker(); 410 | $receiver = new Receiver(); 411 | 412 | $invoker->pushCommand(new TurnOnCommand($receiver)); 413 | $invoker->pushCommand(new TurnOffCommand($receiver)); 414 | $invoker->execute(); 415 | 416 | /* Output: 417 | Receiver: Turning on something.. 418 | Receiver: Turning off something.. */ 419 | ``` 420 | [Полный пример](Behavioral/Command.php) | [Дополнительный пример](Behavioral/CommandExt.php) 421 | 422 | 423 | ## Итератор (Iterator) 424 | #### Суть паттерна 425 | Предоставляет возможность последовательно обходить элементы составных объектов, не раскрывая их внутреннего представления. Идея паттерна состоит в том, чтобы вынести поведение обхода коллекции из самой коллекции отдельно. 426 | 427 | #### Какие проблемы решает 428 | * Позволяет обходить сложные структуры данных, и скрыть при этом детали ее реализации 429 | * Позволяет иметь несколько вариантов обхода одной и той же структуры данных 430 | * Позволяет иметь единый интерфейс обхода различных структур данных 431 | * Дает возможность объекту самостоятельно принимать решение, как он будет итерироваться и какие данные будут доступны на каждой итерации 432 | * Зачастую используется, чтобы не только предоставить доступ к элементам, но и наделить обход некоторой дополнительной логикой 433 | 434 | #### Пример 435 | ```php 436 | $collection = (new SimpleCollection())->addItem('1st item') 437 | ->addItem('2nd item') 438 | ->addItem('3rd item'); 439 | 440 | // go through collection in reverse order 441 | foreach ($collection->getIterator() as $item) { 442 | echo $item . PHP_EOL; 443 | } 444 | 445 | /* Output: 446 | 3rd item 447 | 2nd item 448 | 1st item */ 449 | ``` 450 | [Полный пример](Behavioral/Iterator.php) | [Дополнительный пример](Behavioral/IteratorExt.php) 451 | 452 | 453 | ## Посредник(Mediator) 454 | #### Суть паттерна 455 | Посредник убирает прямые связи между отдельными компонентами, заставляя их общаться друг с другом через себя. 456 | 457 | Паттерн определяет объект, который инкапсулирует логику взаимодействия некоторого набора других объектов. Посредник обеспечивает слабую связность благодаря тому, что объекты не ссылаются друг на друга явно и можно изменять алгоритм их взаимодействия независимо. Таким образом эти объекты проще переиспользовать. 458 | 459 | Допустим в нашей системе есть множество объектов которые взаимодействуют друг с другом (часто их называют "коллегами"). Объекты могут реагировать на действие других объектов, вызывать друг у друга различные методы, и в данной конфигурации они являются сильно связанными. Мы создаем специальный объект `Mediator`, в который перенесем всю логику взаимодействия этого набора объектов, так что эти объекты вместо обращения друг другу будут уведомлять посредника. Тем самым мы избавимся от сильной связности. 460 | 461 | Объекты будут иметь ссылку на посредника и уведомлять его при различных событиях. А посредник свою очередь будет иметь ссылки на все объекты из этого множества. Так, что в соответствии с логикой их взаимодействия будет перенаправлять запросы конкретным объект. 462 | 463 | #### Какие проблемы решает 464 | * Убирает зависимости между компонентами системы, вместо этого они становятся зависимыми от самого посредника 465 | * Централизует управление в одном месте 466 | 467 | #### Пример 468 | ```php 469 | $chat = new ChatMediator(); 470 | 471 | $john = new User('John', $chat); 472 | $jane = new User('Jane', $chat); 473 | $bot = new Bot($chat); 474 | 475 | // every chat member interacts with mediator, 476 | // but not with with each other directly 477 | $john->sendMessage("Hi!"); 478 | $jane->sendMessage("What's up?"); 479 | $bot->sayHello(); 480 | ``` 481 | [Полный пример](Behavioral/Mediator.php) 482 | 483 | 484 | ## Снимок (Memento) 485 | #### Суть паттерна 486 | Позволяет сохранять и восстанавливать прошлые состояния объектов "снимки", не раскрывая подробностей их реализации. Снимок — это простой объект данных, содержащий состояние (состояние свойств) создателя. 487 | Паттерн предлагает держать копию состояния в специальном объекте-снимке с ограниченным интерфейсом, позволяющим, например, узнать дату изготовления или название снимка. Паттерн поручает создание копии состояния объекта самому объекту, который этим состоянием владеет. 488 | 489 | > Реальная применимость паттерна Снимок в PHP под большим вопросом. Чаще всего задачу хранения копии состояния можно решить куда проще при помощи [сериализации](http://php.net/manual/en/language.oop5.serialization.php) (применения вызовов `serialize()` и `unserialize()`) 490 | 491 | #### Какие проблемы решает 492 | * Позволяет создавать любое количество "снимков" объекта и хранить их независимо от объекта 493 | * Позволяет реализовать операции отмены или отката состояния, например если операция не удалась 494 | 495 | #### Пример 496 | ```php 497 | $editor = new Editor(); 498 | $editor->type('This is the first sentence.'); 499 | $editor->type('This is second.'); 500 | // make a snapshot 501 | $memento = $editor->save(); 502 | 503 | $editor->type('And this is third.'); 504 | echo $editor->getContent(); 505 | /* Output: This is the first sentence. This is second. And this is third. */ 506 | 507 | // restore the state from snapshot 508 | $editor->restore($memento); 509 | echo $editor->getContent(); 510 | /* Output: This is the first sentence. This is second. */ 511 | ``` 512 | [Полный пример](Behavioral/Memento.php) 513 | 514 | 515 | ## Наблюдатель (Observer) 516 | #### Суть паттерна 517 | Создает механизм "подписки", позволяющий одним объектам следить и реагировать на события, происходящие в других объектах. Основные участники паттерна это "издатели" `Subject` и "подписчики" `Observer`. 518 | 519 | Паттерн предлагает хранить внутри объекта "издателя" список объектов подписчиков. А также предоставлять методы, с помощью которых "подписчики" могут подписаться или отписаться на события. 520 | 521 | > PHP имеет несколько встроенных интерфейсов `SplSubject`, `SplObserver`, на основе которых можно строить свои реализации 522 | 523 | #### Какие проблемы решает 524 | * Позволяет отдельным компонентам реагировать на события, происходящие в других компонентах 525 | * Наблюдатели могут подписываться или отписываться от получения оповещений динамически, во время выполнения программы 526 | 527 | #### Пример 528 | ```php 529 | $cart = new Cart(); // subject 530 | $cart->attach(new LoggingListener()); // attach an Observer 531 | $cart->setBalance(10); // trigger an event 532 | 533 | /* Output: 534 | Notification: balance of the shopping cart was changed to 10 */ 535 | ``` 536 | [Полный пример](Behavioral/Observer.php) 537 | 538 | 539 | ## Стратегия (Strategy) 540 | #### Суть паттерна 541 | Определяет семейство схожих алгоритмов и помещает каждый из них в собственный класс, предоставляет возможность взаимозаменять алгоритмы во время исполнения программы. 542 | 543 | Вместо того, чтобы изначальный класс сам выполнял тот или иной алгоритм, он будет играть роль контекста, ссылаясь на одну из стратегий и делегируя ей выполнение работы. Чтобы сменить алгоритм, вам будет достаточно подставить в изначальный класс другой объект-стратегию. 544 | 545 | #### Какие проблемы решает 546 | * Описывает разные способы произвести одно и то же действие, позволяя взаимозаменять эти способы в каком-то объекте контекста 547 | * Позволяет вынести отличающееся поведение в отдельную иерархию классов, уменьшает количество if-else операторов 548 | * Позволяет изолировать код, данные и зависимости алгоритмов от других объектов, скрыв детали реализации внутри классов-стратегий 549 | 550 | #### Пример 551 | ```php 552 | interface SortStrategy 553 | { 554 | public function sort(array $data): array; 555 | } 556 | class BubbleSortStrategy implements SortStrategy {} 557 | class QuickSortStrategy implements SortStrategy {} 558 | 559 | $data = [4, 2, 1, 5, 9]; 560 | // for small amount of data the "Bubble Sort" algorithm will be used 561 | // and for large amounts - the "Quick Sort" algorithm 562 | if (count($data) < 10) { 563 | $sorter = new Sorter(new BubbleSortStrategy()); 564 | $sorter->sortArray($data); 565 | } else { 566 | $sorter = new Sorter(new QuickSortStrategy()); 567 | $sorter->sortArray($data); 568 | } 569 | 570 | /* Output: Sorting using bubble sort.. */ 571 | ``` 572 | [Полный пример](Behavioral/Strategy.php) 573 | 574 | 575 | ## Состояние (State) 576 | #### Суть паттерна 577 | Позволяет объекту изменять свое поведение в зависимости от внутреннего состояния, является объектно-ориентированной реализацией конечного автомата. 578 | 579 | Шаблон можно рассматривать как надстройку над шаблоном Стратегия. Оба паттерна используют композицию, чтобы менять поведение основного объекта, делегируя работу вложенным объектам-помощникам. Однако в Стратегии эти объекты не знают друг о друге и никак не связаны, тогда как в Состоянии сами конкретные состояния могут переключать контекст. 580 | 581 | #### Какие проблемы решает 582 | * Позволяет объектам менять поведение в зависимости от своего состояния 583 | * Позволяет изменять поведение во время выполнения программы и избавиться от условных операторов, разбросанных по коду 584 | 585 | #### Пример 586 | ```php 587 | $editor = new TextEditor(new DefaultState()); 588 | $editor->type('First line'); 589 | 590 | $editor->setState(new UpperCase()); 591 | $editor->type('Second line'); 592 | 593 | $editor->setState(new LowerCase()); 594 | $editor->type('Third line'); 595 | 596 | /* Output: 597 | First line 598 | SECOND LINE 599 | third line */ 600 | ``` 601 | [Полный пример](Behavioral/State.php) 602 | 603 | 604 | ## Шаблонный метод (Template method) 605 | #### Суть паттерна 606 | Позволяет определить каркас алгоритма и позволяет подклассам переопределять определенные этапы алгоритма без изменения его структуры. 607 | 608 | Мы разбиваем алгоритм на последовательность шагов, превращаем эти шаги в методы и вызываем их один за другим внутри одного "шаблонного" метода. Подклассы смогут переопределять определенные шаги, но не фактический метод "шаблона". 609 | Мы сохраним последовательность вызовов, но у нас будет возможность изменить один из этих шагов в унаследованных классах. 610 | 611 | #### Какие проблемы решает 612 | * Позволяет подклассам расширять базовый алгоритм, не меняя его структуры 613 | * Позволяет убрать дублирование кода в нескольких классах с похожим поведением, но отличающихся в деталях 614 | 615 | #### Пример 616 | ```php 617 | abstract class AbstractFileConverter 618 | { 619 | // template method 620 | final public function convert() 621 | { 622 | $this->beforeSteps(); 623 | $this->openFile(); 624 | $this->validate(); 625 | $this->makeConversion(); 626 | $this->closeFile(); 627 | $this->afterSteps(); 628 | } 629 | } 630 | // ... 631 | 632 | (new PDFFileConverter())->convert(); 633 | /* Output: 634 | Step1. Read from file.. 635 | Step2. Validate PDF file.. 636 | Step3. Convert PDF file.. 637 | Step4. Close a file descriptor.. */ 638 | 639 | (new CSVFileConverter())->convert(); 640 | /* Output: 641 | Step1. Read from file.. 642 | Step2. Validate CSV file.. 643 | Step3. Convert CSV file.. 644 | Step4. Close a file descriptor.. */ 645 | ``` 646 | [Полный пример](Behavioral/TemplateMethod.php) | [Дополнительный пример](Behavioral/TemplateMethodExt.php) 647 | 648 | 649 | ## Посетитель (Visitor) 650 | #### Суть паттерна 651 | Позволяет расширить набор объектов (не обязательно связанных между собой) новыми функциями. Функция, как правило, имеет общий смысл или одну цель для всех объектов этих классов, но реализуется для каждого из них по разному (например экспорт сущности). 652 | 653 | Другими словами, позволяет добавлять новые операции, не меняя классы объектов, над которыми эти операции могут выполняться. При изменении посетителя нет необходимости изменять основные классы. 654 | 655 | Применяется подход "двойной диспетчеризации" (Double Dispatch), когда конкретная реализация метода, который будет вызван при работе программы, зависит и от объекта у которого этот метод вызывается и от типа объекта который передается в качестве аргумента. 656 | 657 | > Изменить классы узлов единожды все-таки придется. Важно, чтобы иерархия компонентов, менялась редко, так как при добавлении нового компонента придется менять всех существующих посетителей 658 | 659 | #### Какие проблемы решает 660 | * Дает возможность внедрять новое поведение в объекты, без внесения изменений в классы 661 | * Позволяет внедрить функциональность когда нет доступа или возможности изменять классы или не хочется добавлять им дополнительную ответственность 662 | 663 | #### Пример 664 | ```php 665 | $report = new Report('report_title', 'report_content'); 666 | 667 | echo $report->accept(new JSONExportVisitor()); 668 | echo $report->accept(new XMLExportVisitor()); 669 | 670 | /* Output: 671 | "report_title {diagram} report_content" 672 | <?xml version="1.0" <xml><title>report_title<diagram> */ 673 | ``` 674 | [Полный пример](Behavioral/Visitor.php) 675 | 676 | --------------------------------------------------------------------------------