Issues (64)

src/Eloquent/Relationships/CompositeBelongsTo.php (5 issues)

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
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
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
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
        return $keys;
123
    }
124
125
    /**
126
     * Match the eagerly loaded results to their parents.
127
     *
128
     * @param array      $models
129
     * @param Collection $results
130
     * @param string     $relation
131
     *
132
     * @return array
133
     */
134
    public function match(array $models, Collection $results, $relation)
135
    {
136
        $foreignKeys = $this->getForeignKeys();
137
138
        $ownerKeys = $this->getOwnerKeys();
139
140
        // First we will get to build a dictionary of the child models by their primary
141
        // key of the relationship, then we can easily match the children back onto
142
        // the parents using that dictionary and the primary key of the children.
143
        $dictionary = [];
144
145
        foreach ($results as $result) {
146
            $dictionary[implode($this->magicKeyDelimiter, array_map(function ($owner) use ($result) {
147
                return $result->getAttribute($owner);
148
            }, $ownerKeys))] = $result;
149
        }
150
151
        // Once we have the dictionary constructed, we can loop through all the parents
152
        // and match back onto their children using these keys of the dictionary and
153
        // the primary key of the children to map them onto the correct instances.
154
        foreach ($models as $model) {
155
            $foreignKey = implode($this->magicKeyDelimiter, array_map(function ($foreign) use ($model) {
156
                return $model->{$foreign};
157
            }, $foreignKeys));
158
            if (isset($dictionary[$foreignKey])) {
159
                $model->setRelation($relation, $dictionary[$foreignKey]);
160
            }
161
        }
162
163
        return $models;
164
    }
165
}
166