Passed
Push — master ( ac8d71...dd2bba )
by
unknown
04:52
created

EloquentModelDataLayer::getQueryBuilder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace W2w\Laravel\Apie\Plugins\Illuminate\DataLayers;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\ModelNotFoundException;
8
use RuntimeException;
9
use W2w\Laravel\Apie\Plugins\Illuminate\Eloquent\EloquentModelSerializer;
10
use W2w\Laravel\Apie\Plugins\Illuminate\Eloquent\QueryBuilderResolver;
11
use W2w\Lib\Apie\Core\SearchFilters\SearchFilterFromMetadataTrait;
12
use W2w\Lib\Apie\Core\SearchFilters\SearchFilterRequest;
13
use W2w\Lib\Apie\Exceptions\ResourceNotFoundException;
14
use W2w\Lib\Apie\Interfaces\ApiResourcePersisterInterface;
15
use W2w\Lib\Apie\Interfaces\ApiResourceRetrieverInterface;
16
use W2w\Lib\Apie\Interfaces\SearchFilterProviderInterface;
17
18
/**
19
 * Maps a domain object to an eloquent model. Remember that foreign key constraints can be confusing, so it might be
20
 * a good idea to make your own retriever and persister class if the model becomes more complex.
21
 *
22
 * It uses the fill and toArray method of the Eloquent model. Mass alignment is disabled to map the fields as we
23
 * assume the domain object does the protection.
24
 */
25
class EloquentModelDataLayer implements ApiResourceRetrieverInterface, ApiResourcePersisterInterface, SearchFilterProviderInterface
26
{
27
    use SearchFilterFromMetadataTrait;
28
29
    private $serializer;
30
31
    private $queryBuilderResolver;
32
33
    /**
34
     * @param EloquentModelSerializer $serializer
35
     * @param QueryBuilderResolver $queryBuilderResolver
36
     */
37
    public function __construct(EloquentModelSerializer $serializer, QueryBuilderResolver $queryBuilderResolver)
38
    {
39
        $this->serializer = $serializer;
40
        $this->queryBuilderResolver = $queryBuilderResolver;
41
    }
42
43
    /**
44
     * Retrieves all resources. Since the filtering whether you are allowed to see a model instance is done afterwards,
45
     * the pagination could show a less amount of records than indicated. This is only for performance reasons.
46
     *
47
     * @param  string              $resourceClass
48
     * @param  array               $context
49
     * @param  SearchFilterRequest $searchFilterRequest
50
     * @return array
51
     */
52
    public function retrieveAll(string $resourceClass, array $context, SearchFilterRequest $searchFilterRequest): iterable
53
    {
54
        $modelClass = $this->getModelClass($resourceClass, $context);
55
56
        $queryBuilder = $this->getQueryBuilder($modelClass, $context['queryBuilder'] ?? null);
57
        return $this->serializer->toList($queryBuilder, $resourceClass, $searchFilterRequest, $context['mapping'] ?? null);
58
    }
59
60
    /**
61
     * Retrieves a single instance.
62
     *
63
     * @param  string $resourceClass
64
     * @param  mixed  $id
65
     * @param  array  $context
66
     * @return mixed
67
     */
68
    public function retrieve(string $resourceClass, $id, array $context)
69
    {
70
        $modelClass = $this->getModelClass($resourceClass, $context);
71
        try {
72
            $modelInstance = $this->getQueryBuilder($modelClass, $context['queryBuilder'] ?? null)->where($context[1] ?? $context['id'] ?? 'id', $id)->firstOrFail();
73
        } catch (ModelNotFoundException $notFoundException) {
74
            throw new ResourceNotFoundException($id);
75
        }
76
        $result = $this->serializer->toResource($modelInstance, $resourceClass, $context['mapping'] ?? null);
77
78
        return $result;
79
    }
80
81
    /**
82
     * Creates a new Eloquent model from an api resource.
83
     *
84
     * @param  mixed $resource
85
     * @param  array $context
86
     * @return mixed
87
     */
88
    public function persistNew($resource, array $context = [])
89
    {
90
        $resourceClass = get_class($resource);
91
        $modelInstance = $this->serializer->toModel($resource, $this->getModelClass($resourceClass, $context), $context['mapping'] ?? null);
92
93
        return $this->serializer->toResource($modelInstance, $resourceClass);
94
    }
95
96
    /**
97
     * Stores an api resource to an existing Eloquent model instance.
98
     *
99
     * @param  mixed $resource
100
     * @param  mixed $id
101
     * @param  array $context
102
     * @return mixed
103
     */
104
    public function persistExisting($resource, $id, array $context = [])
105
    {
106
        $resourceClass = get_class($resource);
107
        $modelClass = $this->getModelClass($resourceClass, $context);
108
        $modelInstance = $this->serializer->toExistingModel($resource, $id, $modelClass, $context['mapping'] ?? null);
109
110
        return $this->serializer->toResource($modelInstance, $resourceClass);
111
    }
112
113
    /**
114
     * Removes a resource from the database.
115
     *
116
     * @param string $resourceClass
117
     * @param mixed  $id
118
     * @param array  $context
119
     */
120
    public function remove(string $resourceClass, $id, array $context)
121
    {
122
        $modelClass = $this->getModelClass($resourceClass, $context);
123
        $modelInstance = $modelInstance = $this->getQueryBuilder($modelClass, $context['queryBuilder'] ?? null)->where($context[1] ?? $context['id'] ?? 'id', $id)->first();
0 ignored issues
show
Unused Code introduced by
The assignment to $modelInstance is dead and can be removed.
Loading history...
124
        if (!$modelInstance) {
125
            return;
126
        }
127
        $modelInstance->delete();
128
    }
129
130
    /**
131
     * Returns the name of the model class associated to a api resource class.
132
     *
133
     * @param  string $resourceClass
134
     * @return string
135
     */
136
    private function determineModel(string $resourceClass): string
137
    {
138
        $class = str_replace('\\ApiResources\\', '\\Models\\', $resourceClass);
139
        if (class_exists($class)) {
140
            return $class;
141
        }
142
        return str_replace('\\ApiResources\\', '\\Entities\\', $resourceClass);
143
    }
144
145
    /**
146
     * Returns the name of the model class associated to a api resource class and a context.
147
     *
148
     * @param  string $resourceClass
149
     * @param  array  $context
150
     * @return string
151
     */
152
    private function getModelClass(string $resourceClass, array $context): string
153
    {
154
        $modelClass = $context[0] ?? $context['model'] ?? $this->determineModel($resourceClass);
155
        if (!class_exists($modelClass)) {
156
            throw new RuntimeException('Class "' . $modelClass . '" not found!');
157
        }
158
        if (!is_a($modelClass, Model::class, true)) {
159
            throw new RuntimeException('Class "' . $modelClass . '" exists, but is not a Eloquent model!');
160
        }
161
162
        return $modelClass;
163
    }
164
165
    private function getQueryBuilder(string $modelClass, ?string $contextConfig): Builder {
166
        if (null === $contextConfig) {
167
            return $modelClass::query();
168
        }
169
        return $this->queryBuilderResolver->resolveFromString($contextConfig);
170
    }
171
}
172