Completed
Push — master ( 497618...87498e )
by Filipe
02:17
created

BelongsTo   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 99.12%

Importance

Changes 6
Bugs 1 Features 3
Metric Value
wmc 22
c 6
b 1
f 3
lcom 1
cbo 14
dl 0
loc 223
ccs 112
cts 113
cp 0.9912
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A beforeSelect() 0 17 2
A afterSelect() 0 10 3
A beforeSave() 0 11 2
B registerListeners() 0 26 1
A getFieldsPrefixed() 0 11 2
A getFromMap() 0 10 2
A map() 0 9 4
A getData() 0 12 3
B load() 0 27 2
1
<?php
2
3
/**
4
 * This file is part of slick/orm package
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Orm\Mapper\Relation;
11
12
use Slick\Database\Sql;
13
use Slick\Orm\Entity\EntityCollection;
14
use Slick\Orm\EntityInterface;
15
use Slick\Orm\Event\Save;
16
use Slick\Orm\Event\Select;
17
use Slick\Orm\Mapper\RelationInterface;
18
use Slick\Orm\Orm;
19
20
/**
21
 * BelongsTo (Many-To-On) relation
22
 *
23
 * @package Slick\Orm\Mapper\Relation
24
 * @author  Filipe Silva <[email protected]>
25
 */
26
class BelongsTo extends AbstractRelation implements RelationInterface
27
{
28
    /**
29
     * Relations utility methods
30
     */
31
    use RelationsUtilityMethods;
32
33
    /**
34
     * @readwrite
35
     * @var bool
36
     */
37
    protected $dependent = true;
38
39
    /**
40
     * BelongsTo relation
41
     *
42
     * @param array|object $options The parameters from annotation
43
     */
44 12
    public function __construct($options)
45
    {
46
        /** @var \Slick\Orm\Annotations\BelongsTo $annotation */
47 12
        $annotation = $options['annotation'];
48 12
        unset($options['annotation']);
49 12
        $options['foreignKey'] = $annotation->getParameter('foreignKey');
50 12
        $options['parentEntity'] = $annotation->getValue();
51 12
        $options['lazyLoaded'] = $annotation->getParameter('lazyLoaded');
52
53 12
        parent::__construct($options);
54
55 12
        $this->registerListeners();
56 12
    }
57
58
    /**
59
     * Handles the before select callback
60
     *
61
     * @param Select $event
62
     */
63 3
    public function beforeSelect(Select $event)
64
    {
65 3
        if ($this->isLazyLoaded()) {
66
            return;
67
        }
68
69 2
        $fields = $this->getFieldsPrefixed();
70 2
        $table = $this->entityDescriptor->getTableName();
71 2
        $relateTable = $this->getParentTableName();
72 2
        $pmk = $this->getParentPrimaryKey();
73
74 2
        $onClause = "{$table}.{$this->getForeignKey()} = ".
75 2
            "{$relateTable}.{$pmk}";
76
77 2
        $query = $event->getQuery();
78 2
        $query->join($relateTable, $onClause, $fields, $relateTable);
79 2
    }
80
81
    /**
82
     * Handles the after select callback
83
     *
84
     * @param Select $event
85
     */
86 4
    public function afterSelect(Select $event)
87
    {
88 4
        if ($this->isLazyLoaded()) {
89 2
            return;
90
        }
91 2
        foreach ($event->getEntityCollection() as $index => $entity) {
92 2
            $row = $event->getData()[$index];
93 2
            $entity->{$this->propertyName} = $this->getFromMap($row);
94 1
        }
95 2
    }
96
97 2
    public function beforeSave(Save $event)
98
    {
99 2
        $parent = $event->getEntity()->{$this->propertyName};
100 2
        if ($parent instanceof EntityInterface) {
101 2
            $parent = $parent->getId();
102 1
        }
103 2
        $event->params[$this->getForeignKey()] = $parent;
104 2
        $related = $this->getParentRepository()
105 2
            ->get($parent);
106 2
        $event->getEntity()->{$this->propertyName} = $related;
107 2
    }
108
109
    /**
110
     * Registers the listener for before select event
111
     */
112 12
    private function registerListeners()
113
    {
114 12
        Orm::addListener(
115 12
            $this->entityDescriptor->className(),
116 12
            Select::ACTION_BEFORE_SELECT,
117 12
            [$this, 'beforeSelect']
118 6
        );
119
120 12
        Orm::addListener(
121 12
            $this->entityDescriptor->className(),
122 12
            Select::ACTION_AFTER_SELECT,
123 12
            [$this, 'afterSelect']
124 6
        );
125
126 12
        Orm::addListener(
127 12
            $this->entityDescriptor->className(),
128 12
            Save::ACTION_BEFORE_INSERT,
129 12
            [$this, 'beforeSave']
130 6
        );
131
132 12
        Orm::addListener(
133 12
            $this->entityDescriptor->className(),
134 12
            Save::ACTION_BEFORE_UPDATE,
135 12
            [$this, 'beforeSave']
136 6
        );
137 12
    }
138
139
    /**
140
     * Prefixed fields for join
141
     *
142
     * @return array
143
     */
144 2
    private function getFieldsPrefixed()
145
    {
146 2
        $table = $this->getParentTableName();
147 2
        $data = [];
148
149 2
        foreach ($this->getParentFields() as $field) {
150 2
            $data[] = "{$field->getField()} AS ".
151 2
                "{$table}_{$field->getField()}";
152 1
        }
153 2
        return $data;
154
    }
155
156
    /**
157
     * Check if entity is already loaded and uses it.
158
     *
159
     * If not loaded the entity will be created and loaded to the repository's
160
     * identity map so that it can be reused next time.
161
     *
162
     * @param array $dataRow
163
     *
164
     * @return null|EntityCollection|EntityInterface|EntityInterface[]
165
     */
166 2
    private function getFromMap($dataRow)
167
    {
168 2
        $entity = $this->getParentRepository()
169 2
            ->getIdentityMap()
170 2
            ->get($dataRow[$this->getForeignKey()], false);
171 2
        if (false === $entity) {
172 2
            $entity = $this->map($dataRow);
173 1
        }
174 2
        return $entity;
175
    }
176
177
    /**
178
     * Creates and maps related entity
179
     *
180
     * @param array $dataRow
181
     *
182
     * @return null|EntityCollection|EntityInterface|EntityInterface[]
183
     */
184 2
    private function map($dataRow)
185
    {
186 2
        $data = $this->getData($dataRow);
187 2
        $pmk = $this->getParentPrimaryKey();
188 2
        $entity = (isset($data[$pmk]) && $data[$pmk])
189 2
            ? $this->getParentEntityMapper()->createFrom($data)
190 2
            : null;
191 2
        return null == $entity ? null : $this->registerEntity($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by isset($data[$pmk]) && $d...reateFrom($data) : null on line 188 can also be of type array<integer,object<Sli...EntityMapperInterface>>; however, Slick\Orm\Mapper\Relatio...ation::registerEntity() does only seem to accept object<Slick\Orm\EntityI...ntity\EntityCollection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
192
    }
193
194
    /**
195
     * Gets a data array with fields and values for parent entity creation
196
     *
197
     * @param array $dataRow
198
     *
199
     * @return array
200
     */
201 2
    private function getData($dataRow)
202
    {
203 2
        $data = [];
204 2
        $relateTable = $this->getParentTableName();
205 2
        $regexp = "/{$relateTable}_(?P<name>.+)/i";
206 2
        foreach ($dataRow as $field => $value) {
207 2
            if (preg_match($regexp, $field, $matched)) {
208 2
                $data[$matched['name']] = $value;
209 1
            }
210 1
        }
211 2
        return $data;
212
    }
213
214
    /**
215
     * Loads the entity or entity collection for this relation
216
     *
217
     * @param EntityInterface $entity
218
     *
219
     * @return null|EntityInterface
220
     */
221 2
    public function load(EntityInterface $entity)
222
    {
223 2
        $adapter = $this->getAdapter();
224
225 2
        $relTable = $this->getParentTableName();
226 2
        $relPmk = $this->getParentPrimaryKey();
227
228 2
        $table = $this->getEntityDescriptor()->getTableName();
229 2
        $pmk = $this->getEntityDescriptor()->getPrimaryKey();
230 2
        $fnk = $this->getForeignKey();
231
232 2
        $onClause = "{$relTable}.{$relPmk} = {$table}.{$fnk}";
233
234 2
        $data = Sql::createSql($adapter)
235 2
            ->select($relTable)
236 2
            ->join($table, $onClause, null)
237 2
            ->where([
0 ignored issues
show
Documentation introduced by
array("{$table}.{$pmk->g...ty->{$pmk->getName()})) is of type array<string|integer,array<string,?,{":id":"?"}>>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
238 2
                "{$table}.{$pmk->getField()} = :id" => [
239 2
                    ':id' => $entity->{$pmk->getName()}
240 1
                ]
241 1
            ])
242 2
            ->first();
243
244 2
        $relEntity = $this->getParentEntityMapper()->createFrom($data);
245
246 2
        return null == $relEntity ? null :$this->registerEntity($relEntity);
0 ignored issues
show
Bug introduced by
It seems like $relEntity defined by $this->getParentEntityMapper()->createFrom($data) on line 244 can also be of type array<integer,object<Sli...EntityMapperInterface>>; however, Slick\Orm\Mapper\Relatio...ation::registerEntity() does only seem to accept object<Slick\Orm\EntityI...ntity\EntityCollection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
247
    }
248
}