Slug::ensureSlug()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace roaresearch\yii2\roa\behaviors;
4
5
use roaresearch\yii2\roa\hal\ARContract;
6
use yii\{
7
    base\Action,
8
    base\InvalidConfigException,
9
    db\BaseActiveRecord,
10
    helpers\Url,
11
    web\NotFoundHttpException,
12
};
13
14
/**
15
 * Behavior to handle slug componentes linked as parent-child relations.
16
 *
17
 * @author Angel (Faryshta) Guevara <[email protected]>
18
 * @author Luis (Berkant) Campos <[email protected]>
19
 * @author Alejandro (Seether69) Márquez <[email protected]>
20
 */
21
class Slug extends \yii\base\Behavior
22
{
23
    /**
24
     * @var ?string name of the parent relation of the `$owner`
25
     */
26
    public ?string $parentSlugRelation = null;
27
28
    /**
29
     * @var string name of the resource
30
     */
31
    public string $resourceName;
32
33
    /**
34
     * @var array name of the identifier attribute
35
     */
36
    public array $idAttributes = ['id'];
37
38
    /**
39
     * @var string separator to create the route for resources with multiple id
40
     * attributes.
41
     */
42
    public string $idAttributeSeparator = '/';
43
44
    /**
45
     * @var string parentNotFoundMessage for not found exception when the parent
46
     * slug was not found
47
     */
48
    public string $parentNotFoundMessage = '"{resourceName}" not found';
49
50
    /**
51
     * @var ?ARContract parent record.
52
     */
53
    protected ?ARContract $parentSlug = null;
54
55
    /**
56
     * @var string url to resource
57
     */
58
    protected string $resourceLink;
59
60
    /**
61
     * @inheritdoc
62
     */
63 14
    public function init()
64
    {
65 14
        $this->resourceName ?: throw new InvalidConfigException(
66
            $this::class . '::$resourceName must be defined.'
67
        );
68
    }
69
70
    /**
71
     * Ensures the parent record is attached to the behavior.
72
     *
73
     * @param ARContract $owner
74
     * @param bool $force whether to force finding the slug parent record
75
     * when `$parentSlugRelation` is defined
76
     */
77 12
    private function ensureSlug(ARContract $owner, bool $force = false)
78
    {
79 12
        if (null === $this->parentSlugRelation) {
80 12
            $this->resourceLink = $this->defaultResourceLink();
81
        } elseif (
82
            $force
83 9
            || $owner->isRelationPopulated($this->parentSlugRelation)
84
        ) {
85 9
            $this->populateSlugParent($owner);
86
        }
87
    }
88
89
    /**
90
     * @return string default resource link used at bottom level resources.
91
     */
92 12
    protected function defaultResourceLink(): string
93
    {
94 12
        return Url::to([$this->resourceName . '/'], true);
95
    }
96
97
    /**
98
     * This populates the slug to the parentSlug
99
     * @param BaseActiveRecord $owner
100
     */
101 9
    private function populateSlugParent(ARContract $owner)
102
    {
103 9
        $this->parentSlug = $owner->{$this->parentSlugRelation}
104
            ?: throw new NotFoundHttpException(
105
                strtr(
106
                    $this->parentNotFoundMessage,
107
                    ['{resourceName}' => $this->parentSlugRelation]
108
                )
109
            );
110
111 9
        $this->resourceLink = $this->parentSlug->getSelfLink()
0 ignored issues
show
Bug introduced by
The method getSelfLink() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
        $this->resourceLink = $this->parentSlug->/** @scrutinizer ignore-call */ getSelfLink()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
112 9
            . '/' . $this->resourceName;
113
    }
114
115
    /**
116
     * @return string value of the owner's identifier
117
     */
118 12
    public function getResourceRecordId(): string
119
    {
120 12
        $attributeValues = [];
121 12
        foreach ($this->idAttributes as $attribute) {
122 12
            $attributeValues[] = $this->owner->$attribute;
123
        }
124
125 12
        return implode($this->idAttributeSeparator, $attributeValues);
126
    }
127
128
    /**
129
     * @return string HTTP Url to the resource list
130
     */
131 12
    public function getResourceLink(): string
132
    {
133 12
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Bug introduced by
$this->owner of type null|yii\base\Component is incompatible with the type roaresearch\yii2\roa\hal\ARContract expected by parameter $owner of roaresearch\yii2\roa\behaviors\Slug::ensureSlug(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
        $this->ensureSlug(/** @scrutinizer ignore-type */ $this->owner, true);
Loading history...
134
135 12
        return $this->resourceLink;
136
    }
137
138
    /**
139
     * @return string HTTP Url to self resource
140
     */
141 12
    public function getSelfLink(): string
142
    {
143 12
        $resourceRecordId = $this->getResourceRecordId();
144 12
        $resourceLink = $this->getResourceLink();
145
146 12
        return $resourceRecordId
147 12
            ? "$resourceLink/$resourceRecordId"
148 12
            : $resourceLink;
149
    }
150
151
    /**
152
     * @return array link to self resource and all the acumulated parent's links
153
     */
154 9
    public function getSlugLinks(): array
155
    {
156 9
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Bug introduced by
$this->owner of type null|yii\base\Component is incompatible with the type roaresearch\yii2\roa\hal\ARContract expected by parameter $owner of roaresearch\yii2\roa\behaviors\Slug::ensureSlug(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
        $this->ensureSlug(/** @scrutinizer ignore-type */ $this->owner, true);
Loading history...
157 9
        $selfLinks = [
158 9
            'self' => $this->getSelfLink(),
159 9
            $this->resourceName . '_collection' => $this->resourceLink,
160
        ];
161
162 9
        if (null === $this->parentSlug) {
163 9
            return $selfLinks;
164
        }
165
166 6
        $parentLinks = $this->parentSlug->getSlugLinks();
0 ignored issues
show
Bug introduced by
The method getSlugLinks() does not exist on roaresearch\yii2\roa\hal\ARContract. Did you maybe mean getLinks()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
        /** @scrutinizer ignore-call */ 
167
        $parentLinks = $this->parentSlug->getSlugLinks();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
167 6
        $parentLinks[$this->parentSlugRelation . '_record']
168 6
            = $parentLinks['self'];
169 6
        unset($parentLinks['self']);
170
171
        // preserve order
172 6
        return array_merge($selfLinks, $parentLinks);
173
    }
174
175
    /**
176
     * Determines if the logged user has permission to access a resource
177
     * record or any of its chidren resources.
178
     *
179
     * When extending this method make sure to call the parent at the end.
180
     *
181
     * ```php
182
     * // custom logic
183
     * parent::checkAccess($params, $action);
184
     * ```
185
     *
186
     * @param  array $params
187
     * @param ?Action $action
188
     * @throws \yii\web\HttpException
189
     */
190 7
    public function checkAccess(array $params, ?Action $action = null): void
191
    {
192 7
        $this->ensureSlug($this->owner, true);
0 ignored issues
show
Bug introduced by
$this->owner of type null|yii\base\Component is incompatible with the type roaresearch\yii2\roa\hal\ARContract expected by parameter $owner of roaresearch\yii2\roa\behaviors\Slug::ensureSlug(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
        $this->ensureSlug(/** @scrutinizer ignore-type */ $this->owner, true);
Loading history...
193 7
        $this->parentSlug?->checkAccess($params, $action);
194
    }
195
}
196