Passed
Pull Request — master (#1)
by Tom
02:13
created

HasLinksTrait::getRouteName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: tom
5
 * Date: 28/03/19
6
 * Time: 12:47
7
 */
8
9
namespace TomHart\Restful\Traits;
10
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Routing\Router;
13
use Illuminate\Support\Str;
14
use ReflectionException;
15
use ReflectionMethod;
16
use TomHart\Restful\Concerns\HasLinks;
17
18
trait HasLinksTrait
19
{
20
21
    /**
22
     * Append attributes to query when building a query.
23
     *
24
     * @param string[]|string $attributes
25
     * @return $this
26
     */
27
    abstract public function append($attributes);
28
29
    /**
30
     * Get the value of the model's route key.
31
     *
32
     * @return mixed
33
     */
34
    abstract public function getRouteKey();
35
36
    /**
37
     * Add the links attribute to the model.
38
     */
39
    public function initializeHasLinksTrait(): void
40
    {
41
        $this->append('_links');
42
    }
43
44
    /**
45
     * Get the links for this model.
46
     * @return mixed[]
47
     * @throws ReflectionException
48
     */
49
    public function getLinksAttribute(): array
50
    {
51
        $links = $this->buildLinks();
52
        $relationships = $this->buildRelationshipLinks();
53
        if (!empty($relationships)) {
54
            $links['relationships'] = $relationships;
55
        }
56
57
        return $links;
58
    }
59
60
    /**
61
     * Returns the _links for the REST responses.
62
     *
63
     * @return mixed[]
64
     */
65
    public function buildLinks(): array
66
    {
67
        $routes = ['create', 'store', 'show', 'update', 'destroy'];
68
        $links = [];
69
70
        $router = app(Router::class);
71
72
        foreach ($routes as $routePart) {
73
            $link = $this->buildLink($this, $routePart, $router);
74
75
            if ($link) {
76
                $links[$routePart] = $link;
77
            }
78
        }
79
80
        return $links;
81
    }
82
83
84
    /**
85
     * Builds the links to create the relationship resources.
86
     *
87
     * @return mixed[]
88
     * @throws ReflectionException
89
     */
90
    public function buildRelationshipLinks(): array
91
    {
92
        $methods = get_class_methods($this);
93
94
        $links = [];
95
        $router = app(Router::class);
96
97
        foreach ($methods as $method) {
98
            $method2 = new ReflectionMethod($this, $method);
99
            $return = (string)$method2->getReturnType();
100
101
            if (empty($return)) {
102
                continue;
103
            }
104
105
            $isRelationship = is_subclass_of($return, Relation::class);
1 ignored issue
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Illuminate\Database\Elo...lations\Relation::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
106
107
            if (!$isRelationship) {
108
                continue;
109
            }
110
111
            /** @var Relation $relationship */
112
            $relationship = $this->$method();
113
114
            $targetClass = $relationship->getRelated();
115
116
            if (!($targetClass instanceof HasLinks)) {
117
                continue;
118
            }
119
120
            $createLink = $this->buildLink($targetClass, 'create', $router);
121
            $storeLink = $this->buildLink($targetClass, 'store', $router);
122
123
            if ($createLink || $storeLink) {
124
                $links[$method] = [
125
                    'create' => $createLink,
126
                    'store' => $storeLink
127
                ];
128
            }
129
        }
130
131
        return $links;
132
    }
133
134
135
    /**
136
     * Builds a link if possible
137
     *
138
     * @param HasLinks $model
139
     * @param string $routePart
140
     * @param Router $router
141
     * @return mixed[]|bool
142
     */
143
    private function buildLink(HasLinks $model, string $routePart, Router $router)
144
    {
145
        $routeStub = $model->getRouteName();
146
147
        if ($routeStub === null) {
148
            return false;
149
        }
150
151
        if (!property_exists($this, 'primaryKey')) {
152
            return false;
153
        }
154
155
        // Make the route name, and check if it exists.
156
        $routeName = "$routeStub.$routePart";
157
158
        if (!$router->has($routeName)) {
159
            return false;
160
        }
161
162
        // Get any params needed to build the URL.
163
        $params = [];
164
        switch ($routePart) {
165
            case 'destroy':
166
            case 'update':
167
            case 'show':
168
                $params = [$this->getRouteKey() => $this->primaryKey];
1 ignored issue
show
Bug introduced by
The property primaryKey does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
169
                break;
170
        }
171
172
        // Get the route.
173
        $route = $router->getRoutes()->getByName($routeName);
174
175
        if (!$route) {
176
            return false;
177
        }
178
179
        // Get the methods applicable to the route, ignoring HEAD and PATCH.
180
        $methods = collect($route->methods());
181
        $methods = $methods->filter(static function ($item) {
182
            return !in_array($item, ['HEAD', 'PATCH']);
183
        })->map(static function ($str) {
184
            return strtolower($str);
185
        });
186
187
        // If there's only 1, return just that, otherwise, return an array.
188
        if ($methods->count() === 1) {
189
            $methods = $methods->first();
190
        }
191
192
        // Add!
193
        return [
194
            'method' => $methods,
195
            'href' => route($routeName, $params, false)
196
        ];
197
    }
198
199
    /**
200
     * Return the name for the resource route this model
201
     * @return string|null
202
     */
203
    public function getRouteName(): ?string
204
    {
205
        $name = $this->getRouteKey();
206
        if (!$name) {
207
            return null;
208
        }
209
        return Str::kebab(Str::studly($name));
210
    }
211
}
212