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

Alt Strategy 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}