Completed
Push — master ( b9c485...94fed1 )
by Michał
13:51
created

Component/Addressing/Matcher/ZoneMatcher.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sylius\Component\Addressing\Matcher;
13
14
use Sylius\Component\Addressing\Model\AddressInterface;
15
use Sylius\Component\Addressing\Model\ZoneInterface;
16
use Sylius\Component\Addressing\Model\ZoneMemberInterface;
17
use Sylius\Component\Resource\Repository\RepositoryInterface;
18
19
/**
20
 * This implementation can match addresses against zones by country and province.
21
 * It also handles sub-zones.
22
 *
23
 * @author Saša Stamenković <[email protected]>
24
 * @author Gonzalo Vilaseca <[email protected]>
25
 * @author Jan Góralski <[email protected]>
26
 */
27
class ZoneMatcher implements ZoneMatcherInterface
28
{
29
    /**
30
     * @var RepositoryInterface
31
     */
32
    protected $zoneRepository;
33
34
    /**
35
     * Zone matching priorities.
36
     *
37
     * @var array
38
     */
39
    protected $priorities = [
40
        ZoneInterface::TYPE_PROVINCE,
41
        ZoneInterface::TYPE_COUNTRY,
42
        ZoneInterface::TYPE_ZONE,
43
    ];
44
45
    /**
46
     * @param RepositoryInterface $zoneRepository
47
     */
48
    public function __construct(RepositoryInterface $zoneRepository)
49
    {
50
        $this->zoneRepository = $zoneRepository;
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function match(AddressInterface $address, $scope = null)
57
    {
58
        $zones = [];
59
60
        /* @var ZoneInterface $zone */
61
        foreach ($availableZones = $this->getZones($scope) as $zone) {
62
            if ($this->addressBelongsToZone($address, $zone)) {
63
                $zones[$zone->getType()] = $zone;
64
            }
65
        }
66
67
        foreach ($this->priorities as $priority) {
68
            if (isset($zones[$priority])) {
69
                return $zones[$priority];
70
            }
71
        }
72
73
        return null;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function matchAll(AddressInterface $address, $scope = null)
80
    {
81
        $zones = [];
82
83
        foreach ($this->getZones($scope) as $zone) {
84
            if ($this->addressBelongsToZone($address, $zone)) {
85
                $zones[] = $zone;
86
            }
87
        }
88
89
        return $zones;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $zones; (array) is incompatible with the return type declared by the interface Sylius\Component\Address...cherInterface::matchAll of type Doctrine\Common\Collecti...g\Model\ZoneInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
90
    }
91
92
    /**
93
     * @param AddressInterface $address
94
     * @param ZoneInterface    $zone
95
     *
96
     * @return bool
97
     */
98
    protected function addressBelongsToZone(AddressInterface $address, ZoneInterface $zone)
99
    {
100
        foreach ($zone->getMembers() as $member) {
101
            if ($this->addressBelongsToZoneMember($address, $member)) {
102
                return true;
103
            }
104
        }
105
106
        return false;
107
    }
108
109
    /**
110
     * @param AddressInterface    $address
111
     * @param ZoneMemberInterface $member
112
     *
113
     * @return bool
114
     *
115
     * @throws \InvalidArgumentException
116
     */
117
    protected function addressBelongsToZoneMember(AddressInterface $address, ZoneMemberInterface $member)
118
    {
119
        switch ($type = $member->getBelongsTo()->getType()) {
120
            case ZoneInterface::TYPE_PROVINCE:
121
                return null !== $address->getProvinceCode() && $address->getProvinceCode() === $member->getCode();
122
123
            case ZoneInterface::TYPE_COUNTRY:
124
                return null !== $address->getCountryCode() && $address->getCountryCode() === $member->getCode();
125
126
            case ZoneInterface::TYPE_ZONE:
127
                $zone = $this->getZoneByCode($member->getCode());
128
129
                return $this->addressBelongsToZone($address, $zone);
130
131
            default:
132
                throw new \InvalidArgumentException(sprintf('Unexpected zone type "%s".', $type));
133
        }
134
    }
135
136
    /**
137
     * @param string|null $scope
138
     *
139
     * @return array
140
     */
141
    protected function getZones($scope = null)
142
    {
143
        if (null === $scope) {
144
            return $this->zoneRepository->findAll();
145
        }
146
147
        return $this->zoneRepository->findBy(['scope' => $scope]);
148
    }
149
150
    /**
151
     * @param string $code
152
     *
153
     * @return ZoneInterface
154
     */
155
    protected function getZoneByCode($code)
156
    {
157
        return $this->zoneRepository->findOneBy(['code' => $code]);
158
    }
159
}
160