Completed
Push — master ( 7b775c...3fc8ad )
by Jeroen
136:00 queued 130:06
created

getNodeTranslationByNodeId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 13
cp 0
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace Kunstmaan\NodeBundle\Repository;
4
5
use Doctrine\ORM\EntityRepository;
6
use Doctrine\ORM\Query\ResultSetMappingBuilder;
7
use Doctrine\ORM\QueryBuilder;
8
use Kunstmaan\AdminBundle\Entity\BaseUser;
9
use Kunstmaan\NodeBundle\Entity\HasNodeInterface;
10
use Kunstmaan\NodeBundle\Entity\Node;
11
use Kunstmaan\NodeBundle\Entity\NodeTranslation;
12
use Kunstmaan\NodeBundle\Entity\NodeVersion;
13
use Kunstmaan\UtilitiesBundle\Helper\ClassLookup;
14
15
/**
16
 * NodeRepository
17
 */
18
class NodeTranslationRepository extends EntityRepository
19
{
20
    /**
21
     * Get the QueryBuilder based on node id and language.
22
     *
23
     * @deprecated This method is deprecated since KunstmaanNodeBundle 5.7 and will be removed in KunstmaanNodeBundle 6.0. Use the renamed method "getNodeTranslationByNodeId" instead.
24
     *
25
     * @param int    $nodeId
26
     * @param string $lang
27
     *
28
     * @return NodeTranslation|null
29
     */
30
    public function getNodeTranslationByNodeIdQueryBuilder($nodeId, $lang)
31
    {
32
        @trigger_error(sprintf('The method "%s" is deprecated since KunstmaanNodeBundle 5.7 and will be removed in KunstmaanNodeBundle 6.0. Use the renamed method "getNodeTranslationByNodeId" instead.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
33
34
        return $this->getNodeTranslationByNodeId($nodeId, $lang);
35
    }
36
37
    /**
38
     * @return NodeTranslation|null
39
     */
40
    public function getNodeTranslationByNodeId(int $nodeId, string $lang)
41
    {
42
        $qb = $this->createQueryBuilder('nt')
43
            ->select('nt')
44
            ->innerJoin('nt.node', 'n', 'WITH', 'nt.node = n.id')
45
            ->where('n.deleted != 1')
46
            ->andWhere('nt.online = 1')
47
            ->andWhere('nt.lang = :lang')
48
            ->setParameter('lang', $lang)
49
            ->andWhere('n.id = :node_id')
50
            ->setParameter('node_id', $nodeId)
51
            ->setFirstResult(0)
52
            ->setMaxResults(1);
53
54
        return $qb->getQuery()->getOneOrNullResult();
55
    }
56
57
    /**
58
     * Get max children weight
59
     *
60
     * @param Node   $parentNode
0 ignored issues
show
Documentation introduced by
Should the type for parameter $parentNode not be null|Node?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
61
     * @param string $lang       (optional) Only return max weight for the
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
62
     *                           given language
63
     *
64
     * @return int
65
     */
66
    public function getMaxChildrenWeight(Node $parentNode = null, $lang = null)
67
    {
68
        $maxWeight = $this->getNodeTranslationsQueryBuilder($lang)
69
            ->select('max(nt.weight)')
70
            ->andWhere('n.parent = :parentNode')
71
            ->setParameter('parentNode', $parentNode)
72
            ->getQuery()
73
            ->getSingleScalarResult();
74
75
        return (int) $maxWeight;
76
    }
77
78
    /**
79
     * QueryBuilder to fetch node translations (ignoring nodes that have been
80
     * deleted)
81
     *
82
     * @param string $lang (optional) Only return NodeTranslations for the
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
83
     *                     given language
84
     *
85
     * @return \Doctrine\ORM\QueryBuilder
86
     */
87
    public function getNodeTranslationsQueryBuilder($lang = null)
88
    {
89
        $queryBuilder = $this->createQueryBuilder('nt')
90
            ->select('nt,n,v')
91
            ->innerJoin('nt.node', 'n')
92
            ->leftJoin(
93
                'nt.publicNodeVersion',
94
                'v',
95
                'WITH',
96
                'nt.publicNodeVersion = v.id'
97
            )
98
            ->where('n.deleted = false')
99
            ->orderBy('nt.weight')
100
            ->addOrderBy('nt.weight');
101
102
        if (!empty($lang)) {
103
            $queryBuilder
104
                ->andWhere('nt.lang = :lang')
105
                ->setParameter('lang', $lang);
106
        }
107
108
        return $queryBuilder;
109
    }
110
111
    /**
112
     * QueryBuilder to fetch node translations that are currently published
113
     * (ignoring nodes that have been deleted)
114
     *
115
     * @param string $lang (optional) Only return NodeTranslations for the
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
116
     *                     given language
117
     *
118
     * @return \Doctrine\ORM\QueryBuilder
119
     */
120
    public function getOnlineNodeTranslationsQueryBuilder($lang = null)
121
    {
122
        return $this->getNodeTranslationsQueryBuilder($lang)
123
            ->andWhere('nt.online = true');
124
    }
125
126
    /**
127
     * QueryBuilder to fetch immediate child NodeTranslations for a specific
128
     * node and (optional) language
129
     *
130
     * @return \Doctrine\ORM\QueryBuilder
131
     */
132
    public function getChildrenQueryBuilder(Node $parent, $lang = null)
133
    {
134
        return $this->getNodeTranslationsQueryBuilder($lang)
135
            ->andWhere('n.parent = :parent')
136
            ->setParameter('parent', $parent);
137
    }
138
139
    /**
140
     * QueryBuilder to fetch immediate child NodeTranslations for a specific
141
     * node and (optional) language that are currently published
142
     *
143
     * @return \Doctrine\ORM\QueryBuilder
144
     */
145
    public function getOnlineChildrenQueryBuilder(Node $parent, $lang = null)
146
    {
147
        return $this->getChildrenQueryBuilder($parent, $lang)
148
            ->andWhere('nt.online = true');
149
    }
150
151
    /**
152
     * Get all online child node translations for a given node and (optional)
153
     * language
154
     *
155
     * @param Node   $parent
156
     * @param string $lang   (optional, if not specified all languages will be
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
157
     *                       returned)
158
     *
159
     * @return array
160
     */
161
    public function getOnlineChildren(Node $parent, $lang = null)
162
    {
163
        return $this->getOnlineChildrenQueryBuilder($parent, $lang)
164
            ->getQuery()->getResult();
165
    }
166
167
    /**
168
     * Finds all nodetranslations where title is like the given $title parameter
169
     *
170
     * @param string $title
171
     * @param string $lang  (optional, if not specified all languages will be
0 ignored issues
show
Documentation introduced by
Should the type for parameter $lang not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
172
     *                      returned)
173
     *
174
     * @return array
175
     */
176
    public function getNodeTranslationsLikeTitle($title, $lang = null)
177
    {
178
        /** @var QueryBuilder $qb */
179
        $qb = $this->getNodeTranslationsQueryBuilder($lang);
180
        $qb->andWhere('nt.title like :title')
181
            ->setParameter('title', '%' . $title . '%');
182
183
        return $qb->getQuery()->getResult();
184
    }
185
186
    /**
187
     * Get the node translation for a node
188
     *
189
     * @param HasNodeInterface $hasNode
190
     *
191
     * @return NodeTranslation
0 ignored issues
show
Documentation introduced by
Should the return type not be NodeTranslation|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
192
     */
193
    public function getNodeTranslationFor(HasNodeInterface $hasNode)
194
    {
195
        /* @var NodeVersion $nodeVersion */
196
        $nodeVersion = $this->getEntityManager()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method getNodeVersionFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Rep...y\NodeVersionRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
197
            ->getRepository(NodeVersion::class)
198
            ->getNodeVersionFor($hasNode);
199
200
        if (!\is_null($nodeVersion)) {
201
            return $nodeVersion->getNodeTranslation();
202
        }
203
204
        return null;
205
    }
206
207
    /**
208
     * Get the node translation for a given slug string
209
     *
210
     * @param string               $slug       The slug
211
     * @param NodeTranslation|null $parentNode The parentnode
212
     *
213
     * @return NodeTranslation|null
214
     */
215
    public function getNodeTranslationForSlug(
216
        $slug,
217
        NodeTranslation $parentNode = null
218
    ) {
219
        if (empty($slug)) {
220
            return $this->getNodeTranslationForSlugPart(null, $slug);
221
        }
222
223
        $slugParts = explode('/', $slug);
224
        $result = $parentNode;
225
        foreach ($slugParts as $slugPart) {
226
            $result = $this->getNodeTranslationForSlugPart($result, $slugPart);
227
        }
228
229
        return $result;
230
    }
231
232
    /**
233
     * Returns the node translation for a given slug
234
     *
235
     * @param NodeTranslation|null $parentNode The parentNode
236
     * @param string               $slugPart   The slug part
237
     *
238
     * @return NodeTranslation|null
239
     */
240
    private function getNodeTranslationForSlugPart(
241
        NodeTranslation $parentNode = null,
242
        $slugPart = ''
243
    ) {
244
        $qb = $this->createQueryBuilder('t')
245
            ->select('t', 'v', 'n')
246
            ->innerJoin('t.node', 'n', 'WITH', 't.node = n.id')
247
            ->leftJoin(
248
                't.publicNodeVersion',
249
                'v',
250
                'WITH',
251
                't.publicNodeVersion = v.id'
252
            )
253
            ->where('n.deleted != 1')
254
            ->setFirstResult(0)
255
            ->setMaxResults(1);
256
257
        if ($parentNode !== null) {
258
            $qb->andWhere('t.slug = :slug')
259
                ->andWhere('n.parent = :parent')
260
                ->setParameter('slug', $slugPart)
261
                ->setParameter('parent', $parentNode->getNode()->getId());
262
        } else {
263
            /* if parent is null we should look for slugs that have no parent */
264
            $qb->andWhere('n.parent IS NULL');
265
            if (empty($slugPart)) {
266
                $qb->andWhere('t.slug is NULL');
267
            } else {
268
                $qb->andWhere('t.slug = :slug');
269
                $qb->setParameter('slug', $slugPart);
270
            }
271
        }
272
273
        return $qb->getQuery()->getOneOrNullResult();
274
    }
275
276
    /**
277
     * Get the node translation for a given url
278
     *
279
     * @param string          $urlSlug        The full url
280
     * @param string          $locale         The locale
281
     * @param bool            $includeDeleted Include deleted nodes
282
     * @param NodeTranslation $toExclude      Optional NodeTranslation instance
0 ignored issues
show
Documentation introduced by
Should the type for parameter $toExclude not be null|NodeTranslation?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
283
     *                                        you wish to exclude
284
     * @param Node            $rootNode       Optional Root node of the tree you
0 ignored issues
show
Documentation introduced by
Should the type for parameter $rootNode not be null|Node?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
285
     *                                        wish to use
286
     *
287
     * @return array
288
     */
289
    public function getAllNodeTranslationsForUrl(
290
        $urlSlug,
291
        $locale = '',
292
        $includeDeleted = false,
293
        NodeTranslation $toExclude = null,
294
        Node $rootNode = null
295
    ) {
296
        $qb = $this->createQueryBuilder('b')
297
            ->select('b', 'v')
298
            ->innerJoin('b.node', 'n', 'WITH', 'b.node = n.id')
299
            ->leftJoin(
300
                'b.publicNodeVersion',
301
                'v',
302
                'WITH',
303
                'b.publicNodeVersion = v.id'
304
            )
305
            ->addOrderBy('b.online', 'DESC')
306
            ->setFirstResult(0)
307
            ->setMaxResults(1);
308
309
        if (!$includeDeleted) {
310
            $qb->andWhere('n.deleted = 0');
311
        }
312
313
        if (!empty($locale)) {
314
            $qb->andWhere('b.lang = :lang')
315
                ->setParameter('lang', $locale);
316
        }
317
318
        if (empty($urlSlug)) {
319
            $qb->andWhere('b.url IS NULL');
320
        } else {
321
            $qb->andWhere('b.url = :url');
322
            $qb->setParameter('url', $urlSlug);
323
        }
324
325
        if (!\is_null($toExclude)) {
326
            $qb->andWhere('NOT b.id = :exclude_id')
327
                ->setParameter('exclude_id', $toExclude->getId());
328
        }
329
330
        if ($rootNode) {
331
            $qb->andWhere('n.lft >= :left')
332
                ->andWhere('n.rgt <= :right')
333
                ->setParameter('left', $rootNode->getLeft())
334
                ->setParameter('right', $rootNode->getRight());
335
        }
336
337
        return $qb->getQuery()->getResult();
338
    }
339
340
    /**
341
     * Get the node translation for a given url
342
     *
343
     * @param string          $urlSlug        The full url
344
     * @param string          $locale         The locale
345
     * @param bool            $includeDeleted Include deleted nodes
346
     * @param NodeTranslation $toExclude      Optional NodeTranslation instance
0 ignored issues
show
Documentation introduced by
Should the type for parameter $toExclude not be null|NodeTranslation?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
347
     *                                        you wish to exclude
348
     * @param Node            $rootNode       Optional Root node of the tree you
0 ignored issues
show
Documentation introduced by
Should the type for parameter $rootNode not be null|Node?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
349
     *                                        wish to use
350
     *
351
     * @return NodeTranslation|null
352
     */
353
    public function getNodeTranslationForUrl(
354
        $urlSlug,
355
        $locale = '',
356
        $includeDeleted = false,
357
        NodeTranslation $toExclude = null,
358
        Node $rootNode = null
359
    ) {
360
        $translations = $this->getAllNodeTranslationsForUrl($urlSlug, $locale, $includeDeleted, $toExclude, $rootNode);
361
362
        if (empty($translations)) {
363
            return null;
364
        }
365
366
        return $translations[0];
367
    }
368
369
    /**
370
     * Get all top node translations
371
     *
372
     * @return NodeTranslation[]
373
     */
374
    public function getTopNodeTranslations()
375
    {
376
        $qb = $this->createQueryBuilder('b')
377
            ->select('b', 'v')
378
            ->innerJoin('b.node', 'n', 'WITH', 'b.node = n.id')
379
            ->leftJoin(
380
                'b.publicNodeVersion',
381
                'v',
382
                'WITH',
383
                'b.publicNodeVersion = v.id'
384
            )
385
            ->where('n.parent IS NULL')
386
            ->andWhere('n.deleted != 1');
387
388
        return $qb->getQuery()->getResult();
389
    }
390
391
    /**
392
     * Create a node translation for a given node
393
     *
394
     * @param HasNodeInterface $hasNode The hasNode
395
     * @param string           $lang    The locale
396
     * @param Node             $node    The node
397
     * @param BaseUser         $owner   The user
398
     *
399
     * @throws \InvalidArgumentException
400
     *
401
     * @return NodeTranslation
402
     */
403
    public function createNodeTranslationFor(
404
        HasNodeInterface $hasNode,
405
        $lang,
406
        Node $node,
407
        BaseUser $owner
408
    ) {
409
        $em = $this->getEntityManager();
410
        $className = ClassLookup::getClass($hasNode);
411
        if (!$hasNode->getId() > 0) {
412
            throw new \InvalidArgumentException('The entity of class ' . $className . ' has no id, maybe you forgot to flush first');
413
        }
414
415
        $nodeTranslation = new NodeTranslation();
416
        $nodeTranslation
417
            ->setNode($node)
418
            ->setLang($lang)
419
            ->setTitle($hasNode->getTitle())
420
            ->setOnline(false)
421
            ->setWeight(0);
422
423
        $em->persist($nodeTranslation);
424
425
        $nodeVersion = $em->getRepository(NodeVersion::class)
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method createNodeVersionFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Rep...y\NodeVersionRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
426
            ->createNodeVersionFor(
427
                $hasNode,
428
                $nodeTranslation,
429
                $owner,
430
                null
431
            );
432
433
        $nodeTranslation->setPublicNodeVersion($nodeVersion);
434
        $em->persist($nodeTranslation);
435
        $em->flush();
436
        $em->refresh($nodeTranslation);
437
        $em->refresh($node);
438
439
        return $nodeTranslation;
440
    }
441
442
    /**
443
     * Add a draft node version for a given node
444
     *
445
     * @param HasNodeInterface $hasNode The hasNode
446
     * @param string           $lang    The locale
447
     * @param Node             $node    The node
448
     * @param BaseUser         $owner   The user
449
     *
450
     * @throws \InvalidArgumentException
451
     *
452
     * @return NodeTranslation
0 ignored issues
show
Documentation introduced by
Should the return type not be NodeTranslation|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
453
     */
454
    public function addDraftNodeVersionFor(
455
        HasNodeInterface $hasNode,
456
        $lang,
457
        Node $node,
458
        BaseUser $owner
459
    ) {
460
        $em = $this->getEntityManager();
461
        $className = ClassLookup::getClass($hasNode);
462
        if (!$hasNode->getId() > 0) {
463
            throw new \InvalidArgumentException('The entity of class ' . $className . ' has no id, maybe you forgot to flush first');
464
        }
465
466
        $nodeTranslation = $em->getRepository(NodeTranslation::class)->findOneBy(['lang' => $lang, 'node' => $node]);
467
468
        $em->getRepository(NodeVersion::class)
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectRepository as the method createNodeVersionFor() does only exist in the following implementations of said interface: Kunstmaan\NodeBundle\Rep...y\NodeVersionRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
469
            ->createNodeVersionFor(
470
                $hasNode,
471
                $nodeTranslation,
472
                $owner,
473
                null,
474
                NodeVersion::DRAFT_VERSION
475
            );
476
477
        $em->refresh($nodeTranslation);
0 ignored issues
show
Bug introduced by
It seems like $nodeTranslation defined by $em->getRepository(\Kuns...lang, 'node' => $node)) on line 466 can also be of type null; however, Doctrine\ORM\EntityManager::refresh() does only seem to accept object, 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...
478
        $em->refresh($node);
479
480
        return $nodeTranslation;
481
    }
482
483
    /**
484
     * Find best match for given URL and locale
485
     *
486
     * @param string $urlSlug The slug
487
     * @param string $locale  The locale
488
     *
489
     * @return NodeTranslation
490
     */
491
    public function getBestMatchForUrl($urlSlug, $locale)
492
    {
493
        $em = $this->getEntityManager();
494
495
        $rsm = new ResultSetMappingBuilder($em);
496
        $rsm->addRootEntityFromClassMetadata(
497
            'Kunstmaan\NodeBundle\Entity\NodeTranslation',
498
            'nt'
499
        );
500
501
        $query = $em
502
            ->createNativeQuery(
503
                'select nt.*
504
                from kuma_node_translations nt
505
                join kuma_nodes n on n.id = nt.node_id
506
                where n.deleted = 0 and nt.lang = :lang and locate(nt.url, :url) = 1
507
                order by length(nt.url) desc limit 1',
508
                $rsm
509
            );
510
        $query->setParameter('lang', $locale);
511
        $query->setParameter('url', $urlSlug);
512
513
        return $query->getOneOrNullResult();
514
    }
515
516
    /**
517
     * Test if all parents of the specified NodeTranslation have a node
518
     * translation for the specified language
519
     *
520
     * @param NodeTranslation $nodeTranslation The node translation
521
     * @param string          $language        The locale
522
     *
523
     * @return bool
524
     */
525
    public function hasParentNodeTranslationsForLanguage(
526
        NodeTranslation $nodeTranslation,
527
        $language
528
    ) {
529
        $parentNode = $nodeTranslation->getNode()->getParent();
530
        if ($parentNode !== null) {
531
            $parentNodeTranslation = $parentNode->getNodeTranslation(
532
                $language,
533
                true
534
            );
535
            if ($parentNodeTranslation !== null) {
536
                return $this->hasParentNodeTranslationsForLanguage(
537
                    $parentNodeTranslation,
538
                    $language
539
                );
540
            }
541
542
            return false;
543
        }
544
545
        return true;
546
    }
547
548
    /**
549
     * This will return 1 NodeTranslation by default (if one exists).
550
     * Just give it the internal name as defined on the Node in the database
551
     * and the language.
552
     *
553
     * It'll only return the latest version. It'll also hide deleted & offline
554
     * nodes.
555
     *
556
     * @param $language
557
     * @param $internalName
558
     */
559
    public function getNodeTranslationByLanguageAndInternalName(
560
        $language,
561
        $internalName
562
    ) {
563
        $qb = $this->createQueryBuilder('nt')
564
            ->select('nt', 'v')
565
            ->innerJoin('nt.node', 'n', 'WITH', 'nt.node = n.id')
566
            ->leftJoin(
567
                'nt.publicNodeVersion',
568
                'v',
569
                'WITH',
570
                'nt.publicNodeVersion = v.id'
571
            )
572
            ->where('n.deleted != 1')
573
            ->andWhere('nt.online = 1')
574
            ->setFirstResult(0)
575
            ->setMaxResults(1);
576
577
        $qb->andWhere('nt.lang = :lang')
578
            ->setParameter('lang', $language);
579
580
        $qb->andWhere('n.internalName = :internal_name')
581
            ->setParameter('internal_name', $internalName);
582
583
        return $qb->getQuery()->getOneOrNullResult();
584
    }
585
586
    public function getAllNodeTranslationsByRefEntityName($refEntityName)
587
    {
588
        $qb = $this->createQueryBuilder('nt')
589
            ->select('nt,n')
590
            ->innerJoin('nt.publicNodeVersion', 'nv')
591
            ->innerJoin('nt.node', 'n')
592
            ->where('nv.refEntityName = :refEntityName')
593
            ->setParameter('refEntityName', $refEntityName);
594
595
        return $qb->getQuery()->getResult();
596
    }
597
598
    public function getParentNodeTranslation(NodeTranslation $nodeTranslation)
599
    {
600
        $parent = $nodeTranslation->getNode()->getParent();
601
        if (\is_null($parent)) {
602
            return null;
603
        }
604
605
        $qb = $this->createQueryBuilder('nt')
606
            ->select('nt,n')
607
            ->innerJoin('nt.publicNodeVersion', 'nv')
608
            ->innerJoin('nt.node', 'n')
609
            ->where('nt.node = :parent')
610
            ->andWhere('n.deleted = 0')
611
            ->andWhere('nt.lang = :lang')
612
            ->setParameter('parent', $parent)
613
            ->setParameter('lang', $nodeTranslation->getLang());
614
615
        return $qb->getQuery()->getOneOrNullResult();
616
    }
617
}
618