3.11. Strategy
3.11.1. Terminology:
Context
Strategy
Concrete Strategy
3.11.2. Purpose
To separate strategies and to enable fast switching between them. Also this pattern is a good alternative to inheritance (instead of having an abstract class that is extended).
3.11.3. Examples
sorting a list of objects, one strategy by date, the other by id
simplify unit testing: e.g. switching between file and in-memory storage
3.11.4. UML Diagram
3.11.5. Code
You can also find this code on GitHub
Context.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Strategy;
6
7class Context
8{
9 public function __construct(private Comparator $comparator)
10 {
11 }
12
13 public function executeStrategy(array $elements): array
14 {
15 uasort($elements, [$this->comparator, 'compare']);
16
17 return $elements;
18 }
19}
Comparator.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Strategy;
6
7interface Comparator
8{
9 /**
10 * @param mixed $a
11 * @param mixed $b
12 */
13 public function compare($a, $b): int;
14}
DateComparator.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Strategy;
6
7use DateTime;
8
9class DateComparator implements Comparator
10{
11 public function compare($a, $b): int
12 {
13 $aDate = new DateTime($a['date']);
14 $bDate = new DateTime($b['date']);
15
16 return $aDate <=> $bDate;
17 }
18}
IdComparator.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Strategy;
6
7class IdComparator implements Comparator
8{
9 public function compare($a, $b): int
10 {
11 return $a['id'] <=> $b['id'];
12 }
13}
3.11.6. Test
Tests/StrategyTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Strategy\Tests;
6
7use DesignPatterns\Behavioral\Strategy\Context;
8use DesignPatterns\Behavioral\Strategy\DateComparator;
9use DesignPatterns\Behavioral\Strategy\IdComparator;
10use PHPUnit\Framework\TestCase;
11
12class StrategyTest extends TestCase
13{
14 public function provideIntegers()
15 {
16 return [
17 [
18 [['id' => 2], ['id' => 1], ['id' => 3]],
19 ['id' => 1],
20 ],
21 [
22 [['id' => 3], ['id' => 2], ['id' => 1]],
23 ['id' => 1],
24 ],
25 ];
26 }
27
28 public function provideDates()
29 {
30 return [
31 [
32 [['date' => '2014-03-03'], ['date' => '2015-03-02'], ['date' => '2013-03-01']],
33 ['date' => '2013-03-01'],
34 ],
35 [
36 [['date' => '2014-02-03'], ['date' => '2013-02-01'], ['date' => '2015-02-02']],
37 ['date' => '2013-02-01'],
38 ],
39 ];
40 }
41
42 /**
43 * @dataProvider provideIntegers
44 *
45 * @param array $collection
46 * @param array $expected
47 */
48 public function testIdComparator($collection, $expected)
49 {
50 $obj = new Context(new IdComparator());
51 $elements = $obj->executeStrategy($collection);
52
53 $firstElement = array_shift($elements);
54 $this->assertSame($expected, $firstElement);
55 }
56
57 /**
58 * @dataProvider provideDates
59 *
60 * @param array $collection
61 * @param array $expected
62 */
63 public function testDateComparator($collection, $expected)
64 {
65 $obj = new Context(new DateComparator());
66 $elements = $obj->executeStrategy($collection);
67
68 $firstElement = array_shift($elements);
69 $this->assertSame($expected, $firstElement);
70 }
71}