Completed
Pull Request — master (#40)
by
unknown
10:59
created

CrudAction::parentId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright 2016 - 2017, Cake Development Corporation (http://cakedc.com)
4
 *
5
 * Licensed under The MIT License
6
 * Redistributions of files must retain the above copyright notice.
7
 *
8
 * @copyright Copyright 2016 - 2017, Cake Development Corporation (http://cakedc.com)
9
 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10
 */
11
12
namespace CakeDC\Api\Service\Action;
13
14
use CakeDC\Api\Exception\ValidationException;
15
use CakeDC\Api\Service\CrudService;
16
use CakeDC\Api\Service\Utility\ReverseRouting;
17
use Cake\Datasource\EntityInterface;
18
use Cake\Datasource\Exception\InvalidPrimaryKeyException;
19
use Cake\ORM\Table;
20
use Cake\ORM\TableRegistry;
21
use Cake\Utility\Inflector;
22
23
/**
24
 * Class CrudAction
25
 *
26
 * @package CakeDC\Api\Service\Action
27
 */
28
abstract class CrudAction extends Action
29
{
30
    /**
31
     * @var Table
32
     */
33
    protected $_table = null;
34
35
    /**
36
     * Object Identifier
37
     *
38
     * @var string
39
     */
40
    protected $_id = null;
41
42
    protected $_idName = 'id';
43
44
    /**
45
     * Crud service.
46
     *
47
     * @var CrudService
48
     */
49
    protected $_service;
50
51
    /**
52
     * Parent Object Identifier
53
     *
54
     * Used for nested services
55
     *
56
     * @var string
57
     */
58
    protected $_parentId = null;
59
60
    protected $_parentIdName = null;
61
62
    /**
63
     * Api table finder method
64
     *
65
     * @var string
66
     */
67
    protected $_finder = null;
68
69
    /**
70
     * Action constructor.
71
     *
72
     * @param array $config Configuration options passed to the constructor
73
     */
74 113
    public function __construct(array $config = [])
75
    {
76 113
        if (!empty($config['service'])) {
77 113
            $this->setService($config['service']);
78 113
        }
79 113
        if (!empty($config['table'])) {
80
            $tableName = $config['table'];
81
        } else {
82 113
            $tableName = $this->getService()->getTable();
83
        }
84 113
        if ($tableName instanceof Table) {
85
            $this->setTable($tableName);
86
        } else {
87 113
            $table = TableRegistry::get($tableName);
88 113
            $this->setTable($table);
89
        }
90 113
        if (!empty($config['id'])) {
91 24
            $this->_id = $config['id'];
92 24
        }
93 113
        if (!empty($config['idName'])) {
94 55
            $this->_idName = $config['idName'];
95 55
        }
96 113
        if (!empty($config['finder'])) {
97
            $this->_finder = $config['finder'];
98
        }
99 113
        if (!empty($config['parentId'])) {
100 9
            $this->_parentId = $config['parentId'];
101 9
        }
102 113
        if (!empty($config['parentIdName'])) {
103 9
            $this->_parentIdName = $config['parentIdName'];
104 9
        }
105 113
        if (!empty($config['table'])) {
106
            $this->setTable($config['table']);
107
        }
108 113
        parent::__construct($config);
109 113
    }
110
111
    /**
112
     * Gets a Table instance.
113
     *
114
     * @return Table
115
     */
116 75
    public function getTable()
117
    {
118 75
        return $this->_table;
119
    }
120
121
    /**
122
     * Sets the table instance.
123
     *
124
     * @param Table $table A Table instance.
125
     * @return $this
126
     */
127 113
    public function setTable(Table $table)
128
    {
129 113
        $this->_table = $table;
130
131 113
        return $this;
132
    }
133
134
    /**
135
     * Api method for table.
136
     *
137
     * @param Table $table A Table instance.
138
     * @deprecated 3.4.0 Use setTable()/getTable() instead.
139
     * @return Table
140
     */
141
    public function table($table = null)
142
    {
143
        if ($table !== null) {
144
            return $this->setTable($table);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->setTable($table); (CakeDC\Api\Service\Action\CrudAction) is incompatible with the return type documented by CakeDC\Api\Service\Action\CrudAction::table of type Cake\ORM\Table.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
145
        }
146
147
        return $this->getTable();
148
    }
149
150
    /**
151
     * @return CrudService
152
     */
153 113
    public function getService()
154
    {
155 113
        return $this->_service;
156
    }
157
158
    /**
159
     * Model id getter.
160
     *
161
     * @return mixed|string
162
     */
163
    public function getId()
164
    {
165
        return $this->_id;
166
    }
167
168
    /**
169
     * Model id field name getter.
170
     *
171
     * @return string
172
     */
173
    public function getIdName()
174
    {
175
        return $this->_idName;
176
    }
177
178
    /**
179
     * Parent id getter.
180
     *
181
     * @return mixed|string
182
     */
183 9
    public function getParentId()
184
    {
185 9
        return $this->_parentId;
186
    }
187
188
    /**
189
     * Parent model id field name getter.
190
     *
191
     * @return mixed|string
192
     */
193 9
    public function getParentIdName()
194
    {
195 9
        return $this->_parentIdName;
196
    }
197
198
    /**
199
     * Builds new entity instance.
200
     *
201
     * @return EntityInterface
202
     */
203 10
    protected function _newEntity()
204
    {
205 10
        $entity = $this->getTable()->newEntity();
206
207 10
        return $entity;
208
    }
209
210
    /**
211
     * Patch entity.
212
     *
213
     * @param EntityInterface $entity An Entity instance.
214
     * @param array $data Entity data.
215
     * @param array $options Patch entity options.
216
     * @return \Cake\Datasource\EntityInterface|mixed
217
     */
218 13
    protected function _patchEntity($entity, $data, $options = [])
219
    {
220 13
        $entity = $this->getTable()->patchEntity($entity, $data, $options);
221 13
        $event = $this->dispatchEvent('Action.Crud.onPatchEntity', compact('entity'));
222 13
        if ($event->result) {
223 2
            $entity = $event->result;
224 2
        }
225
226 13
        return $entity;
227
    }
228
229
    /**
230
     * Builds entities list
231
     *
232
     * @return \Cake\Collection\Collection
233
     */
234 33
    protected function _getEntities()
235
    {
236 33
        $query = $this->getTable()->find();
237 33
        if ($this->_finder !== null) {
238
            $query = $query->find($this->_finder);
239
        }
240
241 33
        $event = $this->dispatchEvent('Action.Crud.onFindEntities', compact('query'));
242 33
        if ($event->result) {
243 29
            $query = $event->result;
244 29
        }
245 33
        $records = $query->all();
246 33
        $event = $this->dispatchEvent('Action.Crud.afterFindEntities', compact('query', 'records'));
0 ignored issues
show
Unused Code introduced by
$event is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
247
248 33
        return $records;
249
    }
250
251
    /**
252
     * Returns single entity by id.
253
     *
254
     * @param mixed $primaryKey Primary key.
255
     * @return \Cake\Collection\Collection
256
     */
257 22
    protected function _getEntity($primaryKey)
258
    {
259 22
        $query = $this->getTable()->find('all')->where($this->_buildViewCondition($primaryKey));
260 22
        if ($this->_finder !== null) {
261
            $query = $query->find($this->_finder);
262
        }
263 22
        $event = $this->dispatchEvent('Action.Crud.onFindEntity', compact('query'));
264 22
        if ($event->result) {
265 10
            $query = $event->result;
266 10
        }
267 22
        $entity = $query->firstOrFail();
268
269 18
        return $entity;
270
    }
271
272
    /**
273
     * Build condition for get entity method.
274
     *
275
     * @param string $primaryKey Primary key
276
     * @return array
277
     */
278 22
    protected function _buildViewCondition($primaryKey)
279
    {
280 22
        $table = $this->getTable();
281 22
        $key = (array)$table->getPrimaryKey();
282 22
        $alias = $table->getAlias();
283 22
        foreach ($key as $index => $keyname) {
284 22
            $key[$index] = $alias . '.' . $keyname;
285 22
        }
286 22
        $primaryKey = (array)$primaryKey;
287 22
        if (count($key) !== count($primaryKey)) {
288
            $primaryKey = $primaryKey ?: [null];
289
            $primaryKey = array_map(function ($key) {
290
                return var_export($key, true);
291
            }, $primaryKey);
292
293
            throw new InvalidPrimaryKeyException(sprintf('Record not found in table "%s" with primary key [%s]', $table->getTable(), implode($primaryKey, ', ')));
294
        }
295 22
        $conditions = array_combine($key, $primaryKey);
296
297 22
        return $conditions;
298
    }
299
300
    /**
301
     * Save entity.
302
     *
303
     * @param EntityInterface $entity An Entity instance.
304
     * @return EntityInterface
305
     */
306 8
    protected function _save($entity)
307
    {
308 8
        if ($this->getTable()->save($entity)) {
309 6
            return $entity;
310
        } else {
311 2
            throw new ValidationException(__('Validation on {0} failed', $this->getTable()->getAlias()), 0, null, $entity->errors());
0 ignored issues
show
Bug introduced by
It seems like $entity->errors() targeting Cake\Datasource\EntityInterface::errors() can also be of type object<Cake\Datasource\EntityInterface>; however, CakeDC\Api\Exception\Val...xception::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Deprecated Code introduced by
The method Cake\Datasource\EntityInterface::errors() has been deprecated with message: 3.4.0 Use setErrors() and getErrors() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
312
        }
313
    }
314
315
    /**
316
     * Describe table with full details.
317
     *
318
     * @return array
319
     */
320 1
    protected function _describe()
321
    {
322 1
        $table = $this->getTable();
323 1
        $schema = $table->getSchema();
324
325 1
        $entity = $this->_newEntity();
326 1
        $reverseRouter = new ReverseRouting();
327 1
        $path = $reverseRouter->indexPath($this);
328
        $actions = [
329 1
            'index' => $reverseRouter->link('self', $path, 'GET'),
330 1
            'add' => $reverseRouter->link('add', $path, 'POST'),
331 1
            'edit' => $reverseRouter->link('edit', $path . '/{id}', 'PUT'),
332 1
            'delete' => $reverseRouter->link('delete', $path . '/{id}', 'DELETE'),
333 1
        ];
334
335 1
        $validators = [];
336 1
        foreach ($table->validator()
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Validation\ValidatorAwareTrait::validator() has been deprecated with message: 3.5.0 Use getValidator/setValidator instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
337 1
                       ->getIterator() as $name => $field) {
338 1
            $validators[$name] = [
339 1
                'validatePresence' => $field->isPresenceRequired(),
340 1
                'emptyAllowed' => $field->isEmptyAllowed(),
341 1
                'rules' => []
342 1
            ];
343 1
            foreach ($field->getIterator() as $ruleName => $rule) {
344 1
                $_rule = $rule->get('rule');
345 1
                if (is_callable($_rule)) {
346
                    continue;
347
                }
348 1
                $params = $rule->get('pass');
349 1
                if (is_callable($params)) {
350
                    $params = null;
351
                }
352 1
                if (is_array($params)) {
353 1
                    foreach ($params as &$param) {
354
                        if (is_callable($param)) {
355
                            $param = null;
356
                        }
357 1
                    }
358 1
                }
359 1
                $validators[$name]['rules'][$ruleName] = [
360 1
                    'message' => $rule->get('message'),
361 1
                    'on' => $rule->get('on'),
362 1
                    'rule' => $_rule,
363 1
                    'params' => $params,
364 1
                    'last' => $rule->get('last'),
365
                ];
366 1
            }
367 1
        }
368
369 1
        $labels = collection($schema->columns())
370
            ->map(function ($column) use ($schema) {
371
                return [
372 1
                    'name' => $column,
373 1
                    'label' => __(Inflector::humanize(preg_replace('/_id$/', '', $column)))
374 1
                ];
375 1
            })
376 1
            ->combine('name', 'label')
377 1
            ->toArray();
378
379 1
        $associationTypes = ['BelongsTo', 'HasOne', 'HasMany', 'BelongsToMany'];
380 1
        $associations = collection($associationTypes)
381
            ->map(function ($type) use ($table) {
382
                return [
383 1
                    'type' => $type,
384 1
                    'assocs' => collection($table->associations()->type($type))
385
                        ->map(function ($assoc) {
386 1
                            return $assoc->target()->table();
387 1
                        })
388 1
                        ->toArray()
389 1
                ];
390 1
            })
391 1
            ->combine('type', 'assocs')
392 1
            ->toArray();
393
394 1
        $fieldTypes = collection($schema->columns())
395 1
            ->map(function ($column) use ($schema) {
396
                return [
397 1
                    'name' => $column,
398 1
                    'column' => $schema->column($column)
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Database\Schema\TableSchema::column() has been deprecated with message: 3.5.0 Use getColumn() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
399 1
                ];
400 1
            })
401 1
            ->combine('name', 'column')
402 1
            ->toArray();
403
404
        return [
405
            'entity' => [
406 1
                'hidden' => $entity->hiddenProperties(),
407
408
                // ... fields with data types
409 1
            ],
410
            'schema' => [
411 1
                'columns' => $fieldTypes,
412
                'labels' => $labels
413 1
            ],
414 1
            'validators' => $validators,
415 1
            'relations' => $associations,
416
            'actions' => $actions
417 1
        ];
418
    }
419
}
420