Completed
Push — master ( aeeaba...d44f14 )
by Simonas
67:22 queued 42:18
created

SeoUrlMatcher::getDocumentByUrl()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 13
rs 9.4286
cc 3
eloc 7
nc 4
nop 1
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
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 ONGR\RouterBundle\Routing;
13
14
use ONGR\ElasticsearchDSL\Query\BoolQuery;
15
use ONGR\ElasticsearchDSL\Query\MatchQuery;
16
use ONGR\ElasticsearchDSL\Query\NestedQuery;
17
use ONGR\ElasticsearchDSL\Query\TermQuery;
18
use ONGR\ElasticsearchDSL\Search;
19
use ONGR\ElasticsearchBundle\Service\Manager;
20
use ONGR\RouterBundle\Document\SeoAwareTrait;
21
use ONGR\RouterBundle\Service\SeoUrlMapper;
22
use Symfony\Bundle\FrameworkBundle\Routing\RedirectableUrlMatcher;
23
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
24
25
/**
26
 * URL matcher with extended functionality for document matching.
27
 */
28
class SeoUrlMatcher extends RedirectableUrlMatcher
29
{
30
    const SCHEME_HTTP = 'http';
31
32
    /**
33
     * @var RedirectableUrlMatcher
34
     */
35
    protected $cachedMatcher = null;
36
37
    /**
38
     * @var Manager
39
     */
40
    protected $esManager = null;
41
42
    /**
43
     * @var array
44
     */
45
    protected $typeMap = null;
46
47
    /**
48
     * @var bool
49
     */
50
    protected $allowHttps = false;
51
52
    /**
53
     * @var string
54
     */
55
    protected $urlKey = null;
56
57
    /**
58
     * @var SeoUrlMapper
59
     */
60
    protected $seoUrlMapper;
61
62
    /**
63
     * @param RedirectableUrlMatcher $parentMatcher Parent matcher that is called when this matcher fails.
64
     * @param Manager                $esManager     ES manager.
65
     * @param array                  $typeMap       Type map.
66
     * @param bool                   $allowHttps    Is https allowed.
67
     * @param string                 $urlKey        Url key to search routes with.
68
     */
69
    public function __construct($parentMatcher, $esManager, $typeMap, $allowHttps = false, $urlKey = null)
70
    {
71
        $this->cachedMatcher = $parentMatcher;
72
        $this->esManager = $esManager;
73
        $this->typeMap = array_change_key_case($typeMap, CASE_LOWER);
74
        $this->allowHttps = $allowHttps;
75
        $this->urlKey = $urlKey;
76
    }
77
78
    /**
79
     * Sets SEO URL mapper.
80
     *
81
     * @param SeoUrlMapper $seoUrlMapper SEO URL mapper.
82
     */
83
    public function setSeoUrlMapper($seoUrlMapper)
84
    {
85
        $this->seoUrlMapper = $seoUrlMapper;
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function match($pathinfo)
92
    {
93
        $url = ltrim(rawurldecode($pathinfo), '/');
94
        $requestedUrlOriginal = $url;
95
96
        /** @var RequestContext $context */
97
        $context = $this->getCachedMatcher()->getContext();
98
99
        if (!$this->allowHttps && $context->getScheme() != self::SCHEME_HTTP) {
100
            throw new ResourceNotFoundException('Non-http urls are not processed');
101
        }
102
103
        $result = $this->getDocumentByUrl($url);
104
105
        if ($result !== null) {
106
            /** @var SeoAwareTrait $document */
107
            list($documentName, $document) = $result;
108
            list($documentSeoKey, $documentLink) = $this->getLink($document, $url);
109
110
            // Url doesn't exist.
111
            $urls = $document->getUrls();
0 ignored issues
show
Bug introduced by
It seems like getUrls() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
112
            if (count($urls) === 0 && $documentLink === false) {
113
                throw new ResourceNotFoundException();
114
            }
115
116
            if (is_array($document->getExpiredUrls())
0 ignored issues
show
Bug introduced by
It seems like getExpiredUrls() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
117
                && in_array($this->getUrlHash($url), $document->getExpiredUrls())
0 ignored issues
show
Bug introduced by
It seems like getExpiredUrls() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
118
            ) {
119
                if ($documentSeoKey === false) {
120
                    return $this->doRedirect(
121
                        $this->ensurePrefixSlash($documentLink),
0 ignored issues
show
Bug introduced by
It seems like $documentLink defined by $this->getLink($document, $url) on line 108 can also be of type boolean; however, ONGR\RouterBundle\Routin...er::ensurePrefixSlash() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
122
                        $this->getTypeMap()[$documentName]['_route'],
123
                        (!$this->allowHttps) ? self::SCHEME_HTTP : null
124
                    );
125
                }
126
            }
127
128
            // Force redirect to original link, if links are not identical (lowercase, trailing slash).
129
            if ($requestedUrlOriginal !== $documentLink) {
130
                return $this->doRedirect($documentLink, $this->getTypeMap()[$documentName]['_route']);
0 ignored issues
show
Bug introduced by
It seems like $documentLink defined by $this->getLink($document, $url) on line 108 can also be of type boolean; however, ONGR\RouterBundle\Routin...rlMatcher::doRedirect() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
131
            }
132
133
            return array_merge(
134
                $this->getTypeMap()[$documentName],
135
                [
136
                    'document' => $document,
137
                    'seoKey' => $documentSeoKey,
138
                ]
139
            );
140
        }
141
142
        throw new ResourceNotFoundException();
143
    }
144
145
    /**
146
     * Cached matcher redirect.
147
     *
148
     * @param string $link   URL.
149
     * @param string $route  Route name.
150
     * @param string $scheme Scheme to use. E.g. 'http' or 'https'.
151
     *
152
     * @return array
153
     */
154
    protected function doRedirect($link, $route, $scheme = self::SCHEME_HTTP)
155
    {
156
        return $this->getCachedMatcher()->redirect($this->ensurePrefixSlash($link), $route, $scheme);
157
    }
158
159
    /**
160
     * Hacky solution to get seo key and url.
161
     *
162
     * @param SeoAwareTrait $document Document.
0 ignored issues
show
introduced by
The type SeoAwareTrait for parameter $document is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
163
     * @param string        $url      Url.
164
     *
165
     * @return array
166
     * @throws \LogicException
167
     */
168
    protected function getLink($document, $url)
169
    {
170
        if (!$this->getSeoUrlMapper()) {
171
            throw new \LogicException('Seo url mapper is not set.');
172
        }
173
174
        $documentSeoKey = $this->getSeoUrlMapper()->checkDocumentUrlExists($document, $url);
175
        $documentLink = $this->getSeoUrlMapper()->getLinkByKey($document, $documentSeoKey);
0 ignored issues
show
Bug introduced by
It seems like $documentSeoKey defined by $this->getSeoUrlMapper()...Exists($document, $url) on line 174 can also be of type boolean; however, ONGR\RouterBundle\Servic...lMapper::getLinkByKey() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
176
177
        return [$documentSeoKey, $documentLink];
178
    }
179
180
    /**
181
     * Generates hash for given url.
182
     *
183
     * @param string $url URL string.
184
     *
185
     * @return string
186
     */
187
    protected function getUrlHash($url)
188
    {
189
        return md5(strtolower($url));
190
    }
191
192
    /**
193
     * Returns search instance that is used to search for documents in Elasticsearch.
194
     *
195
     * @param string $url URL.
196
     *
197
     * @return Search
198
     */
199
    protected function getSearch($url)
200
    {
201
        $search = new Search();
202
203
        if (!$url) {
204
            return $search;
205
        }
206
207
        $bool = new BoolQuery();
208
        $bool->add(new MatchQuery('urls.url', $url), BoolQuery::SHOULD);
209
210
        if ($this->urlKey != null) {
211
            $bool->add(new TermQuery('urls.key', $this->urlKey), BoolQuery::SHOULD);
212
        }
213
214
        $search->addQuery(new NestedQuery('urls', $bool), BoolQuery::SHOULD);
215
        $search->addQuery(new TermQuery('expired_urls', $this->getUrlHash($url)), BoolQuery::SHOULD);
216
217
        return $search;
218
    }
219
220
    /**
221
     * Returns document by URL.
222
     *
223
     * @param string $url URL.
224
     *
225
     * @return array|null
226
     * @throws \Exception Search type not found.
227
     */
228
    private function getDocumentByUrl($url)
229
    {
230
        $repository = $this->getEsManager()->getRepository([]);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
231
        $out = [];
232
233
        /** @var SeoAwareTrait $document */
234
        foreach ($repository->execute($this->getSearch($url)) as $document) {
235
            $mapping = $this->getEsManager()->getMetadataCollector()->getDocumentMapping($document);
236
            $type = $mapping['type'];
0 ignored issues
show
Unused Code introduced by
$type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
237
        }
238
239
        return empty($out) ? null : $out;
240
    }
241
242
    /**
243
     * Helper method to ensure that link has slash prefix.
244
     *
245
     * @param string $link Link.
246
     *
247
     * @return string Link with slash prefix.
248
     */
249
    private function ensurePrefixSlash($link)
250
    {
251
        if (substr($link, 0, 1) != '/') {
252
            $link = '/' . $link;
253
        }
254
255
        return $link;
256
    }
257
258
    /**
259
     * @return SeoUrlMapper
260
     */
261
    public function getSeoUrlMapper()
262
    {
263
        return $this->seoUrlMapper;
264
    }
265
266
    /**
267
     * @return array
268
     */
269
    public function getTypeMap()
270
    {
271
        return $this->typeMap;
272
    }
273
274
    /**
275
     * @return Manager
276
     */
277
    public function getEsManager()
278
    {
279
        return $this->esManager;
280
    }
281
282
    /**
283
     * @return RedirectableUrlMatcher
284
     */
285
    public function getCachedMatcher()
286
    {
287
        return $this->cachedMatcher;
288
    }
289
}
290