Completed
Push — master ( efc7f4...497618 )
by Filipe
02:47
created

BelongsTo   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 64.77%

Importance

Changes 5
Bugs 1 Features 2
Metric Value
wmc 18
c 5
b 1
f 2
lcom 1
cbo 12
dl 0
loc 185
ccs 57
cts 88
cp 0.6477
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A beforeSelect() 0 13 1
A afterSelect() 0 7 2
A registerListeners() 0 14 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\Select;
16
use Slick\Orm\Mapper\RelationInterface;
17
use Slick\Orm\Orm;
18
19
/**
20
 * BelongsTo (Many-To-On) relation
21
 *
22
 * @package Slick\Orm\Mapper\Relation
23
 * @author  Filipe Silva <[email protected]>
24
 */
25
class BelongsTo extends AbstractRelation implements RelationInterface
26
{
27
    /**
28
     * Relations utility methods
29
     */
30
    use RelationsUtilityMethods;
31
32
    /**
33
     * BelongsTo relation
34
     *
35
     * @param array|object $options The parameters from annotation
36
     */
37 6
    public function __construct($options)
38
    {
39
        /** @var \Slick\Orm\Annotations\BelongsTo $annotation */
40 6
        $annotation = $options['annotation'];
41 6
        unset($options['annotation']);
42 6
        $options['foreignKey'] = $annotation->getParameter('foreignKey');
43 6
        $options['parentEntity'] = $annotation->getValue();
44
45 6
        parent::__construct($options);
46
47 6
        $this->registerListeners();
48 6
    }
49
50
    /**
51
     * Handles the before select callback
52
     *
53
     * @param Select $event
54
     */
55 3
    public function beforeSelect(Select $event)
56
    {
57 2
        $fields = $this->getFieldsPrefixed();
58 2
        $table = $this->entityDescriptor->getTableName();
59 2
        $relateTable = $this->getParentTableName();
60 2
        $pmk = $this->getParentPrimaryKey();
61
62 2
        $onClause = "{$table}.{$this->getForeignKey()} = ".
63 2
            "{$relateTable}.{$pmk}";
64
65 3
        $query = $event->getQuery();
66 2
        $query->join($relateTable, $onClause, $fields, $relateTable);
67 2
    }
68
69
    /**
70
     * Handles the after select callback
71
     *
72
     * @param Select $event
73
     */
74
    public function afterSelect(Select $event)
75
    {
76
        foreach ($event->getEntityCollection() as $index => $entity) {
77
            $row = $event->getData()[$index];
78
            $entity->{$this->propertyName} = $this->getFromMap($row);
79
        }
80
    }
81
82
    /**
83
     * Registers the listener for before select event
84
     */
85 6
    private function registerListeners()
86
    {
87 6
        Orm::addListener(
88 6
            $this->entityDescriptor->className(),
89 6
            Select::ACTION_BEFORE_SELECT,
90 6
            [$this, 'beforeSelect']
91 3
        );
92
93 6
        Orm::addListener(
94 6
            $this->entityDescriptor->className(),
95 6
            Select::ACTION_AFTER_SELECT,
96 6
            [$this, 'afterSelect']
97 3
        );
98 6
    }
99
100
    /**
101
     * Prefixed fields for join
102
     *
103
     * @return array
104
     */
105 2
    private function getFieldsPrefixed()
106
    {
107 2
        $table = $this->getParentTableName();
108 2
        $data = [];
109
110 2
        foreach ($this->getParentFields() as $field) {
111 2
            $data[] = "{$field->getField()} AS ".
112 2
                "{$table}_{$field->getField()}";
113 1
        }
114 2
        return $data;
115
    }
116
117
    /**
118
     * Check if entity is already loaded and uses it.
119
     *
120
     * If not loaded the entity will be created and loaded to the repository's
121
     * identity map so that it can be reused next time.
122
     *
123
     * @param array $dataRow
124
     *
125
     * @return null|EntityCollection|EntityInterface|EntityInterface[]
126
     */
127
    private function getFromMap($dataRow)
128
    {
129
        $entity = $this->getParentRepository()
130
            ->getIdentityMap()
131
            ->get($dataRow[$this->getForeignKey()], false);
132
        if (false === $entity) {
133
            $entity = $this->map($dataRow);
134
        }
135
        return $entity;
136
    }
137
138
    /**
139
     * Creates and maps related entity
140
     *
141
     * @param array $dataRow
142
     *
143
     * @return null|EntityCollection|EntityInterface|EntityInterface[]
144
     */
145
    private function map($dataRow)
146
    {
147
        $data = $this->getData($dataRow);
148
        $pmk = $this->getParentPrimaryKey();
149
        $entity = (isset($data[$pmk]) && $data[$pmk])
150
            ? $this->getParentEntityMapper()->createFrom($data)
151
            : null;
152
        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 149 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...
153
    }
154
155
    /**
156
     * Gets a data array with fields and values for parent entity creation
157
     *
158
     * @param array $dataRow
159
     *
160
     * @return array
161
     */
162
    private function getData($dataRow)
163
    {
164
        $data = [];
165
        $relateTable = $this->getParentTableName();
166
        $regexp = "/{$relateTable}_(?P<name>.+)/i";
167
        foreach ($dataRow as $field => $value) {
168
            if (preg_match($regexp, $field, $matched)) {
169
                $data[$matched['name']] = $value;
170
            }
171
        }
172
        return $data;
173
    }
174
175
    /**
176
     * Loads the entity or entity collection for this relation
177
     *
178
     * @param EntityInterface $entity
179
     *
180
     * @return null|EntityInterface
181
     */
182 2
    public function load(EntityInterface $entity)
183
    {
184 2
        $adapter = $this->getAdapter();
185
186 2
        $relTable = $this->getParentTableName();
187 2
        $relPmk = $this->getParentPrimaryKey();
188
189 2
        $table = $this->getEntityDescriptor()->getTableName();
190 2
        $pmk = $this->getEntityDescriptor()->getPrimaryKey();
191 2
        $fnk = $this->getForeignKey();
192
193 2
        $onClause = "{$relTable}.{$relPmk} = {$table}.{$fnk}";
194
195 2
        $data = Sql::createSql($adapter)
196 2
            ->select($relTable)
197 2
            ->join($table, $onClause, null)
198 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...
199 2
                "{$table}.{$pmk->getField()} = :id" => [
200 2
                    ':id' => $entity->{$pmk->getName()}
201 1
                ]
202 1
            ])
203 2
            ->first();
204
205 2
        $relEntity = $this->getParentEntityMapper()->createFrom($data);
206
207 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 205 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...
208
    }
209
}