Completed
Pull Request — develop (#147)
by Wachter
14:07
created

ArticlePageSubscriber::handleMetadataLoad()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 2
eloc 9
nc 2
nop 1
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\ArticleBundle\Document\Subscriber;
13
14
use Sulu\Bundle\ArticleBundle\Document\ArticleInterface;
15
use Sulu\Bundle\ArticleBundle\Document\ArticlePageDocument;
16
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
17
use Sulu\Component\Content\Document\LocalizationState;
18
use Sulu\Component\Content\Document\WorkflowStage;
19
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactoryInterface;
20
use Sulu\Component\Content\Metadata\PropertyMetadata;
21
use Sulu\Component\DocumentManager\DocumentManagerInterface;
22
use Sulu\Component\DocumentManager\Event\MetadataLoadEvent;
23
use Sulu\Component\DocumentManager\Event\PersistEvent;
24
use Sulu\Component\DocumentManager\Event\RemoveEvent;
25
use Sulu\Component\DocumentManager\Events;
26
use Sulu\Component\DocumentManager\NameResolver;
27
use Symfony\Cmf\Api\Slugifier\SlugifierInterface;
28
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
29
30
/**
31
 * Handle specialized article events.
32
 */
33
class ArticlePageSubscriber implements EventSubscriberInterface
34
{
35
    const PAGE_TITLE_TAG_NAME = 'sulu_article.page_title';
36
    const PAGE_TITLE_PROPERTY_NAME = 'pageTitle';
37
38
    /**
39
     * @var StructureMetadataFactoryInterface
40
     */
41
    private $structureMetadataFactory;
42
43
    /**
44
     * @var DocumentManagerInterface
45
     */
46
    private $documentManager;
47
48
    /**
49
     * @var DocumentInspector
50
     */
51
    private $documentInspector;
52
53
    /**
54
     * @var SlugifierInterface
55
     */
56
    private $slugifier;
57
58
    /**
59
     * @var NameResolver
60
     */
61
    private $resolver;
62
63
    /**
64
     * @param StructureMetadataFactoryInterface $structureMetadataFactory
65
     * @param DocumentManagerInterface $documentManager
66
     * @param DocumentInspector $documentInspector
67
     * @param SlugifierInterface $slugifier
68
     * @param NameResolver $resolver
69
     */
70
    public function __construct(
71
        StructureMetadataFactoryInterface $structureMetadataFactory,
72
        DocumentManagerInterface $documentManager,
73
        DocumentInspector $documentInspector,
74
        SlugifierInterface $slugifier,
75
        NameResolver $resolver
76
    ) {
77
        $this->structureMetadataFactory = $structureMetadataFactory;
78
        $this->documentManager = $documentManager;
79
        $this->documentInspector = $documentInspector;
80
        $this->slugifier = $slugifier;
81
        $this->resolver = $resolver;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public static function getSubscribedEvents()
88
    {
89
        return [
90
            Events::PERSIST => [
91
                ['setTitleOnPersist', 2000],
92
                ['setNodeOnPersist', 480],
93
                ['setPageTitleOnPersist'],
94
                ['setStructureTypeToParent', -2000],
95
                ['setWorkflowStageOnArticle', -2000],
96
            ],
97
            Events::REMOVE => ['setWorkflowStageOnArticle'],
98
            Events::METADATA_LOAD => ['handleMetadataLoad'],
99
        ];
100
    }
101
102
    /**
103
     * Set page-title from structure to document.
104
     *
105
     * @param PersistEvent $event
106
     */
107
    public function setTitleOnPersist(PersistEvent $event)
108
    {
109
        $document = $event->getDocument();
110
        if (!$document instanceof ArticlePageDocument) {
111
            return;
112
        }
113
114
        $document->setTitle($document->getParent()->getTitle());
115
    }
116
117
    /**
118
     * Set workflow-stage to test for article.
119
     *
120
     * @param PersistEvent|RemoveEvent $event
121
     */
122
    public function setWorkflowStageOnArticle($event)
123
    {
124
        $document = $event->getDocument();
125
        if (!$document instanceof ArticlePageDocument
126
            || $this->documentInspector->getLocalizationState($document->getParent()) === LocalizationState::GHOST
127
        ) {
128
            return;
129
        }
130
131
        $document->getParent()->setWorkflowStage(WorkflowStage::TEST);
132
        $this->documentManager->persist(
133
            $document->getParent(),
134
            $this->documentInspector->getLocale($document),
135
            $event instanceof PersistEvent ? $event->getOptions() : []
0 ignored issues
show
Bug introduced by
The method getOptions does only exist in Sulu\Component\DocumentManager\Event\PersistEvent, but not in Sulu\Component\DocumentManager\Event\RemoveEvent.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
136
        );
137
    }
138
139
    /**
140
     * Set node to event on persist.
141
     *
142
     * @param PersistEvent $event
143
     */
144
    public function setNodeOnPersist(PersistEvent $event)
145
    {
146
        $document = $event->getDocument();
147
        if (!$document instanceof ArticlePageDocument || $event->hasNode()) {
148
            return;
149
        }
150
151
        $pageTitle = $this->getPageTitle($document);
152
153
        // if no page-title exists use a unique-id
154
        $nodeName = $this->slugifier->slugify($pageTitle ?: uniqid('page-', true));
155
        $nodeName = $this->resolver->resolveName($event->getParentNode(), $nodeName);
156
        $node = $event->getParentNode()->addNode($nodeName);
157
158
        $event->setNode($node);
159
    }
160
161
    /**
162
     * Set page-title on persist event.
163
     *
164
     * @param PersistEvent $event
165
     */
166
    public function setPageTitleOnPersist(PersistEvent $event)
167
    {
168
        $document = $event->getDocument();
169
        if (!$document instanceof ArticleInterface) {
170
            return;
171
        }
172
173
        $document->setPageTitle($this->getPageTitle($document));
174
    }
175
176
    /**
177
     * Returns page-title for node.
178
     *
179
     * @param ArticleInterface $document
180
     *
181
     * @return string
182
     */
183
    private function getPageTitle(ArticleInterface $document)
184
    {
185
        $pageTitleProperty = $this->getPageTitleProperty($document);
186
        if (!$pageTitleProperty) {
187
            return;
188
        }
189
190
        $stagedData = $document->getStructure()->getStagedData();
191
        if (array_key_exists($pageTitleProperty->getName(), $stagedData)) {
192
            return $stagedData[$pageTitleProperty->getName()];
193
        }
194
195
        if (!$document->getStructure()->hasProperty($pageTitleProperty->getName())) {
196
            return;
197
        }
198
199
        return $document->getStructure()->getProperty($pageTitleProperty->getName())->getValue();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $document->getStr...getName())->getValue(); (array) is incompatible with the return type documented by Sulu\Bundle\ArticleBundl...ubscriber::getPageTitle of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
200
    }
201
202
    /**
203
     * Find page-title property.
204
     *
205
     * @param ArticleInterface $document
206
     *
207
     * @return PropertyMetadata
208
     */
209
    private function getPageTitleProperty(ArticleInterface $document)
210
    {
211
        $metadata = $this->structureMetadataFactory->getStructureMetadata(
212
            'article_page',
213
            $document->getStructureType()
214
        );
215
216
        if ($metadata->hasPropertyWithTagName(self::PAGE_TITLE_TAG_NAME)) {
217
            return $metadata->getPropertyByTagName(self::PAGE_TITLE_TAG_NAME);
218
        }
219
220
        if ($metadata->hasProperty(self::PAGE_TITLE_PROPERTY_NAME)) {
221
            return $metadata->getProperty(self::PAGE_TITLE_PROPERTY_NAME);
222
        }
223
224
        return null;
225
    }
226
227
    /**
228
     * Set structure-type to parent document.
229
     *
230
     * @param PersistEvent $event
231
     */
232
    public function setStructureTypeToParent(PersistEvent $event)
233
    {
234
        $document = $event->getDocument();
235
        if (!$document instanceof ArticlePageDocument
236
            || $this->documentInspector->getLocalizationState($document->getParent()) === LocalizationState::GHOST
237
            || $document->getStructureType() === $document->getParent()->getStructureType()
238
        ) {
239
            return;
240
        }
241
242
        $document->getParent()->setStructureType($document->getStructureType());
243
        $this->documentManager->persist($document->getParent(), $event->getLocale());
244
    }
245
246
    /**
247
     * Extend metadata for article-page.
248
     *
249
     * @param MetadataLoadEvent $event
250
     */
251
    public function handleMetadataLoad(MetadataLoadEvent $event)
252
    {
253
        if (!$event->getMetadata()->getReflectionClass()->implementsInterface(ArticleInterface::class)) {
254
            return;
255
        }
256
257
        $metadata = $event->getMetadata();
258
        $metadata->setSyncRemoveLive(false);
259
        $metadata->addFieldMapping(
260
            'pageTitle',
261
            [
262
                'encoding' => 'system_localized',
263
                'property' => 'suluPageTitle',
264
            ]
265
        );
266
    }
267
}
268