Passed
Pull Request — master (#1)
by Tom
07:01
created

HasLinksTrait   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 6
dl 0
loc 194
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
append() 0 1 ?
getRouteKey() 0 1 ?
A initializeHasLinksTrait() 0 4 1
A getLinksAttribute() 0 10 2
A buildLinks() 0 17 3
B buildRelationshipLinks() 0 43 7
B buildLink() 0 55 9
A getRouteName() 0 8 2
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);
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];
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