Passed
Push — master ( d8e957...a3e243 )
by Maksim
03:45
created

CompositeBelongsTo::addConstraints()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 16
rs 10
c 0
b 0
f 0
cc 4
nc 4
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
13
    protected $magicKeyDelimiter = '___';
14
15
    /**
16
     * Set the base constraints on the relation query.
17
     *
18
     * @return void
19
     * @throws WrongRelationConfigurationException
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
     * Set the constraints for an eager load of the relation.
42
     *
43
     * @param  array  $models
44
     * @return void
45
     */
46
    public function addEagerConstraints(array $models)
47
    {
48
        $ownerKeys = $this->getOwnerKeys();
49
50
        // We'll grab the primary key name of the related models since it could be set to
51
        // a non-standard name and not "id". We will then construct the constraint for
52
        // our eagerly loading query so it returns the proper models from execution.
53
        (new CompositeKeyScope(array_map(function($keyName){
54
            return  $this->related->getTable().'.'.$keyName;
55
        }, $ownerKeys), $this->getEagerModelKeys($models), false, method_exists($this->related,'getBinaryColumns') ? $this->related->getBinaryColumns() : []))->apply($this->query);
56
    }
57
58
    public function getOwnerKeys()
59
    {
60
        if(method_exists($this->parent, 'hasCompositeIndex') && $this->parent->hasCompositeIndex() && !is_array($this->ownerKey) && strpos($this->ownerKey,$this->magicKeyDelimiter) !== false)
61
            $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...
62
        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...
63
    }
64
65
    public function getForeignKeys()
66
    {
67
        if(method_exists($this->related, 'hasCompositeIndex') && $this->related->hasCompositeIndex() && !is_array($this->foreignKey) && strpos($this->foreignKey,$this->magicKeyDelimiter) !== false)
68
            $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...
69
        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...
70
    }
71
72
    /**
73
     * Gather the keys from an array of related models.
74
     *
75
     * @param  array  $models
76
     * @return array
77
     */
78
    protected function getEagerModelKeys(array $models)
79
    {
80
        $keys = [];
81
82
        $ownerKeys = $this->getOwnerKeys();
83
        $foreignKeys = $this->getForeignKeys();
84
85
        // First we need to gather all of the keys from the parent models so we know what
86
        // to query for via the eager loading query. We will add them to an array then
87
        // execute a "where in" statement to gather up all of those related records.
88
        foreach ($models as $model) {
89
            $compositeKey = [];
90
            foreach ($foreignKeys as $index => $foreignKey) {
91
                if (!is_null($value = $model->{$foreignKey})) {
92
                    $compositeKey[$this->related->getTable().'.'.$ownerKeys[$index]] = $value;
93
                }
94
            }
95
            $keys[] = $compositeKey;
96
        }
97
98
        if(count($foreignKeys) == 1) {
99
            // If there are no keys that were not null we will just return an array with null
100
            // so this query wont fail plus returns zero results, which should be what the
101
            // developer expects to happen in this situation. Otherwise we'll sort them.
102
            if (count($keys) === 0) {
103
                return [null];
104
            }
105
106
            sort($keys);
107
108
            return array_values(array_unique($keys));
109
        }
110
        else
111
            return $keys;
112
    }
113
114
    /**
115
     * Match the eagerly loaded results to their parents.
116
     *
117
     * @param  array   $models
118
     * @param  \Illuminate\Database\Eloquent\Collection  $results
119
     * @param  string  $relation
120
     * @return array
121
     */
122
    public function match(array $models, Collection $results, $relation)
123
    {
124
        $foreignKeys = $this->getForeignKeys();
125
126
        $ownerKeys = $this->getOwnerKeys();
127
128
        // First we will get to build a dictionary of the child models by their primary
129
        // key of the relationship, then we can easily match the children back onto
130
        // the parents using that dictionary and the primary key of the children.
131
        $dictionary = [];
132
133
        foreach ($results as $result) {
134
            $dictionary[implode($this->magicKeyDelimiter,array_map(function($owner) use ($result) {
135
                return $result->getAttribute($owner);
136
            }, $ownerKeys))] = $result;
137
        }
138
139
        // Once we have the dictionary constructed, we can loop through all the parents
140
        // and match back onto their children using these keys of the dictionary and
141
        // the primary key of the children to map them onto the correct instances.
142
        foreach ($models as $model) {
143
            $foreignKey = implode($this->magicKeyDelimiter,array_map(function($foreign) use ($model) {
144
                return $model->{$foreign};
145
            }, $foreignKeys));
146
            if (isset($dictionary[$foreignKey])) {
147
                $model->setRelation($relation, $dictionary[$foreignKey]);
148
            }
149
        }
150
151
        return $models;
152
    }
153
154
}