Completed
Push — master ( 308c33...4cc160 )
by Michał
303:29 queued 289:05
created

Sylius/Bundle/CoreBundle/Routing/RouteProvider.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\Bundle\CoreBundle\Routing;
13
14
use Doctrine\Common\Persistence\ManagerRegistry;
15
use Doctrine\Common\Persistence\ObjectRepository;
16
use Doctrine\Common\Util\ClassUtils;
17
use Sylius\Component\Resource\Repository\RepositoryInterface;
18
use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\DoctrineProvider;
19
use Symfony\Component\DependencyInjection\ContainerInterface;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\Routing\Exception\RouteNotFoundException;
22
use Symfony\Component\Routing\Route;
23
use Symfony\Component\Routing\RouteCollection;
24
25
/**
26
 * @author Gonzalo Vilaseca <[email protected]>
27
 * @author Paweł Jędrzejewski <[email protected]>
28
 */
29
class RouteProvider extends DoctrineProvider implements RouteProviderInterface
30
{
31
    /**
32
     * @var ContainerInterface
33
     */
34
    protected $container;
35
36
    /**
37
     * Route configuration for the object classes to search in
38
     *
39
     * @var array
40
     */
41
    protected $routeConfigs;
42
43
    /**
44
     * Contains an associative array of all the classes and the repositories needed in route generation
45
     *
46
     * @var ObjectRepository[]
47
     */
48
    protected $classRepositories = [];
49
50
    /**
51
     * @param ContainerInterface $container
52
     * @param ManagerRegistry    $managerRegistry
53
     * @param array              $routeConfigs
54
     */
55
    public function __construct(ContainerInterface $container, ManagerRegistry $managerRegistry, array $routeConfigs)
56
    {
57
        $this->container = $container;
58
        $this->routeConfigs = $routeConfigs;
59
        $this->classRepositories = [];
60
61
        parent::__construct($managerRegistry);
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function getRouteByName($name, $parameters = [])
68
    {
69
        if (is_object($name)) {
70
            $className = ClassUtils::getClass($name);
71
            if (isset($this->routeConfigs[$className])) {
72
                return $this->createRouteFromEntity($name);
73
            }
74
        }
75
76
        foreach ($this->getRepositories() as $className => $repository) {
77
            $entity = $this->tryToFindEntity($name, $repository, $className);
78
            if ($entity) {
79
                return $this->createRouteFromEntity($entity);
80
            }
81
        }
82
83
        throw new RouteNotFoundException("No route found for name '$name'");
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function getRoutesByNames($names = null)
90
    {
91
        if (null === $names) {
92
            if (0 === $this->routeCollectionLimit) {
93
                return [];
94
            }
95
96
            $collection = new RouteCollection();
97
            foreach ($this->getRepositories() as $className => $repository) {
98
                $entities = $repository->findBy([], null, $this->routeCollectionLimit ?: null);
99
                foreach ($entities as $entity) {
100
                    $name = $this->getFieldValue($entity, $this->routeConfigs[$className]['field']);
101
                    $collection->add($name, $this->createRouteFromEntity($entity));
102
                }
103
            }
104
105
            return $collection;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $collection; (Symfony\Component\Routing\RouteCollection) is incompatible with the return type declared by the interface Symfony\Cmf\Component\Ro...rface::getRoutesByNames of type Symfony\Component\Routing\Route[].

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...
106
        }
107
108
        $routes = [];
109
        foreach ($names as $name) {
110
            try {
111
                $routes[] = $this->getRouteByName($name);
112
            } catch (RouteNotFoundException $e) {
113
                // not found
114
            }
115
        }
116
117
        return $routes;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function getRouteCollectionForRequest(Request $request)
124
    {
125
        $path = $request->getPathInfo();
126
        $collection = new RouteCollection();
127
128
        if (empty($path)) {
129
            return $collection;
130
        }
131
132
        foreach ($this->getRepositories() as $className => $repository) {
133
            if ('' === $this->routeConfigs[$className]['prefix']
134
                || 0 === strpos($path, $this->routeConfigs[$className]['prefix'])
135
            ) {
136
                $value = substr($path, strlen($this->routeConfigs[$className]['prefix']));
137
                $value = trim($value, '/');
138
                $value = urldecode($value);
139
140
                if (empty($value)) {
141
                    continue;
142
                }
143
144
                $entity = $this->tryToFindEntity($value, $repository, $className);
145
146
                if (null === $entity) {
147
                    continue;
148
                }
149
150
                $route = $this->createRouteFromEntity($entity, $value);
151
                if (preg_match('/.+\.([a-z]+)$/i', $value, $matches)) {
152
                    $route->setDefault('_format', $matches[1]);
153
                }
154
155
                $collection->add($value, $route);
156
            }
157
        }
158
159
        return $collection;
160
    }
161
162
    /**
163
     * This method is called from a compiler pass
164
     *
165
     * @param string           $class
166
     * @param string           $id
167
     */
168
    public function addRepository($class, $id)
169
    {
170
        if (!is_string($id)) {
171
            throw new \InvalidArgumentException('Expected service id!');
172
        }
173
174
        $this->classRepositories[$class] = $id;
175
    }
176
177
    /**
178
     * Get repository services.
179
     *
180
     * @return array
181
     */
182
    private function getRepositories()
183
    {
184
        $repositories = [];
185
186
        foreach ($this->classRepositories as $class => $id) {
187
            $repositories[$class] = $this->container->get($id);
188
        }
189
190
        return $repositories;
191
    }
192
193
    /**
194
     * @param object $entity
195
     * @param string $fieldName
196
     *
197
     * @return string
198
     */
199
    private function getFieldValue($entity, $fieldName)
200
    {
201
        return $entity->{'get'.ucfirst($fieldName)}();
202
    }
203
204
    /**
205
     * @param object $entity
206
     *
207
     * @return Route
208
     */
209
    private function createRouteFromEntity($entity, $value = null)
210
    {
211
        $className = ClassUtils::getClass($entity);
212
        $fieldName = $this->routeConfigs[$className]['field'];
213
214
        // Used for matching by translated field
215
        // eg:
216
        // If the url slug doesn't match the current's locale slug
217
        // the method getSlug would return the slug in current locale
218
        // it won't match the url and will fail
219
        // TODO refactor class if locale is included in url
220
        if (null === $value) {
221
            $value = $this->getFieldValue($entity, $fieldName);
222
        }
223
        $defaults = ['_sylius_entity' => $entity, $fieldName => $value];
224
225
        return new Route($this->routeConfigs[$className]['prefix'].'/'.$value, $defaults);
226
    }
227
228
    /**
229
     * @param string $identifier
230
     * @param RepositoryInterface $repository
231
     * @param string $className
232
     *
233
     * @return object|null
234
     */
235
    private function tryToFindEntity($identifier, RepositoryInterface $repository, $className)
236
    {
237
        if ('slug' === $this->routeConfigs[$className]['field']) {
238
            return $repository->findOneBySlug($identifier);
239
        }
240
        if ('name' === $this->routeConfigs[$className]['field']) {
241
            return $repository->findOneByName($identifier);
242
        }
243
        if ('permalink' === $this->routeConfigs[$className]['field']) {
244
            return $repository->findOneByPermalink($identifier);
245
        }
246
247
        return $repository->findOneBy([$this->routeConfigs[$className]['field'] => $identifier]);
248
    }
249
}
250