Completed
Push — master ( f6a0b6...8e4f56 )
by Filipe
02:21
created

BelongsTo   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 223
Duplicated Lines 7.62 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 1 Features 4
Metric Value
wmc 22
c 7
b 1
f 4
lcom 1
cbo 14
dl 17
loc 223
ccs 113
cts 113
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
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
A beforeSelect() 17 17 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 26
    public function __construct($options)
45
    {
46
        /** @var \Slick\Orm\Annotations\BelongsTo $annotation */
47 26
        $annotation = $options['annotation'];
48 26
        unset($options['annotation']);
49 26
        $options['foreignKey'] = $annotation->getParameter('foreignKey');
50 26
        $options['parentEntity'] = $annotation->getValue();
51 26
        $options['lazyLoaded'] = $annotation->getParameter('lazyLoaded');
52
53 26
        parent::__construct($options);
54
55 26
        $this->registerListeners();
56 26
    }
57
58
    /**
59
     * Handles the before select callback
60
     *
61
     * @param Select $event
62
     */
63 4 View Code Duplication
    public function beforeSelect(Select $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
    {
65 4
        if ($this->isLazyLoaded()) {
66 2
            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 6
    public function afterSelect(Select $event)
87
    {
88 6
        if ($this->isLazyLoaded()) {
89 2
            return;
90
        }
91 4
        foreach ($event->getEntityCollection() as $index => $entity) {
92 4
            $row = $event->getData()[$index];
93 4
            $entity->{$this->propertyName} = $this->getFromMap($row);
94 2
        }
95 4
    }
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 14
    protected function registerListeners()
113
    {
114 14
        Orm::addListener(
115 14
            $this->entityDescriptor->className(),
116 14
            Select::ACTION_BEFORE_SELECT,
117 14
            [$this, 'beforeSelect']
118 7
        );
119
120 14
        Orm::addListener(
121 14
            $this->entityDescriptor->className(),
122 14
            Select::ACTION_AFTER_SELECT,
123 14
            [$this, 'afterSelect']
124 7
        );
125
126 14
        Orm::addListener(
127 14
            $this->entityDescriptor->className(),
128 14
            Save::ACTION_BEFORE_INSERT,
129 14
            [$this, 'beforeSave']
130 7
        );
131
132 14
        Orm::addListener(
133 14
            $this->entityDescriptor->className(),
134 14
            Save::ACTION_BEFORE_UPDATE,
135 14
            [$this, 'beforeSave']
136 7
        );
137 14
    }
138
139
    /**
140
     * Prefixed fields for join
141
     *
142
     * @return array
143
     */
144 4
    protected function getFieldsPrefixed()
145
    {
146 4
        $table = $this->getParentTableName();
147 4
        $data = [];
148
149 4
        foreach ($this->getParentFields() as $field) {
150 4
            $data[] = "{$field->getField()} AS ".
151 4
                "{$table}_{$field->getField()}";
152 2
        }
153 4
        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
    protected 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 4
    protected function map($dataRow)
185
    {
186 4
        $data = $this->getData($dataRow);
187 4
        $pmk = $this->getParentPrimaryKey();
188 4
        $entity = (isset($data[$pmk]) && $data[$pmk])
189 4
            ? $this->getParentEntityMapper()->createFrom($data)
190 4
            : null;
191 4
        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 4
    protected function getData($dataRow)
202
    {
203 4
        $data = [];
204 4
        $relateTable = $this->getParentTableName();
205 4
        $regexp = "/{$relateTable}_(?P<name>.+)/i";
206 4
        foreach ($dataRow as $field => $value) {
207 4
            if (preg_match($regexp, $field, $matched)) {
208 4
                $data[$matched['name']] = $value;
209 2
            }
210 2
        }
211 4
        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
}