Completed
Push — master ( 9cefff...efc7f4 )
by Filipe
07:40
created

BelongsTo::load()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 27
ccs 0
cts 22
cp 0
rs 8.8571
cc 2
eloc 18
nc 2
nop 1
crap 6
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
    public function __construct($options)
38
    {
39
        /** @var \Slick\Orm\Annotations\BelongsTo $annotation */
40
        $annotation = $options['annotation'];
41
        unset($options['annotation']);
42
        $options['foreignKey'] = $annotation->getParameter('foreignKey');
43
        $options['parentEntity'] = $annotation->getValue();
44
45
        parent::__construct($options);
46
47
        $this->registerListeners();
48
    }
49
50
    /**
51
     * Handles the before select callback
52
     *
53
     * @param Select $event
54
     */
55
    public function beforeSelect(Select $event)
56
    {
57
        $fields = $this->getFieldsPrefixed();
58
        $table = $this->entityDescriptor->getTableName();
59
        $relateTable = $this->getParentTableName();
60
        $pmk = $this->getParentPrimaryKey();
61
62
        $onClause = "{$table}.{$this->getForeignKey()} = ".
63
            "{$relateTable}.{$pmk}";
64
65
        $query = $event->getQuery();
66
        $query->join($relateTable, $onClause, $fields, $relateTable);
67
    }
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
    private function registerListeners()
86
    {
87
        Orm::addListener(
88
            $this->entityDescriptor->className(),
89
            Select::ACTION_BEFORE_SELECT,
90
            [$this, 'beforeSelect']
91
        );
92
93
        Orm::addListener(
94
            $this->entityDescriptor->className(),
95
            Select::ACTION_AFTER_SELECT,
96
            [$this, 'afterSelect']
97
        );
98
    }
99
100
    /**
101
     * Prefixed fields for join
102
     *
103
     * @return array
104
     */
105
    private function getFieldsPrefixed()
106
    {
107
        $table = $this->getParentTableName();
108
        $data = [];
109
110
        foreach ($this->getParentFields() as $field) {
111
            $data[] = "{$field->getField()} AS ".
112
                "{$table}_{$field->getField()}";
113
        }
114
        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
    public function load(EntityInterface $entity)
183
    {
184
        $adapter = Orm::getRepository(get_class($entity))
185
            ->getAdapter();
186
187
        $relTable = $this->getParentTableName();
188
        $relPmk = $this->getParentPrimaryKey();
189
190
        $table = $this->getEntityDescriptor()->getTableName();
191
        $pmk = $this->getEntityDescriptor()->getPrimaryKey();
192
        $fnk = $this->getForeignKey();
193
194
        $onClause = "{$relTable}.{$relPmk} = {$table}.{$fnk}";
195
196
        $data = Sql::createSql($adapter)
197
            ->select($relTable)
198
            ->join($table, $onClause, null)
199
            ->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...
200
                "{$table}.{$pmk->getField()} = :id" => [
201
                    ':id' => $entity->{$pmk->getName()}
202
                ]
203
            ])
204
            ->first();
205
        $relEntity = $this->getParentEntityMapper()->createFrom($data);
206
207
        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
}