Passed
Pull Request — master (#5)
by Maksim
04:51 queued 01:17
created

CompositeBelongsTo::getResults()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 9.6111
cc 5
nc 6
nop 0
1
<?php
2
3
namespace MaksimM\CompositePrimaryKeys\Eloquent\Relationships;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7
use MaksimM\CompositePrimaryKeys\Exceptions\WrongRelationConfigurationException;
8
use MaksimM\CompositePrimaryKeys\Scopes\CompositeKeyScope;
9
10
class CompositeBelongsTo extends BelongsTo
11
{
12
    protected $magicKeyDelimiter = '___';
13
14
    /**
15
     * Set the base constraints on the relation query.
16
     *
17
     * @throws WrongRelationConfigurationException
18
     *
19
     * @return void
20
     */
21
    public function addConstraints()
22
    {
23
        if (static::$constraints) {
24
            // For belongs to relationships, which are essentially the inverse of has one
25
            // or has many relationships, we need to actually query on the primary key
26
            // of the related models matching on the foreign key that's on a parent.
27
            $table = $this->related->getTable();
28
29
            $ownerKeys = $this->getOwnerKeys();
30
            $foreignKeys = $this->getForeignKeys();
31
32
            if (count($ownerKeys) != count($foreignKeys)) {
33
                throw new WrongRelationConfigurationException();
34
            }
35
            foreach ($ownerKeys as $keyIndex => $key) {
36
                $this->query->where($table.'.'.$key, '=', $this->child->{$foreignKeys[$keyIndex]});
37
            }
38
        }
39
    }
40
41
    /**
42
     * Get the results of the relationship.
43
     *
44
     * @return mixed
45
     */
46
    public function getResults()
47
    {
48
        $foreignKey = !is_array($this->foreignKey) ? [$this->foreignKey] : $this->foreignKey;
0 ignored issues
show
introduced by
The condition is_array($this->foreignKey) is always false.
Loading history...
49
        foreach ($foreignKey as $foreignKeyVal) {
50
            if (is_null($this->child->{$foreignKeyVal})) {
51
                return $this->getDefaultFor($this->parent);
52
            }
53
        }
54
55
        return $this->query->first() ?: $this->getDefaultFor($this->parent);
56
    }
57
58
    /**
59
     * Set the constraints for an eager load of the relation.
60
     *
61
     * @param array $models
62
     *
63
     * @return void
64
     */
65
    public function addEagerConstraints(array $models)
66
    {
67
        $ownerKeys = $this->getOwnerKeys();
68
69
        // We'll grab the primary key name of the related models since it could be set to
70
        // a non-standard name and not "id". We will then construct the constraint for
71
        // our eagerly loading query so it returns the proper models from execution.
72
        (new CompositeKeyScope(array_map(function ($keyName) {
73
            return  $this->related->getTable().'.'.$keyName;
74
        }, $ownerKeys), $this->getEagerModelKeys($models), false, method_exists($this->related, 'getBinaryColumns') ? $this->related->getBinaryColumns() : []))->apply($this->query);
75
    }
76
77
    public function getOwnerKeys()
78
    {
79
        if (method_exists($this->parent, 'hasCompositeIndex') && $this->parent->hasCompositeIndex() && !is_array($this->ownerKey) && strpos($this->ownerKey, $this->magicKeyDelimiter) !== false) {
80
            $this->ownerKey = explode($this->magicKeyDelimiter, $this->ownerKey);
0 ignored issues
show
Documentation Bug introduced by
It seems like explode($this->magicKeyD...miter, $this->ownerKey) of type string[] is incompatible with the declared type string of property $ownerKey.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
81
        }
82
83
        return !is_array($this->ownerKey) ? [$this->ownerKey] : $this->ownerKey;
0 ignored issues
show
introduced by
The condition is_array($this->ownerKey) is always false.
Loading history...
84
    }
85
86
    public function getForeignKeys()
87
    {
88
        if (method_exists($this->related, 'hasCompositeIndex') && $this->related->hasCompositeIndex() && !is_array($this->foreignKey) && strpos($this->foreignKey, $this->magicKeyDelimiter) !== false) {
89
            $this->foreignKey = explode($this->magicKeyDelimiter, $this->foreignKey);
0 ignored issues
show
Documentation Bug introduced by
It seems like explode($this->magicKeyD...ter, $this->foreignKey) of type string[] is incompatible with the declared type string of property $foreignKey.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
90
        }
91
92
        return !is_array($this->foreignKey) ? [$this->foreignKey] : $this->foreignKey;
0 ignored issues
show
introduced by
The condition is_array($this->foreignKey) is always false.
Loading history...
93
    }
94
95
    /**
96
     * Gather the keys from an array of related models.
97
     *
98
     * @param array $models
99
     *
100
     * @return array
101
     */
102
    protected function getEagerModelKeys(array $models)
103
    {
104
        $keys = [];
105
106
        $ownerKeys = $this->getOwnerKeys();
107
        $foreignKeys = $this->getForeignKeys();
108
109
        // First we need to gather all of the keys from the parent models so we know what
110
        // to query for via the eager loading query. We will add them to an array then
111
        // execute a "where in" statement to gather up all of those related records.
112
        foreach ($models as $model) {
113
            $compositeKey = [];
114
            foreach ($foreignKeys as $index => $foreignKey) {
115
                if (!is_null($value = $model->{$foreignKey})) {
116
                    $compositeKey[$this->related->getTable().'.'.$ownerKeys[$index]] = $value;
117
                }
118
            }
119
            $keys[] = $compositeKey;
120
        }
121
122
        if (count($foreignKeys) == 1) {
123
            sort($keys);
124
125
            return array_values(array_unique($keys));
126
        } else {
127
            return $keys;
128
        }
129
    }
130
131
    /**
132
     * Match the eagerly loaded results to their parents.
133
     *
134
     * @param array      $models
135
     * @param Collection $results
136
     * @param string     $relation
137
     *
138
     * @return array
139
     */
140
    public function match(array $models, Collection $results, $relation)
141
    {
142
        $foreignKeys = $this->getForeignKeys();
143
144
        $ownerKeys = $this->getOwnerKeys();
145
146
        // First we will get to build a dictionary of the child models by their primary
147
        // key of the relationship, then we can easily match the children back onto
148
        // the parents using that dictionary and the primary key of the children.
149
        $dictionary = [];
150
151
        foreach ($results as $result) {
152
            $dictionary[implode($this->magicKeyDelimiter, array_map(function ($owner) use ($result) {
153
                return $result->getAttribute($owner);
154
            }, $ownerKeys))] = $result;
155
        }
156
157
        // Once we have the dictionary constructed, we can loop through all the parents
158
        // and match back onto their children using these keys of the dictionary and
159
        // the primary key of the children to map them onto the correct instances.
160
        foreach ($models as $model) {
161
            $foreignKey = implode($this->magicKeyDelimiter, array_map(function ($foreign) use ($model) {
162
                return $model->{$foreign};
163
            }, $foreignKeys));
164
            if (isset($dictionary[$foreignKey])) {
165
                $model->setRelation($relation, $dictionary[$foreignKey]);
166
            }
167
        }
168
169
        return $models;
170
    }
171
}
172