Completed
Push — 4-cactus ( 5614e8...d450f9 )
by Stefano
20s queued 10s
created

UpdateAssociatedAction::execute()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 7
nop 1
dl 0
loc 23
rs 8.8333
c 0
b 0
f 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2016 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
14
namespace BEdita\API\Model\Action;
15
16
use BEdita\Core\Model\Action\BaseAction;
17
use Cake\Database\Expression\QueryExpression;
18
use Cake\Datasource\Exception\RecordNotFoundException;
19
use Cake\ORM\Association;
20
use Cake\ORM\Association\BelongsTo;
21
use Cake\ORM\Association\BelongsToMany;
22
use Cake\ORM\Association\HasOne;
23
use Cake\Utility\Hash;
24
25
/**
26
 * Command to update links between entities.
27
 *
28
 * @since 4.0.0
29
 */
30
class UpdateAssociatedAction extends BaseAction
31
{
32
33
    /**
34
     * Add associated action.
35
     *
36
     * @var \BEdita\Core\Model\Action\UpdateAssociatedAction
37
     */
38
    protected $Action;
39
40
    /**
41
     * Request instance.
42
     *
43
     * @var \Cake\Http\ServerRequest
44
     */
45
    protected $request;
46
47
    /**
48
     * {@inheritDoc}
49
     */
50
    protected function initialize(array $data)
51
    {
52
        $this->Action = $this->getConfig('action');
53
        $this->request = $this->getConfig('request');
54
    }
55
56
    /**
57
     * {@inheritDoc}
58
     */
59
    public function execute(array $data = [])
60
    {
61
        $association = $this->Action->getConfig('association');
62
        if (!($association instanceof Association)) {
63
            throw new \LogicException(__d('bedita', 'Unknown association type'));
64
        }
65
66
        $entity = $association->getSource()->get($data['primaryKey']);
67
68
        $requestData = $this->request->getData();
69
        if (!Hash::numeric(array_keys($requestData))) {
70
            $requestData = [$requestData];
71
        }
72
73
        $relatedEntities = $this->getTargetEntities($requestData, $association);
74
        $count = count($relatedEntities);
75
        if ($count === 0) {
76
            $relatedEntities = [];
77
        } elseif ($count === 1 && ($association instanceof BelongsTo || $association instanceof HasOne)) {
78
            $relatedEntities = reset($relatedEntities);
79
        }
80
81
        return $this->Action->execute(compact('entity', 'relatedEntities'));
82
    }
83
84
    /**
85
     * Get target entities.
86
     *
87
     * @param array $data Request data.
88
     * @param \Cake\ORM\Association $association Association.
89
     * @return \Cake\Datasource\EntityInterface[]
90
     */
91
    protected function getTargetEntities(array $data, Association $association)
92
    {
93
        $target = $association->getTarget();
94
        $primaryKeyField = $target->getPrimaryKey();
95
        $targetPKField = $target->aliasField($primaryKeyField);
0 ignored issues
show
Bug introduced by
It seems like $primaryKeyField can also be of type array; however, parameter $field of Cake\ORM\Table::aliasField() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

95
        $targetPKField = $target->aliasField(/** @scrutinizer ignore-type */ $primaryKeyField);
Loading history...
96
97
        $targetPrimaryKeys = array_unique(Hash::extract($data, '{*}.id'));
0 ignored issues
show
Bug introduced by
It seems like Cake\Utility\Hash::extract($data, '{*}.id') can also be of type ArrayAccess; however, parameter $array of array_unique() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
        $targetPrimaryKeys = array_unique(/** @scrutinizer ignore-type */ Hash::extract($data, '{*}.id'));
Loading history...
98
        if (empty($targetPrimaryKeys)) {
99
            return [];
100
        }
101
102
        $targetEntities = $target->find()
103
            ->where(function (QueryExpression $exp) use ($targetPKField, $targetPrimaryKeys) {
104
                return $exp->in($targetPKField, $targetPrimaryKeys);
105
            });
106
        $targetEntities = $targetEntities->indexBy($primaryKeyField)->toArray();
107
        /** @var \Cake\Datasource\EntityInterface[] $targetEntities */
108
109
        // sort following the original order
110
        uksort(
111
            $targetEntities,
112
            function ($a, $b) use ($targetPrimaryKeys) {
113
                return array_search($a, $targetPrimaryKeys) - array_search($b, $targetPrimaryKeys);
114
            }
115
        );
116
117
        foreach ($data as $datum) {
118
            $id = Hash::get($datum, 'id');
119
            $type = Hash::get($datum, 'type');
120
            if (!isset($targetEntities[$id]) || ($targetEntities[$id]->has('type') && $targetEntities[$id]->get('type') !== $type)) {
121
                throw new RecordNotFoundException(
122
                    __('Record not found in table "{0}"', $type ?: $target->getTable()),
123
                    400
124
                );
125
            }
126
127
            $meta = Hash::get($datum, '_meta.relation');
128
            if (!$this->request->is('delete') && $association instanceof BelongsToMany && $meta !== null) {
129
                $targetEntities[$id]->_joinData = $meta;
130
            }
131
        }
132
133
        return $targetEntities;
134
    }
135
}
136