Completed
Push — master ( 8e33ff...1a8186 )
by Kamil
18:07
created

Product::getTranslation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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
declare(strict_types=1);
13
14
namespace Sylius\Component\Product\Model;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Collections\Collection;
18
use Sylius\Component\Attribute\Model\AttributeValueInterface;
19
use Sylius\Component\Resource\Model\TimestampableTrait;
20
use Sylius\Component\Resource\Model\ToggleableTrait;
21
use Sylius\Component\Resource\Model\TranslatableTrait;
22
use Sylius\Component\Resource\Model\TranslationInterface;
23
use Webmozart\Assert\Assert;
24
25
class Product implements ProductInterface
26
{
27
    use TimestampableTrait, ToggleableTrait;
28
    use TranslatableTrait {
29
        __construct as private initializeTranslationsCollection;
30
        getTranslation as private doGetTranslation;
31
    }
32
33
    /**
34
     * @var mixed
35
     */
36
    protected $id;
37
38
    /**
39
     * @var string
40
     */
41
    protected $code;
42
43
    /**
44
     * @var Collection|AttributeValueInterface[]
45
     */
46
    protected $attributes;
47
48
    /**
49
     * @var Collection|ProductVariantInterface[]
50
     */
51
    protected $variants;
52
53
    /**
54
     * @var Collection|ProductOptionInterface[]
55
     */
56
    protected $options;
57
58
    /**
59
     * @var Collection|ProductAssociationInterface[]
60
     */
61
    protected $associations;
62
63
    public function __construct()
64
    {
65
        $this->initializeTranslationsCollection();
66
67
        $this->createdAt = new \DateTime();
68
        $this->attributes = new ArrayCollection();
69
        $this->associations = new ArrayCollection();
70
        $this->variants = new ArrayCollection();
71
        $this->options = new ArrayCollection();
72
    }
73
74
    /**
75
     * @return string
76
     */
77
    public function __toString(): string
78
    {
79
        return (string) $this->getName();
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getId()
86
    {
87
        return $this->id;
88
    }
89
90
    /**
91
     * @return string
92
     */
93
    public function getCode(): ?string
94
    {
95
        return $this->code;
96
    }
97
98
    /**
99
     * @param string $code
100
     */
101
    public function setCode(?string $code): void
102
    {
103
        $this->code = $code;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getName(): ?string
110
    {
111
        return $this->getTranslation()->getName();
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function setName(?string $name): void
118
    {
119
        $this->getTranslation()->setName($name);
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function getSlug(): ?string
126
    {
127
        return $this->getTranslation()->getSlug();
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function setSlug(?string $slug): void
134
    {
135
        $this->getTranslation()->setSlug($slug);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function getDescription(): ?string
142
    {
143
        return $this->getTranslation()->getDescription();
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function setDescription(?string $description): void
150
    {
151
        $this->getTranslation()->setDescription($description);
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function getMetaKeywords(): ?string
158
    {
159
        return $this->getTranslation()->getMetaKeywords();
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function setMetaKeywords(?string $metaKeywords): void
166
    {
167
        $this->getTranslation()->setMetaKeywords($metaKeywords);
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function getMetaDescription(): ?string
174
    {
175
        return $this->getTranslation()->getMetaDescription();
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function setMetaDescription(?string $metaDescription): void
182
    {
183
        $this->getTranslation()->setMetaDescription($metaDescription);
184
    }
185
186
    public function getAttributes(): Collection
187
    {
188
        return $this->attributes;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function getAttributesByLocale(
195
        string $localeCode,
196
        string $fallbackLocaleCode,
197
        ?string $baseLocaleCode = null
198
    ): Collection {
199
        if (null === $baseLocaleCode || $baseLocaleCode === $fallbackLocaleCode) {
200
            $baseLocaleCode = $fallbackLocaleCode;
201
            $fallbackLocaleCode = null;
202
        }
203
204
        $attributes = $this->attributes->filter(
205
            function (ProductAttributeValueInterface $attribute) use ($baseLocaleCode) {
206
                return $attribute->getLocaleCode() === $baseLocaleCode;
207
            }
208
        );
209
210
        $attributesWithFallback = [];
211
        foreach ($attributes as $attribute) {
212
            $attributesWithFallback[] = $this->getAttributeInDifferentLocale($attribute, $localeCode, $fallbackLocaleCode);
213
        }
214
215
        return new ArrayCollection($attributesWithFallback);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function addAttribute(?AttributeValueInterface $attribute): void
222
    {
223
        Assert::isInstanceOf(
224
            $attribute,
225
            ProductAttributeValueInterface::class,
226
            'Attribute objects added to a Product object have to implement ProductAttributeValueInterface'
227
        );
228
229
        if (!$this->hasAttribute($attribute)) {
230
            $attribute->setProduct($this);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Attribu...AttributeValueInterface as the method setProduct() does only exist in the following implementations of said interface: Sylius\Component\Product...l\ProductAttributeValue.

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...
231
            $this->attributes->add($attribute);
232
        }
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function removeAttribute(?AttributeValueInterface $attribute): void
239
    {
240
        Assert::isInstanceOf(
241
            $attribute,
242
            ProductAttributeValueInterface::class,
243
            'Attribute objects removed from a Product object have to implement ProductAttributeValueInterface'
244
        );
245
246
        if ($this->hasAttribute($attribute)) {
247
            $this->attributes->removeElement($attribute);
248
            $attribute->setProduct(null);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Attribu...AttributeValueInterface as the method setProduct() does only exist in the following implementations of said interface: Sylius\Component\Product...l\ProductAttributeValue.

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...
249
        }
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function hasAttribute(AttributeValueInterface $attribute): bool
256
    {
257
        return $this->attributes->contains($attribute);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function hasAttributeByCodeAndLocale(string $attributeCode, ?string $localeCode = null): bool
264
    {
265
        $localeCode = $localeCode ?: $this->getTranslation()->getLocale();
266
267
        foreach ($this->attributes as $attribute) {
268
            if ($attribute->getAttribute()->getCode() === $attributeCode
269
                && $attribute->getLocaleCode() === $localeCode) {
270
                return true;
271
            }
272
        }
273
274
        return false;
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280
    public function getAttributeByCodeAndLocale(string $attributeCode, ?string $localeCode = null): ?AttributeValueInterface
281
    {
282
        if (null === $localeCode) {
283
            $localeCode = $this->getTranslation()->getLocale();
284
        }
285
286
        foreach ($this->attributes as $attribute) {
287
            if ($attribute->getAttribute()->getCode() === $attributeCode &&
288
                $attribute->getLocaleCode() === $localeCode) {
289
                return $attribute;
290
            }
291
        }
292
293
        return null;
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function hasVariants(): bool
300
    {
301
        return !$this->getVariants()->isEmpty();
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    public function getVariants(): Collection
308
    {
309
        return $this->variants;
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    public function addVariant(ProductVariantInterface $variant): void
316
    {
317
        if (!$this->hasVariant($variant)) {
318
            $variant->setProduct($this);
319
            $this->variants->add($variant);
320
        }
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326
    public function removeVariant(ProductVariantInterface $variant): void
327
    {
328
        if ($this->hasVariant($variant)) {
329
            $variant->setProduct(null);
330
            $this->variants->removeElement($variant);
331
        }
332
    }
333
334
    /**
335
     * {@inheritdoc}
336
     */
337
    public function hasVariant(ProductVariantInterface $variant): bool
338
    {
339
        return $this->variants->contains($variant);
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function hasOptions(): bool
346
    {
347
        return !$this->options->isEmpty();
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     */
353
    public function getOptions(): Collection
354
    {
355
        return $this->options;
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361
    public function addOption(ProductOptionInterface $option): void
362
    {
363
        if (!$this->hasOption($option)) {
364
            $this->options->add($option);
365
        }
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371
    public function removeOption(ProductOptionInterface $option): void
372
    {
373
        if ($this->hasOption($option)) {
374
            $this->options->removeElement($option);
375
        }
376
    }
377
378
    /**
379
     * {@inheritdoc}
380
     */
381
    public function hasOption(ProductOptionInterface $option): bool
382
    {
383
        return $this->options->contains($option);
384
    }
385
386
    /**
387
     * {@inheritdoc}
388
     */
389
    public function getAssociations(): Collection
390
    {
391
        return $this->associations;
392
    }
393
394
    /**
395
     * {@inheritdoc}
396
     */
397
    public function addAssociation(ProductAssociationInterface $association): void
398
    {
399
        if (!$this->hasAssociation($association)) {
400
            $this->associations->add($association);
401
            $association->setOwner($this);
402
        }
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408
    public function removeAssociation(ProductAssociationInterface $association): void
409
    {
410
        if ($this->hasAssociation($association)) {
411
            $association->setOwner(null);
412
            $this->associations->removeElement($association);
413
        }
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419
    public function hasAssociation(ProductAssociationInterface $association): bool
420
    {
421
        return $this->associations->contains($association);
422
    }
423
424
    /**
425
     * {@inheritdoc}
426
     */
427
    public function isSimple(): bool
428
    {
429
        return 1 === $this->variants->count() && !$this->hasOptions();
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     */
435
    public function isConfigurable(): bool
436
    {
437
        return !$this->isSimple();
438
    }
439
440
    /**
441
     * @param string|null $locale
442
     *
443
     * @return ProductTranslationInterface
444
     */
445
    public function getTranslation(?string $locale = null): TranslationInterface
446
    {
447
        /** @var ProductTranslationInterface $translation */
448
        $translation = $this->doGetTranslation($locale);
449
450
        return $translation;
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456
    protected function createTranslation(): ProductTranslationInterface
457
    {
458
        return new ProductTranslation();
459
    }
460
461
    /**
462
     * @param ProductAttributeValueInterface $attributeValue
463
     * @param string $localeCode
464
     * @param string|null $fallbackLocaleCode
465
     *
466
     * @return AttributeValueInterface
467
     */
468
    private function getAttributeInDifferentLocale(
469
        ProductAttributeValueInterface $attributeValue,
470
        string $localeCode,
471
        ?string $fallbackLocaleCode = null
472
    ): AttributeValueInterface {
473
        if (!$this->hasNotEmptyAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode)) {
474
            if (
475
                null !== $fallbackLocaleCode &&
476
                $this->hasNotEmptyAttributeByCodeAndLocale($attributeValue->getCode(), $fallbackLocaleCode)
477
            ) {
478
                return $this->getAttributeByCodeAndLocale($attributeValue->getCode(), $fallbackLocaleCode);
479
            }
480
481
            return $attributeValue;
482
        }
483
484
        return $this->getAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode);
485
    }
486
487
    /**
488
     * @param string $attributeCode
489
     * @param string $localeCode
490
     *
491
     * @return bool
492
     */
493
    private function hasNotEmptyAttributeByCodeAndLocale(string $attributeCode, string $localeCode): bool
494
    {
495
        $attributeValue = $this->getAttributeByCodeAndLocale($attributeCode, $localeCode);
496
        if (null === $attributeValue) {
497
            return false;
498
        }
499
500
        $value = $attributeValue->getValue();
501
        if ('' === $value || null === $value || [] === $value) {
502
            return false;
503
        }
504
505
        return true;
506
    }
507
}
508