Completed
Push — master ( 6db6da...5370d9 )
by Angel
03:42
created

Slug::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace roaresearch\yii2\roa\behaviors;
4
5
use yii\{
6
    base\InvalidConfigException,
7
    db\BaseActiveRecord,
8
    helpers\Url,
9
    web\NotFoundHttpException
10
};
11
12
/**
13
 * Behavior to handle slug componentes linked as parent-child relations.
14
 *
15
 * @author Angel (Faryshta) Guevara <[email protected]>
16
 * @author Luis (Berkant) Campos <[email protected]>
17
 * @author Alejandro (Seether69) Márquez <[email protected]>
18
 */
19
class Slug extends \yii\base\Behavior
20
{
21
    /**
22
     * @var callable a PHP callable which will determine if the logged
23
     * user has permission to access a resource record or any of its
24
     * chidren resources.
25
     *
26
     * It must have signature
27
     * ```php
28
     * function (array $queryParams): void
29
     *     throws \yii\web\HTTPException
30
     * {
31
     * }
32
     * ```
33
     */
34
    public $checkAccess;
35
36
    /**
37
     * @var string name of the parent relation of the `$owner`
38
     */
39
    public $parentSlugRelation;
40
41
    /**
42
     * @var string name of the resource
43
     */
44
    public $resourceName;
45
46
    /**
47
     * @var string|array name of the identifier attribute
48
     */
49
    public $idAttribute = 'id';
50
51
    /**
52
     * @var string separator to create the route for resources with multiple id
53
     * attributes.
54
     */
55
    public $idAttributeSeparator = '/';
56
57
    /**
58
     * @var string parentNotFoundMessage for not found exception when the parent
59
     * slug was not found
60
     */
61
    public $parentNotFoundMessage = '"{resourceName}" not found';
62
63
    /**
64
     * @var ?BaseActiveRecord parent record.
65
     */
66
    protected $parentSlug;
67
68
    /**
69
     * @var string url to resource
70
     */
71
    protected $resourceLink;
72
73
    /**
74
     * @inheritdoc
75
     */
76
    public function init()
77
    {
78
        if (empty($this->resourceName)) {
79
            throw new InvalidConfigException(
80
                self::class . '::$resourceName must be defined.'
81
            );
82
        }
83
84
        $this->idAttribute = (array)$this->idAttribute;
85
    }
86
87
    /**
88
     * Ensures the parent record is attached to the behavior.
89
     *
90
     * @param BaseActiveRecord $owner
0 ignored issues
show
Documentation introduced by
Should the type for parameter $owner not be \yii\db\BaseActiveRecord?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
91
     * @param bool $force whether to force finding the slug parent record
92
     * when `$parentSlugRelation` is defined
93
     */
94
    private function ensureSlug(BaseActiveRecord $owner, bool $force = false)
95
    {
96
        if (null === $this->parentSlugRelation) {
97
            $this->resourceLink = Url::to([$this->resourceName . '/'], true);
98
        } elseif ($force
99
            || $owner->isRelationPopulated($this->parentSlugRelation)
100
        ) {
101
            $this->populateSlugParent($owner);
102
        }
103
    }
104
105
    /**
106
     * This populates the slug to the parentSlug
107
     * @param BaseActiveRecord $owner
0 ignored issues
show
Documentation introduced by
Should the type for parameter $owner not be \yii\db\BaseActiveRecord?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
108
     */
109
    private function populateSlugParent(BaseActiveRecord $owner)
110
    {
111
        $relation = $this->parentSlugRelation;
112
        $this->parentSlug = $owner->$relation;
113
114
        if (null === $this->parentSlug) {
115
            throw new NotFoundHttpException(
116
                strtr(
117
                    $this->parentNotFoundMessage,
118
                    [
119
                        '{resourceName}' => $this->parentSlugRelation,
120
                    ]
121
                )
122
            );
123
        }
124
125
        $this->resourceLink = $this->parentSlug->getSelfLink()
126
            . '/' . $this->resourceName;
127
    }
128
129
    /**
130
     * @return string value of the owner's identifier
131
     */
132
    public function getResourceRecordId(): string
133
    {
134
        $attributeValues = [];
135
        foreach ($this->idAttribute as $attribute) {
0 ignored issues
show
Bug introduced by
The expression $this->idAttribute of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
136
            $attributeValues[] = $this->owner->$attribute;
137
        }
138
139
        return implode($this->idAttributeSeparator, $attributeValues);
140
    }
141
142
    /**
143
     * @return string HTTP Url to the resource list
144
     */
145
    public function getResourceLink(): string
146
    {
147
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Documentation introduced by
$this->owner is of type object<yii\base\Component>|null, but the function expects a object<yii\db\BaseActiveRecord>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
148
149
        return $this->resourceLink;
150
    }
151
152
    /**
153
     * @return string HTTP Url to self resource
154
     */
155
    public function getSelfLink(): string
156
    {
157
        $resourceRecordId = $this->getResourceRecordId();
158
        $resourceLink = $this->getResourceLink();
159
160
        return $resourceRecordId
161
            ? "$resourceLink/$resourceRecordId"
162
            : $resourceLink;
163
    }
164
165
    /**
166
     * @return array link to self resource and all the acumulated parent's links
167
     */
168
    public function getSlugLinks(): array
169
    {
170
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Documentation introduced by
$this->owner is of type object<yii\base\Component>|null, but the function expects a object<yii\db\BaseActiveRecord>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
171
        $selfLinks = [
172
            'self' => $this->getSelfLink(),
173
            $this->resourceName . '_collection' => $this->resourceLink,
174
        ];
175
        if (null === $this->parentSlug) {
176
            return $selfLinks;
177
        }
178
        $parentLinks = $this->parentSlug->getSlugLinks();
179
        $parentLinks[$this->parentSlugRelation . '_record']
180
            = $parentLinks['self'];
181
        unset($parentLinks['self']);
182
183
        // preserve order
184
        return array_merge($selfLinks, $parentLinks);
185
    }
186
187
    /**
188
     * Determines if the logged user has permission to access a resource
189
     * record or any of its chidren resources.
190
     * @param  Array $params
191
     */
192
    public function checkAccess(array $params)
193
    {
194
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Documentation introduced by
$this->owner is of type object<yii\base\Component>|null, but the function expects a object<yii\db\BaseActiveRecord>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
195
196
        if (null !== $this->checkAccess) {
197
            call_user_func($this->checkAccess, $params);
198
        }
199
        if (null !== $this->parentSlug) {
200
            $this->parentSlug->checkAccess($params);
201
        }
202
    }
203
}
204