├── .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 = '';
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 | 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..
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('Home page
'))
72 | ->addHeader('')
73 | ->addContent('content');
74 |
75 | // some time letter ...
76 | $page->addFooter('');
77 |
78 | echo $page->build()->show();
79 | /* Output:
80 | Home page
content */
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: {{ $title }}
{{ $content }} */
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 | report_title */
673 | ```
674 | [Полный пример](Behavioral/Visitor.php)
675 |
676 |
--------------------------------------------------------------------------------