StructureProvider::getMetadata()   F
last analyzed

Complexity

Conditions 24
Paths 4032

Size

Total Lines 178

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 178
rs 0
c 0
b 0
f 0
cc 24
nc 4032
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) MASSIVE ART WebServices GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Bundle\PageBundle\Search\Metadata;
13
14
use Massive\Bundle\SearchBundle\Search\Document;
15
use Massive\Bundle\SearchBundle\Search\Factory;
16
use Massive\Bundle\SearchBundle\Search\Metadata\ComplexMetadata;
17
use Massive\Bundle\SearchBundle\Search\Metadata\Field\Expression;
18
use Massive\Bundle\SearchBundle\Search\Metadata\Field\Value;
19
use Massive\Bundle\SearchBundle\Search\Metadata\IndexMetadata;
20
use Massive\Bundle\SearchBundle\Search\Metadata\IndexMetadataInterface;
21
use Massive\Bundle\SearchBundle\Search\Metadata\ProviderInterface;
22
use Sulu\Component\Content\Document\Behavior\ExtensionBehavior;
23
use Sulu\Component\Content\Document\Behavior\LocalizedAuthorBehavior;
24
use Sulu\Component\Content\Document\Behavior\RedirectTypeBehavior;
25
use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
26
use Sulu\Component\Content\Document\Behavior\StructureBehavior;
27
use Sulu\Component\Content\Document\Behavior\WebspaceBehavior;
28
use Sulu\Component\Content\Document\Behavior\WorkflowStageBehavior;
29
use Sulu\Component\Content\Document\RedirectType;
30
use Sulu\Component\Content\Document\WorkflowStage;
31
use Sulu\Component\Content\Extension\ExtensionManagerInterface;
32
use Sulu\Component\Content\Metadata\BlockMetadata;
33
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactory;
34
use Sulu\Component\Content\Metadata\PropertyMetadata;
35
use Sulu\Component\Content\Metadata\StructureMetadata;
36
use Sulu\Component\DocumentManager\Behavior\Mapping\TitleBehavior;
37
use Sulu\Component\DocumentManager\Metadata;
38
use Sulu\Component\DocumentManager\Metadata\MetadataFactory;
39
40
/**
41
 * Provides a Metadata Driver for massive search-bundle.
42
 */
43
class StructureProvider implements ProviderInterface
44
{
45
    const FIELD_STRUCTURE_TYPE = '_structure_type';
46
47
    const FIELD_TEASER_DESCRIPTION = '_teaser_description';
48
49
    const FIELD_TEASER_MEDIA = '_teaser_media';
50
51
    /**
52
     * @var Factory
53
     */
54
    private $factory;
55
56
    /**
57
     * @var string
58
     */
59
    private $mapping;
60
61
    /**
62
     * @var StructureMetadataFactory
63
     */
64
    private $structureFactory;
65
66
    /**
67
     * @var ExtensionManagerInterface
68
     */
69
    private $extensionManager;
70
71
    /**
72
     * @var MetadataFactory
73
     */
74
    private $metadataFactory;
75
76
    /**
77
     * @param Factory $factory
78
     * @param MetadataFactory $metadataFactory
79
     * @param StructureMetadataFactory $structureFactory
80
     * @param ExtensionManagerInterface $extensionManager
81
     * @param array $mapping
82
     */
83
    public function __construct(
84
        Factory $factory,
85
        MetadataFactory $metadataFactory,
86
        StructureMetadataFactory $structureFactory,
87
        ExtensionManagerInterface $extensionManager,
88
        array $mapping = []
89
    ) {
90
        $this->factory = $factory;
91
        $this->mapping = $mapping;
0 ignored issues
show
Documentation Bug introduced by
It seems like $mapping of type array is incompatible with the declared type string of property $mapping.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
92
        $this->metadataFactory = $metadataFactory;
93
        $this->structureFactory = $structureFactory;
94
        $this->extensionManager = $extensionManager;
95
    }
96
97
    /**
98
     * loads metadata for a given class if its derived from StructureInterface.
99
     *
100
     * @param object $object
101
     *
102
     * @return IndexMetadataInterface|null
103
     */
104
    public function getMetadataForObject($object)
105
    {
106
        if (!$object instanceof StructureBehavior) {
107
            return;
108
        }
109
110
        $documentMetadata = $this->metadataFactory->getMetadataForClass(get_class($object));
111
        $structure = $this->structureFactory->getStructureMetadata(
112
            $documentMetadata->getAlias(),
113
            $object->getStructureType()
114
        );
115
116
        return $this->getMetadata($documentMetadata, $structure);
117
    }
118
119
    public function getMetadata(Metadata $documentMetadata, StructureMetadata $structure)
120
    {
121
        $classMetadata = $this->factory->createClassMetadata($documentMetadata->getClass());
122
        $class = $documentMetadata->getReflectionClass();
123
124
        $indexMeta = $this->factory->createIndexMetadata();
125
        $indexMeta->setIdField($this->factory->createMetadataField('uuid'));
126
        $indexMeta->setLocaleField($this->factory->createMetadataField('originalLocale'));
127
128
        $indexName = 'page';
129
        $decorate = false;
130
131
        // See if the mapping overrides the default index and category name
132
        foreach ($this->mapping as $className => $mapping) {
0 ignored issues
show
Bug introduced by
The expression $this->mapping of type string is not traversable.
Loading history...
133
            if ($documentMetadata->getAlias() !== $className &&
134
                $class->name !== $className &&
135
                false === $class->isSubclassOf($className)
136
            ) {
137
                continue;
138
            }
139
140
            $indexName = $mapping['index'];
141
            if ($mapping['decorate_index']) {
142
                $decorate = true;
143
            }
144
        }
145
146
        $indexMeta->setIndexName($this->createIndexNameField($documentMetadata, $indexName, $decorate));
147
148
        foreach ($structure->getProperties() as $property) {
149
            if ($property instanceof BlockMetadata) {
150
                $propertyMapping = new ComplexMetadata();
151
                foreach ($property->getComponents() as $component) {
152
                    foreach ($component->getChildren() as $componentProperty) {
153
                        if (false === $componentProperty->hasTag('sulu.search.field')) {
154
                            continue;
155
                        }
156
157
                        $tag = $componentProperty->getTag('sulu.search.field');
158
                        $tagAttributes = $tag['attributes'];
159
160
                        if (!isset($tagAttributes['index']) || 'false' !== $tagAttributes['index']) {
161
                            $propertyMapping->addFieldMapping(
162
                                $property->getName() . '.' . $componentProperty->getName(),
163
                                [
164
                                    'type' => isset($tagAttributes['type']) ? $tagAttributes['type'] : 'string',
165
                                    'field' => $this->factory->createMetadataProperty(
166
                                        '[' . $componentProperty->getName() . ']'
167
                                    ),
168
                                    'aggregate' => true,
169
                                    'indexed' => false,
170
                                ]
171
                            );
172
                        }
173
                    }
174
                }
175
176
                $indexMeta->addFieldMapping(
177
                    $property->getName(),
178
                    [
179
                        'type' => 'complex',
180
                        'mapping' => $propertyMapping,
181
                        'field' => $this->getContentField($property),
182
                    ]
183
                );
184
            } else {
185
                $this->mapProperty($property, $indexMeta);
186
            }
187
        }
188
189
        if ($class->isSubclassOf(ExtensionBehavior::class)) {
190
            $extensions = $this->extensionManager->getExtensions($structure->getName());
191
            foreach ($extensions as $extension) {
192
                foreach ($extension->getFieldMapping() as $name => $mapping) {
193
                    $indexMeta->addFieldMapping($name, $mapping);
194
                }
195
            }
196
        }
197
198
        if ($class->isSubclassOf(ResourceSegmentBehavior::class)) {
199
            $field = $this->factory->createMetadataField('resourceSegment');
200
            if ($class->isSubclassOf(RedirectTypeBehavior::class)) {
201
                $expression = <<<'EOT'
202
                    (object.getRedirectType() === %s
203
                        ? (object.getRedirectTarget() ? object.getRedirectTarget().getResourceSegment()) 
204
                        : (object.getRedirectType() === %s 
205
                            ? object.getRedirectExternal() 
206
                            : object.getResourceSegment()
207
                        )
208
                    )
209
EOT;
210
211
                $field = new Expression(
212
                    sprintf(
213
                        $expression,
214
                        RedirectType::INTERNAL,
215
                        RedirectType::EXTERNAL
216
                    )
217
                );
218
            }
219
220
            $indexMeta->setUrlField($field);
221
        }
222
223
        if (!$indexMeta->getTitleField()) {
224
            if ($class->isSubclassOf(TitleBehavior::class)) {
225
                $indexMeta->setTitleField($this->factory->createMetadataProperty('title'));
226
227
                $indexMeta->addFieldMapping(
228
                    'title',
229
                    [
230
                        'type' => 'string',
231
                        'field' => $this->factory->createMetadataField('title'),
232
                        'aggregate' => true,
233
                        'indexed' => false,
234
                    ]
235
                );
236
            }
237
        }
238
239
        if ($class->isSubclassOf(WebspaceBehavior::class)) {
240
            // index the webspace
241
            $indexMeta->addFieldMapping(
242
                'webspace_key',
243
                [
244
                    'type' => 'string',
245
                    'field' => $this->factory->createMetadataProperty('webspaceName'),
246
                ]
247
            );
248
        }
249
250
        if ($class->isSubclassOf(WorkflowStageBehavior::class)) {
251
            $indexMeta->addFieldMapping(
252
                'state',
253
                [
254
                    'type' => 'string',
255
                    'field' => $this->factory->createMetadataExpression(
256
                        'object.getWorkflowStage() == 1 ? "test" : "published"'
257
                    ),
258
                ]
259
            );
260
            $indexMeta->addFieldMapping(
261
                'published',
262
                [
263
                    'type' => 'date',
264
                    'field' => $this->factory->createMetadataExpression(
265
                        'object.getPublished()'
266
                    ),
267
                ]
268
            );
269
        }
270
271
        if ($class->isSubclassOf(LocalizedAuthorBehavior::class)) {
272
            $indexMeta->addFieldMapping(
273
                'authored',
274
                [
275
                    'type' => 'date',
276
                    'field' => $this->factory->createMetadataExpression(
277
                        'object.getAuthored()'
278
                    ),
279
                ]
280
            );
281
        }
282
283
        $indexMeta->addFieldMapping(
284
            self::FIELD_STRUCTURE_TYPE,
285
            [
286
                'type' => 'string',
287
                'stored' => true,
288
                'indexed' => true,
289
                'field' => $this->factory->createMetadataProperty('structureType'),
290
            ]
291
        );
292
293
        $classMetadata->addIndexMetadata('_default', $indexMeta);
294
295
        return $classMetadata;
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301
    public function getAllMetadata()
302
    {
303
        $metadatas = [];
304
        foreach ($this->metadataFactory->getAliases() as $alias) {
305
            $metadata = $this->metadataFactory->getMetadataForAlias($alias);
306
307
            if (!$this->structureFactory->hasStructuresFor($alias)) {
308
                continue;
309
            }
310
311
            foreach ($this->structureFactory->getStructures($alias) as $structure) {
312
                $structureMetadata = $this->getMetadata($metadata, $structure);
313
                $metadatas[] = $structureMetadata;
314
            }
315
        }
316
317
        return $metadatas;
318
    }
319
320
    /**
321
     * {@inheritdoc}
322
     */
323
    public function getMetadataForDocument(Document $document)
324
    {
325
        if (!$document->hasField(self::FIELD_STRUCTURE_TYPE)) {
326
            return;
327
        }
328
329
        $className = $document->getClass();
330
        $structureType = $document->getField(self::FIELD_STRUCTURE_TYPE)->getValue();
331
        $documentMetadata = $this->metadataFactory->getMetadataForClass($className);
332
        $structure = $this->structureFactory->getStructureMetadata($documentMetadata->getAlias(), $structureType);
333
334
        return $this->getMetadata($documentMetadata, $structure);
335
    }
336
337
    private function mapProperty(PropertyMetadata $property, $metadata)
338
    {
339
        if ($metadata instanceof IndexMetadata && $property->hasTag('sulu.teaser.description')) {
0 ignored issues
show
Bug introduced by
The class Massive\Bundle\SearchBun...\Metadata\IndexMetadata does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
340
            $this->mapTeaserDescription($property, $metadata);
341
        }
342
        if ($metadata instanceof IndexMetadata && $property->hasTag('sulu.teaser.media')) {
0 ignored issues
show
Bug introduced by
The class Massive\Bundle\SearchBun...\Metadata\IndexMetadata does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
343
            $this->mapTeaserMedia($property, $metadata);
344
        }
345
346
        if (false === $property->hasTag('sulu.search.field')) {
347
            return;
348
        }
349
350
        $tag = $property->getTag('sulu.search.field');
351
        $tagAttributes = $tag['attributes'];
352
353
        if ($metadata instanceof IndexMetadata && isset($tagAttributes['role'])) {
0 ignored issues
show
Bug introduced by
The class Massive\Bundle\SearchBun...\Metadata\IndexMetadata does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
354
            switch ($tagAttributes['role']) {
355 View Code Duplication
                case 'title':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
356
                    $metadata->setTitleField($this->getContentField($property));
357
                    $metadata->addFieldMapping(
358
                        $property->getName(),
359
                        [
360
                            'field' => $this->getContentField($property),
361
                            'type' => 'string',
362
                            'aggregate' => true,
363
                            'indexed' => false,
364
                        ]
365
                    );
366
                    break;
367 View Code Duplication
                case 'description':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
                    $metadata->setDescriptionField($this->getContentField($property));
369
                    $metadata->addFieldMapping(
370
                        $property->getName(),
371
                        [
372
                            'field' => $this->getContentField($property),
373
                            'type' => 'string',
374
                            'aggregate' => true,
375
                            'indexed' => false,
376
                        ]
377
                    );
378
                    break;
379
                case 'image':
380
                    $metadata->setImageUrlField($this->getContentField($property));
381
                    break;
382
                default:
383
                    throw new \InvalidArgumentException(
384
                        sprintf(
385
                            'Unknown search field role "%s", role must be one of "%s"',
386
                            $tagAttributes['role'],
387
                            implode(', ', ['title', 'description', 'image'])
388
                        )
389
                    );
390
            }
391
392
            return;
393
        }
394
395
        if (!isset($tagAttributes['index']) || 'false' !== $tagAttributes['index']) {
396
            $metadata->addFieldMapping(
397
                $property->getName(),
398
                [
399
                    'type' => isset($tagAttributes['type']) ? $tagAttributes['type'] : 'string',
400
                    'field' => $this->getContentField($property),
401
                    'aggregate' => true,
402
                    'indexed' => false,
403
                ]
404
            );
405
        }
406
    }
407
408
    private function getContentField(PropertyMetadata $property)
409
    {
410
        $field = $this->factory->createMetadataExpression(
411
            sprintf(
412
                'object.getStructure().%s.getValue()',
413
                $property->getName()
414
            )
415
        );
416
417
        return $field;
418
    }
419
420 View Code Duplication
    private function mapTeaserDescription(PropertyMetadata $property, IndexMetadata $metadata)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
421
    {
422
        $metadata->addFieldMapping(
423
            self::FIELD_TEASER_DESCRIPTION,
424
            [
425
                'type' => 'string',
426
                'field' => $this->getContentField($property),
427
                'aggregate' => true,
428
                'indexed' => false,
429
            ]
430
        );
431
    }
432
433 View Code Duplication
    private function mapTeaserMedia(PropertyMetadata $property, IndexMetadata $metadata)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
434
    {
435
        $metadata->addFieldMapping(
436
            self::FIELD_TEASER_MEDIA,
437
            [
438
                'type' => 'json',
439
                'field' => $this->getContentField($property),
440
                'aggregate' => true,
441
                'indexed' => false,
442
            ]
443
        );
444
    }
445
446
    private function createIndexNameField(Metadata $documentMetadata, $indexName, $decorate)
447
    {
448
        if (!$decorate) {
449
            return new Value($indexName);
450
        }
451
452
        $expression = '"' . $indexName . '"';
453
        if ($documentMetadata->getReflectionClass()->isSubclassOf(WebspaceBehavior::class)) {
454
            $expression .= '~"_"~object.getWebspaceName()';
455
        }
456
        if ($documentMetadata->getReflectionClass()->isSubclassOf(WorkflowStageBehavior::class)) {
457
            $expression .= '~(object.getWorkflowStage() == ' . WorkflowStage::PUBLISHED . ' ? "_published" : "")';
458
        }
459
460
        return new Expression($expression);
461
    }
462
}
463