Completed
Push — master ( 0b4fa8...bbf19f )
by Andrii
04:44
created

AbstractPage::setKeywords()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * Yii2 Pages Module
4
 *
5
 * @link      https://github.com/hiqdev/yii2-module-pages
6
 * @package   yii2-module-pages
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\yii2\modules\pages\models;
12
13
use Yii;
14
use hiqdev\yii2\modules\pages\interfaces\PageInterface;
15
use hiqdev\yii2\modules\pages\storage\FileSystemStorage;
16
use hiqdev\yii2\modules\pages\interfaces\StorageInterface;
17
use Symfony\Component\Yaml\Yaml;
18
use yii\base\BaseObject;
19
20
abstract class AbstractPage extends BaseObject implements PageInterface
21
{
22
    /** @var \yii\web\View */
23
    protected $view;
24
25
    public $layout;
26
27
    /** @var string */
28
    public $title;
29
30
    /** @var null|string */
31
    protected $path;
32
33
    /** @var string */
34
    protected $text;
35
36
    /** @var array  */
37
    protected $data = [];
38
39
    /** @var string */
40
    protected $url;
41
42
    /** @var null|string */
43
    protected $featuredImageUrl;
44
45
    /** @var null|string */
46
    protected $slug;
47
48
    /** @var null|string */
49
    protected $keywords;
50
51
    /** @var null|string */
52
    protected $description;
53
54
    /** @var null|string */
55
    protected $canonical;
56
57
    const META_DATA = ['keywords', 'description', 'canonical'];
58
59
    /** @var StorageInterface  */
60
    protected $storage;
61
62
    public function __construct($path = null, StorageInterface $storage = null, $config = [])
63
    {
64
        if ($path) {
65
            $this->storage = $storage;
66
            list($data, $text) = $this->extractData($path);
67
68
            $this->path = $path;
69
            $this->text = $text;
70
            $this->setData($data);
71
        }
72
73
        $this->view = Yii::$app->view;
74
        parent::__construct($config);
75
    }
76
77
    public function setData($data)
78
    {
79
        if (!is_array($data)) {
80
            return;
81
        }
82
        $this->data = $data;
83
        foreach (['title', 'layout', 'url'] as $key) {
84
            if (isset($data[$key])) {
85
                $this->{$key} = $data[$key];
86
            }
87
        }
88
    }
89
90
    /**
91
     * @return array
92
     */
93
    public function getData(): array
94
    {
95
        return $this->data;
96
    }
97
98
    public function getPath()
99
    {
100
        return $this->path;
101
    }
102
103
    public function getDate()
104
    {
105
        return $this->data['date'];
106
    }
107
108
    public function getUrl()
109
    {
110
        return $this->url ?: ['/pages/render/index', 'page' => $this->getPath()];
111
    }
112
113
    public static function createFromFile($path, FileSystemStorage $storage)
114
    {
115
        $extension = pathinfo($path)['extension'];
116
        $class = $storage->findPageClass($extension);
117
118
        return new $class($path, $storage);
119
    }
120
121
    public function extractData($path)
122
    {
123
        $lines = $this->storage->readArray($path);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface hiqdev\yii2\modules\page...rfaces\StorageInterface as the method readArray() does only exist in the following implementations of said interface: hiqdev\yii2\modules\page...orage\FileSystemStorage.

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...
124
        $yaml = $this->readQuotedLines($lines, '/^---$/', '/^---$/');
125
        if (empty($yaml)) {
126
            $data = [];
127
            $text = $lines;
128
        } else {
129
            $data = $this->readYaml($yaml);
130
            $text = array_slice($lines, count($yaml));
131
        }
132
133
        return [$data, implode("\n", $text)];
134
    }
135
136
    public function readFrontMatter($lines)
137
    {
138
        $yaml = $this->readQuotedLines($lines, '/^---$/', '/^---$/');
139
        if (empty($yaml)) {
140
            return [];
141
        }
142
143
        return empty($yaml) ? [] : $this->readYaml($yaml);
144
    }
145
146
    public function readYaml($lines)
147
    {
148
        $data = Yaml::parse(implode("\n", $lines));
149
        if (is_int($data['date'])) {
150
            $data['date'] = date('c', $data['date']);
151
        }
152
153
        return $data;
154
    }
155
156
    public function readQuotedLines($lines, $headMarker, $tailMarker)
157
    {
158
        $line = array_shift($lines);
159
        if (!preg_match($headMarker, $line, $matches)) {
160
            return null;
161
        }
162
        $res[] = ltrim(substr($line, strlen($matches[0])));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$res was never initialized. Although not strictly required by PHP, it is generally a good practice to add $res = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
163
        while ($line) {
164
            $line = array_shift($lines);
165
            if (preg_match($tailMarker, $line, $matches)) {
166
                $res[] = rtrim(substr($line, 0, -strlen($matches[0])));
167
                break;
168
            }
169
            $res[] = $line;
170
        }
171
172
        return $res;
173
    }
174
175
    /**
176
     * @param string $text
177
     */
178
    public function setText(string $text): void
179
    {
180
        $this->text = $text;
181
    }
182
183
    /**
184
     * @param string $url
185
     */
186
    public function setUrl(string $url): void
187
    {
188
        $this->url = $url;
189
    }
190
191
    protected function setMetaData(): void
192
    {
193
        foreach (self::META_DATA as $tag) {
194
            if (is_null($this->{$tag})) {
195
                continue;
196
            }
197
            $this->view->registerMetaTag([
198
                'name' => $tag,
199
                'content' => $this->{$tag},
200
            ]);
201
        }
202
    }
203
204
    /**
205
     * @param null|string $slug
206
     */
207
    public function setSlug(?string $slug): void
208
    {
209
        $this->slug = $slug;
210
    }
211
212
    /**
213
     * @param null|string $keywords
214
     */
215
    public function setKeywords(?string $keywords): void
216
    {
217
        $this->keywords = $keywords;
218
    }
219
220
    /**
221
     * @param null|string $description
222
     */
223
    public function setDescription(?string $description): void
224
    {
225
        $this->description = $description;
226
    }
227
228
    /**
229
     * @param null|string $featuredImageUrl
230
     */
231
    public function setFeaturedImageUrl(?string $featuredImageUrl): void
232
    {
233
        $this->featuredImageUrl = $featuredImageUrl;
234
    }
235
236
    /**
237
     * @param null|string $canonical
238
     */
239
    public function setCanonical(?string $canonical): void
240
    {
241
        $this->canonical = $canonical;
242
    }
243
}
244