Completed
Push — master ( 6d6774...64f3ed )
by Jeroen
11:23 queued 05:13
created

Repository/NodeTranslationRepository.php (2 issues)

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
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
     * @param int    $nodeId
24
     * @param string $lang
25
     *
26
     * @return array_shift($result)
27
     */
28
    public function getNodeTranslationByNodeIdQueryBuilder($nodeId, $lang)
29
    {
30
        $qb = $this->createQueryBuilder('nt')
31
            ->select('nt')
32
            ->innerJoin('nt.node', 'n', 'WITH', 'nt.node = n.id')
33
            ->where('n.deleted != 1')
34
            ->andWhere('nt.online = 1')
35
            ->andWhere('nt.lang = :lang')
36
            ->setParameter('lang', $lang)
37
            ->andWhere('n.id = :node_id')
38
            ->setParameter('node_id', $nodeId)
39
            ->setFirstResult(0)
40
            ->setMaxResults(1);
41
42
        return $qb->getQuery()->getOneOrNullResult();
43
    }
44
45
    /**
46
     * Get max children weight
47
     *
48
     * @param Node   $parentNode
49
     * @param string $lang       (optional) Only return max weight for the
50
     *                           given language
51
     *
52
     * @return int
53
     */
54
    public function getMaxChildrenWeight(Node $parentNode = null, $lang = null)
55
    {
56
        $maxWeight = $this->getNodeTranslationsQueryBuilder($lang)
57
            ->select('max(nt.weight)')
58
            ->andWhere('n.parent = :parentNode')
59
            ->setParameter('parentNode', $parentNode)
60
            ->getQuery()
61
            ->getSingleScalarResult();
62
63
        return (int) $maxWeight;
64
    }
65
66
    /**
67
     * QueryBuilder to fetch node translations (ignoring nodes that have been
68
     * deleted)
69
     *
70
     * @param string $lang (optional) Only return NodeTranslations for the
71
     *                     given language
72
     *
73
     * @return \Doctrine\ORM\QueryBuilder
74
     */
75
    public function getNodeTranslationsQueryBuilder($lang = null)
76
    {
77
        $queryBuilder = $this->createQueryBuilder('nt')
78
            ->select('nt,n,v')
79
            ->innerJoin('nt.node', 'n')
80
            ->leftJoin(
81
                'nt.publicNodeVersion',
82
                'v',
83
                'WITH',
84
                'nt.publicNodeVersion = v.id'
85
            )
86
            ->where('n.deleted = false')
87
            ->orderBy('nt.weight')
88
            ->addOrderBy('nt.weight');
89
90
        if (!empty($lang)) {
91
            $queryBuilder
92
                ->andWhere('nt.lang = :lang')
93
                ->setParameter('lang', $lang);
94
        }
95
96
        return $queryBuilder;
97
    }
98
99
    /**
100
     * QueryBuilder to fetch node translations that are currently published
101
     * (ignoring nodes that have been deleted)
102
     *
103
     * @param string $lang (optional) Only return NodeTranslations for the
104
     *                     given language
105
     *
106
     * @return \Doctrine\ORM\QueryBuilder
107
     */
108
    public function getOnlineNodeTranslationsQueryBuilder($lang = null)
109
    {
110
        return $this->getNodeTranslationsQueryBuilder($lang)
111
            ->andWhere('nt.online = true');
112
    }
113
114
    /**
115
     * QueryBuilder to fetch immediate child NodeTranslations for a specific
116
     * node and (optional) language
117
     *
118
     * @return \Doctrine\ORM\QueryBuilder
119
     */
120
    public function getChildrenQueryBuilder(Node $parent, $lang = null)
121
    {
122
        return $this->getNodeTranslationsQueryBuilder($lang)
123
            ->andWhere('n.parent = :parent')
124
            ->setParameter('parent', $parent);
125
    }
126
127
    /**
128
     * QueryBuilder to fetch immediate child NodeTranslations for a specific
129
     * node and (optional) language that are currently published
130
     *
131
     * @return \Doctrine\ORM\QueryBuilder
132
     */
133
    public function getOnlineChildrenQueryBuilder(Node $parent, $lang = null)
134
    {
135
        return $this->getChildrenQueryBuilder($parent, $lang)
136
            ->andWhere('nt.online = true');
137
    }
138
139
    /**
140
     * Get all online child node translations for a given node and (optional)
141
     * language
142
     *
143
     * @param Node   $parent
144
     * @param string $lang   (optional, if not specified all languages will be
145
     *                       returned)
146
     *
147
     * @return array
148
     */
149
    public function getOnlineChildren(Node $parent, $lang = null)
150
    {
151
        return $this->getOnlineChildrenQueryBuilder($parent, $lang)
152
            ->getQuery()->getResult();
153
    }
154
155
    /**
156
     * Finds all nodetranslations where title is like the given $title parameter
157
     *
158
     *
159
     * @param string $title
160
     * @param string $lang  (optional, if not specified all languages will be
161
     *                      returned)
162
     *
163
     * @return array
164
     */
165
    public function getNodeTranslationsLikeTitle($title, $lang = null)
166
    {
167
        /** @var QueryBuilder $qb */
168
        $qb = $this->getNodeTranslationsQueryBuilder($lang);
169
        $qb->andWhere('nt.title like :title')
170
            ->setParameter('title', '%' . $title . '%');
171
172
        return $qb->getQuery()->getResult();
173
    }
174
175
    /**
176
     * Get the node translation for a node
177
     *
178
     * @param HasNodeInterface $hasNode
179
     *
180
     * @return NodeTranslation
181
     */
182
    public function getNodeTranslationFor(HasNodeInterface $hasNode)
183
    {
184
        /* @var NodeVersion $nodeVersion */
185
        $nodeVersion = $this->getEntityManager()
186
            ->getRepository('KunstmaanNodeBundle:NodeVersion')
187
            ->getNodeVersionFor($hasNode);
188
189
        if (!\is_null($nodeVersion)) {
190
            return $nodeVersion->getNodeTranslation();
191
        }
192
193
        return null;
194
    }
195
196
    /**
197
     * Get the node translation for a given slug string
198
     *
199
     * @param string               $slug       The slug
200
     * @param NodeTranslation|null $parentNode The parentnode
201
     *
202
     * @return NodeTranslation|null
203
     */
204
    public function getNodeTranslationForSlug(
205
        $slug,
206
        NodeTranslation $parentNode = null
207
    ) {
208
        if (empty($slug)) {
209
            return $this->getNodeTranslationForSlugPart(null, $slug);
210
        }
211
212
        $slugParts = explode('/', $slug);
213
        $result = $parentNode;
214
        foreach ($slugParts as $slugPart) {
215
            $result = $this->getNodeTranslationForSlugPart($result, $slugPart);
216
        }
217
218
        return $result;
219
    }
220
221
    /**
222
     * Returns the node translation for a given slug
223
     *
224
     * @param NodeTranslation|null $parentNode The parentNode
225
     * @param string               $slugPart   The slug part
226
     *
227
     * @return NodeTranslation|null
228
     */
229
    private function getNodeTranslationForSlugPart(
230
        NodeTranslation $parentNode = null,
231
        $slugPart = ''
232
    ) {
233
        $qb = $this->createQueryBuilder('t')
234
            ->select('t', 'v', 'n')
235
            ->innerJoin('t.node', 'n', 'WITH', 't.node = n.id')
236
            ->leftJoin(
237
                't.publicNodeVersion',
238
                'v',
239
                'WITH',
240
                't.publicNodeVersion = v.id'
241
            )
242
            ->where('n.deleted != 1')
243
            ->setFirstResult(0)
244
            ->setMaxResults(1);
245
246
        if ($parentNode !== null) {
247
            $qb->andWhere('t.slug = :slug')
248
                ->andWhere('n.parent = :parent')
249
                ->setParameter('slug', $slugPart)
250
                ->setParameter('parent', $parentNode->getNode()->getId());
251
        } else {
252
            /* if parent is null we should look for slugs that have no parent */
253
            $qb->andWhere('n.parent IS NULL');
254
            if (empty($slugPart)) {
255
                $qb->andWhere('t.slug is NULL');
256
            } else {
257
                $qb->andWhere('t.slug = :slug');
258
                $qb->setParameter('slug', $slugPart);
259
            }
260
        }
261
262
        return $qb->getQuery()->getOneOrNullResult();
263
    }
264
265
    /**
266
     * Get the node translation for a given url
267
     *
268
     * @param string          $urlSlug        The full url
269
     * @param string          $locale         The locale
270
     * @param bool            $includeDeleted Include deleted nodes
271
     * @param NodeTranslation $toExclude      Optional NodeTranslation instance
272
     *                                        you wish to exclude
273
     * @param Node            $rootNode       Optional Root node of the tree you
274
     *                                        wish to use
275
     *
276
     * @return array
277
     */
278
    public function getAllNodeTranslationsForUrl(
279
        $urlSlug,
280
        $locale = '',
281
        $includeDeleted = false,
282
        NodeTranslation $toExclude = null,
283
        Node $rootNode = null
284
    ) {
285
        $qb = $this->createQueryBuilder('b')
286
            ->select('b', 'v')
287
            ->innerJoin('b.node', 'n', 'WITH', 'b.node = n.id')
288
            ->leftJoin(
289
                'b.publicNodeVersion',
290
                'v',
291
                'WITH',
292
                'b.publicNodeVersion = v.id'
293
            )
294
            ->addOrderBy('b.online', 'DESC')
295
            ->setFirstResult(0)
296
            ->setMaxResults(1);
297
298
        if (!$includeDeleted) {
299
            $qb->andWhere('n.deleted = 0');
300
        }
301
302
        if (!empty($locale)) {
303
            $qb->andWhere('b.lang = :lang')
304
                ->setParameter('lang', $locale);
305
        }
306
307
        if (empty($urlSlug)) {
308
            $qb->andWhere('b.url IS NULL');
309
        } else {
310
            $qb->andWhere('b.url = :url');
311
            $qb->setParameter('url', $urlSlug);
312
        }
313
314
        if (!\is_null($toExclude)) {
315
            $qb->andWhere('NOT b.id = :exclude_id')
316
                ->setParameter('exclude_id', $toExclude->getId());
317
        }
318
319
        if ($rootNode) {
320
            $qb->andWhere('n.lft >= :left')
321
                ->andWhere('n.rgt <= :right')
322
                ->setParameter('left', $rootNode->getLeft())
323
                ->setParameter('right', $rootNode->getRight());
324
        }
325
326
        return $qb->getQuery()->getResult();
327
    }
328
329
    /**
330
     * Get the node translation for a given url
331
     *
332
     * @param string          $urlSlug        The full url
333
     * @param string          $locale         The locale
334
     * @param bool            $includeDeleted Include deleted nodes
335
     * @param NodeTranslation $toExclude      Optional NodeTranslation instance
336
     *                                        you wish to exclude
337
     * @param Node            $rootNode       Optional Root node of the tree you
338
     *                                        wish to use
339
     *
340
     * @return NodeTranslation|null
341
     */
342
    public function getNodeTranslationForUrl(
343
        $urlSlug,
344
        $locale = '',
345
        $includeDeleted = false,
346
        NodeTranslation $toExclude = null,
347
        Node $rootNode = null
348
    ) {
349
        $translations = $this->getAllNodeTranslationsForUrl($urlSlug, $locale, $includeDeleted, $toExclude, $rootNode);
350
351
        if (empty($translations)) {
352
            return null;
353
        }
354
355
        return $translations[0];
356
    }
357
358
    /**
359
     * Get all top node translations
360
     *
361
     * @return NodeTranslation[]
362
     */
363
    public function getTopNodeTranslations()
364
    {
365
        $qb = $this->createQueryBuilder('b')
366
            ->select('b', 'v')
367
            ->innerJoin('b.node', 'n', 'WITH', 'b.node = n.id')
368
            ->leftJoin(
369
                'b.publicNodeVersion',
370
                'v',
371
                'WITH',
372
                'b.publicNodeVersion = v.id'
373
            )
374
            ->where('n.parent IS NULL')
375
            ->andWhere('n.deleted != 1');
376
377
        return $qb->getQuery()->getResult();
378
    }
379
380
    /**
381
     * Create a node translation for a given node
382
     *
383
     * @param HasNodeInterface $hasNode The hasNode
384
     * @param string           $lang    The locale
385
     * @param Node             $node    The node
386
     * @param BaseUser         $owner   The user
387
     *
388
     * @throws \InvalidArgumentException
389
     *
390
     * @return NodeTranslation
391
     */
392
    public function createNodeTranslationFor(
393
        HasNodeInterface $hasNode,
394
        $lang,
395
        Node $node,
396
        BaseUser $owner
397
    ) {
398
        $em = $this->getEntityManager();
399
        $className = ClassLookup::getClass($hasNode);
400
        if (!$hasNode->getId() > 0) {
401
            throw new \InvalidArgumentException('The entity of class ' . $className . ' has no id, maybe you forgot to flush first');
402
        }
403
404
        $nodeTranslation = new NodeTranslation();
405
        $nodeTranslation
406
            ->setNode($node)
407
            ->setLang($lang)
408
            ->setTitle($hasNode->getTitle())
409
            ->setOnline(false)
410
            ->setWeight(0);
411
412
        $em->persist($nodeTranslation);
413
414
        $nodeVersion = $em->getRepository('KunstmaanNodeBundle:NodeVersion')
415
            ->createNodeVersionFor(
416
                $hasNode,
417
                $nodeTranslation,
418
                $owner,
419
                null
420
            );
421
422
        $nodeTranslation->setPublicNodeVersion($nodeVersion);
423
        $em->persist($nodeTranslation);
424
        $em->flush();
425
        $em->refresh($nodeTranslation);
426
        $em->refresh($node);
427
428
        return $nodeTranslation;
429
    }
430
431
    /**
432
     * Add a draft node version for a given node
433
     *
434
     * @param HasNodeInterface $hasNode The hasNode
435
     * @param string           $lang    The locale
436
     * @param Node             $node    The node
437
     * @param BaseUser         $owner   The user
438
     *
439
     * @throws \InvalidArgumentException
440
     *
441
     * @return NodeTranslation
442
     */
443
    public function addDraftNodeVersionFor(
444
        HasNodeInterface $hasNode,
445
        $lang,
446
        Node $node,
447
        BaseUser $owner
448
    ) {
449
        $em = $this->getEntityManager();
450
        $className = ClassLookup::getClass($hasNode);
451
        if (!$hasNode->getId() > 0) {
452
            throw new \InvalidArgumentException('The entity of class ' . $className . ' has no id, maybe you forgot to flush first');
453
        }
454
455
        $nodeTranslation = $em->getRepository('KunstmaanNodeBundle:NodeTranslation')->findOneBy(['lang' => $lang, 'node' => $node]);
456
457
        $em->getRepository('KunstmaanNodeBundle:NodeVersion')
0 ignored issues
show
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...
458
            ->createNodeVersionFor(
459
                $hasNode,
460
                $nodeTranslation,
461
                $owner,
462
                null,
463
                NodeVersion::DRAFT_VERSION
464
            );
465
466
        $em->refresh($nodeTranslation);
0 ignored issues
show
It seems like $nodeTranslation defined by $em->getRepository('Kuns...lang, 'node' => $node)) on line 455 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...
467
        $em->refresh($node);
468
469
        return $nodeTranslation;
470
    }
471
472
    /**
473
     * Find best match for given URL and locale
474
     *
475
     * @param string $urlSlug The slug
476
     * @param string $locale  The locale
477
     *
478
     * @return NodeTranslation
479
     */
480
    public function getBestMatchForUrl($urlSlug, $locale)
481
    {
482
        $em = $this->getEntityManager();
483
484
        $rsm = new ResultSetMappingBuilder($em);
485
        $rsm->addRootEntityFromClassMetadata(
486
            'Kunstmaan\NodeBundle\Entity\NodeTranslation',
487
            'nt'
488
        );
489
490
        $query = $em
491
            ->createNativeQuery(
492
                'select nt.*
493
                from kuma_node_translations nt
494
                join kuma_nodes n on n.id = nt.node_id
495
                where n.deleted = 0 and nt.lang = :lang and locate(nt.url, :url) = 1
496
                order by length(nt.url) desc limit 1',
497
                $rsm
498
            );
499
        $query->setParameter('lang', $locale);
500
        $query->setParameter('url', $urlSlug);
501
502
        return $query->getOneOrNullResult();
503
    }
504
505
    /**
506
     * Test if all parents of the specified NodeTranslation have a node
507
     * translation for the specified language
508
     *
509
     * @param NodeTranslation $nodeTranslation The node translation
510
     * @param string          $language        The locale
511
     *
512
     * @return bool
513
     */
514
    public function hasParentNodeTranslationsForLanguage(
515
        NodeTranslation $nodeTranslation,
516
        $language
517
    ) {
518
        $parentNode = $nodeTranslation->getNode()->getParent();
519
        if ($parentNode !== null) {
520
            $parentNodeTranslation = $parentNode->getNodeTranslation(
521
                $language,
522
                true
523
            );
524
            if ($parentNodeTranslation !== null) {
525
                return $this->hasParentNodeTranslationsForLanguage(
526
                    $parentNodeTranslation,
527
                    $language
528
                );
529
            }
530
531
            return false;
532
        }
533
534
        return true;
535
    }
536
537
    /**
538
     * This will return 1 NodeTranslation by default (if one exists).
539
     * Just give it the internal name as defined on the Node in the database
540
     * and the language.
541
     *
542
     * It'll only return the latest version. It'll also hide deleted & offline
543
     * nodes.
544
     *
545
     * @param $language
546
     * @param $internalName
547
     */
548
    public function getNodeTranslationByLanguageAndInternalName(
549
        $language,
550
        $internalName
551
    ) {
552
        $qb = $this->createQueryBuilder('nt')
553
            ->select('nt', 'v')
554
            ->innerJoin('nt.node', 'n', 'WITH', 'nt.node = n.id')
555
            ->leftJoin(
556
                'nt.publicNodeVersion',
557
                'v',
558
                'WITH',
559
                'nt.publicNodeVersion = v.id'
560
            )
561
            ->where('n.deleted != 1')
562
            ->andWhere('nt.online = 1')
563
            ->setFirstResult(0)
564
            ->setMaxResults(1);
565
566
        $qb->andWhere('nt.lang = :lang')
567
            ->setParameter('lang', $language);
568
569
        $qb->andWhere('n.internalName = :internal_name')
570
            ->setParameter('internal_name', $internalName);
571
572
        return $qb->getQuery()->getOneOrNullResult();
573
    }
574
575
    public function getAllNodeTranslationsByRefEntityName($refEntityName)
576
    {
577
        $qb = $this->createQueryBuilder('nt')
578
            ->select('nt,n')
579
            ->innerJoin('nt.publicNodeVersion', 'nv')
580
            ->innerJoin('nt.node', 'n')
581
            ->where('nv.refEntityName = :refEntityName')
582
            ->setParameter('refEntityName', $refEntityName);
583
584
        return $qb->getQuery()->getResult();
585
    }
586
587
    public function getParentNodeTranslation(NodeTranslation $nodeTranslation)
588
    {
589
        $parent = $nodeTranslation->getNode()->getParent();
590
        if (\is_null($parent)) {
591
            return null;
592
        }
593
594
        $qb = $this->createQueryBuilder('nt')
595
            ->select('nt,n')
596
            ->innerJoin('nt.publicNodeVersion', 'nv')
597
            ->innerJoin('nt.node', 'n')
598
            ->where('nt.node = :parent')
599
            ->andWhere('n.deleted = 0')
600
            ->andWhere('nt.lang = :lang')
601
            ->setParameter('parent', $parent)
602
            ->setParameter('lang', $nodeTranslation->getLang());
603
604
        return $qb->getQuery()->getOneOrNullResult();
605
    }
606
}
607