Test Setup Failed
Push — master ( eb8e32...ff17f5 )
by Evgeny
03:06
created

CrudAction   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 87.42%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 3
dl 0
loc 329
ccs 139
cts 159
cp 0.8742
rs 9.2
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 30 8
A table() 0 9 2
A _newEntity() 0 6 1
A _patchEntity() 0 10 2
A _getEntities() 0 12 2
B _getEntity() 0 27 5
A _save() 0 8 2
A id() 0 4 1
A idName() 0 4 1
A parentId() 0 4 1
A parentIdName() 0 4 1
C _describe() 0 100 8
1
<?php
2
/**
3
 * Copyright 2016, 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, 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
     * Action constructor.
64
     *
65
     * @param array $config Configuration options passed to the constructor
66
     */
67 91
    public function __construct(array $config = [])
68
    {
69 91
        parent::__construct($config);
70 91
        if (!empty($config['table'])) {
71
            $tableName = $config['table'];
72
        } else {
73 91
            $tableName = $this->service()->table();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CakeDC\Api\Service\Service as the method table() does only exist in the following sub-classes of CakeDC\Api\Service\Service: CakeDC\Api\Service\CrudService, CakeDC\Api\Service\FallbackService, CakeDC\Api\Service\NestedCrudService, CakeDC\Api\Test\App\Service\ArticlesService. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
74
        }
75 91
        if ($tableName instanceof Table) {
0 ignored issues
show
Bug introduced by
The class Cake\ORM\Table does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
76
            $this->table($tableName);
77
        } else {
78 91
            $table = TableRegistry::get($tableName);
79 91
            $this->table($table);
80
        }
81 91
        if (!empty($config['id'])) {
82 24
            $this->_id = $config['id'];
83 24
        }
84 91
        if (!empty($config['idName'])) {
85 54
            $this->_idName = $config['idName'];
86 54
        }
87 91
        if (!empty($config['parentId'])) {
88 9
            $this->_parentId = $config['parentId'];
89 9
        }
90 91
        if (!empty($config['parentIdName'])) {
91 9
            $this->_parentIdName = $config['parentIdName'];
92 9
        }
93 91
        if (!empty($config['table'])) {
94
            $this->table($config['table']);
95
        }
96 91
    }
97
98
    /**
99
     * Api method for table.
100
     *
101
     * @param Table $table A Table instance.
102
     * @return Table
103
     */
104 91
    public function table($table = null)
105
    {
106 91
        if ($table === null) {
107 60
            return $this->_table;
108
        }
109 91
        $this->_table = $table;
110
111 91
        return $this->_table;
112
    }
113
114
    /**
115
     * Builds new entity instance.
116
     *
117
     * @return EntityInterface
118
     */
119 5
    protected function _newEntity()
120
    {
121 5
        $entity = $this->table()->newEntity();
122
123 5
        return $entity;
124
    }
125
126
    /**
127
     * Patch entity.
128
     *
129
     * @param EntityInterface $entity An Entity instance.
130
     * @param array $data Entity data.
131
     * @return \Cake\Datasource\EntityInterface|mixed
132
     */
133 8
    protected function _patchEntity($entity, $data)
134
    {
135 8
        $entity = $this->table()->patchEntity($entity, $data);
136 8
        $event = $this->dispatchEvent('Action.Crud.onPatchEntity', compact('entity'));
137 8
        if ($event->result) {
138 2
            $entity = $event->result;
139 2
        }
140
141 8
        return $entity;
142
    }
143
144
    /**
145
     * Builds entities list
146
     *
147
     * @return \Cake\Collection\Collection
148
     */
149 33
    protected function _getEntities()
150
    {
151 33
        $query = $this->table()->find();
152 33
        $event = $this->dispatchEvent('Action.Crud.onFindEntities', compact('query'));
153 33
        if ($event->result) {
154 29
            $query = $event->result;
155 29
        }
156 33
        $records = $query->all();
157 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...
158
159 33
        return $records;
160
    }
161
162
    /**
163
     * Returns single entity by id.
164
     *
165
     * @param mixed $primaryKey Primary key.
166
     * @return \Cake\Collection\Collection
167
     */
168 22
    protected function _getEntity($primaryKey)
169
    {
170 22
        $table = $this->table();
171 22
        $key = (array)$table->primaryKey();
172 22
        $alias = $table->alias();
173 22
        foreach ($key as $index => $keyname) {
174 22
            $key[$index] = $alias . '.' . $keyname;
175 22
        }
176 22
        $primaryKey = (array)$primaryKey;
177 22
        if (count($key) !== count($primaryKey)) {
178
            $primaryKey = $primaryKey ?: [null];
179
            $primaryKey = array_map(function ($key) {
180
                return var_export($key, true);
181
            }, $primaryKey);
182
183
            throw new InvalidPrimaryKeyException(sprintf('Record not found in table "%s" with primary key [%s]', $table->table(), implode($primaryKey, ', ')));
184
        }
185 22
        $conditions = array_combine($key, $primaryKey);
186 22
        $query = $table->find('all')->where($conditions);
187 22
        $event = $this->dispatchEvent('Action.Crud.onFindEntity', compact('query'));
188 22
        if ($event->result) {
189 10
            $query = $event->result;
190 10
        }
191 22
        $entity = $query->firstOrFail();
192
193 18
        return $entity;
194
    }
195
196
    /**
197
     * Save entity.
198
     *
199
     * @param EntityInterface $entity An Entity instance.
200
     * @return EntityInterface
201
     */
202 8
    protected function _save($entity)
203
    {
204 8
        if ($this->table()->save($entity)) {
205 6
            return $entity;
206
        } else {
207 2
            throw new ValidationException(__('Validation on {0} failed', $this->table()->alias()), 0, null, $entity->errors());
208
        }
209
    }
210
211
    /**
212
     * Model id getter.
213
     *
214
     * @return mixed|string
215
     */
216
    public function id()
217
    {
218
        return $this->_id;
219
    }
220
221
    /**
222
     * Model id field name getter.
223
     *
224
     * @return string
225
     */
226
    public function idName()
227
    {
228
        return $this->_idName;
229
    }
230
231
    /**
232
     * Parent id getter.
233
     *
234
     * @return mixed|string
235
     */
236 9
    public function parentId()
237
    {
238 9
        return $this->_parentId;
239
    }
240
241
    /**
242
     * Parent model id field name getter.
243
     *
244
     * @return mixed|string
245
     */
246 9
    public function parentIdName()
247
    {
248 9
        return $this->_parentIdName;
249
    }
250
251
    /**
252
     * Describe table with full details.
253
     *
254
     * @return array
255
     */
256 1
    protected function _describe()
257
    {
258 1
        $table = $this->table();
259 1
        $schema = $table->schema();
260
261 1
        $entity = $this->_newEntity();
262
//		$q=$entity->visibleProperties();
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
263 1
        $reverseRouter = new ReverseRouting();
264 1
        $path = $reverseRouter->indexPath($this);
265
        $actions = [
266 1
            'index' => $reverseRouter->link('self', $path, 'GET'),
267 1
            'add' => $reverseRouter->link('add', $path, 'POST'),
268 1
            'edit' => $reverseRouter->link('edit', $path . '/{id}', 'PUT'),
269 1
            'delete' => $reverseRouter->link('delete', $path . '/{id}', 'DELETE'),
270 1
        ];
271
272 1
        $validators = [];
273 1
        foreach ($table->validator()
274 1
                       ->getIterator() as $name => $field) {
275 1
            $validators[$name] = [
276 1
                'validatePresence' => $field->isPresenceRequired(),
277 1
                'emptyAllowed' => $field->isEmptyAllowed(),
278 1
                'rules' => []
279 1
            ];
280 1
            foreach ($field->getIterator() as $ruleName => $rule) {
281 1
                $_rule = $rule->get('rule');
282 1
                if (is_callable($_rule)) {
283
                    continue;
284
                }
285 1
                $params = $rule->get('pass');
286 1
                if (is_callable($params)) {
287
                    $params = null;
288
                }
289 1
                if (is_array($params)) {
290 1
                    foreach ($params as &$param) {
291
                        if (is_callable($param)) {
292
                            $param = null;
293
                        }
294 1
                    }
295 1
                }
296 1
                $validators[$name]['rules'][$ruleName] = [
297 1
                    'message' => $rule->get('message'),
298 1
                    'on' => $rule->get('on'),
299 1
                    'rule' => $_rule,
300 1
                    'params' => $params,
301 1
                    'last' => $rule->get('last'),
302
                ];
303 1
            }
304 1
        }
305
306 1
        $labels = collection($schema->columns())
307
            ->map(function ($column) use ($schema) {
308
                return [
309 1
                    'name' => $column,
310 1
                    'label' => __(Inflector::humanize(preg_replace('/_id$/', '', $column)))
311 1
                ];
312 1
            })
313 1
            ->combine('name', 'label')
314 1
            ->toArray();
315
316 1
        $associationTypes = ['BelongsTo', 'HasOne', 'HasMany', 'BelongsToMany'];
317 1
        $associations = collection($associationTypes)
318
            ->map(function ($type) use ($table) {
319
                return [
320 1
                    'type' => $type,
321 1
                    'assocs' => collection($table->associations()->type($type))
322
                        ->map(function ($assoc) {
323 1
                            return $assoc->target()->table();
324 1
                        })
325 1
                        ->toArray()
326 1
                ];
327 1
            })
328 1
            ->combine('type', 'assocs')
329 1
            ->toArray();
330
331 1
        $fieldTypes = collection($schema->columns())
332 1
            ->map(function ($column) use ($schema) {
333
                return [
334 1
                    'name' => $column,
335 1
                    'column' => $schema->column($column)
336 1
                ];
337 1
            })
338 1
            ->combine('name', 'column')
339 1
            ->toArray();
340
341
        return [
342
            'entity' => [
343 1
                'hidden' => $entity->hiddenProperties(),
344
345
                // ... fields with data types
346 1
            ],
347
            'schema' => [
348 1
                'columns' => $fieldTypes,
349
                'labels' => $labels
350 1
            ],
351 1
            'validators' => $validators,
352 1
            'relations' => $associations,
353
            'actions' => $actions
354 1
        ];
355
    }
356
}
357