Completed
Push — master ( ffcb0c...30d2ce )
by ARCANEDEV
9s
created

AncestorsRelation::getAncestorsForModel()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 2
crap 12
1
<?php namespace Arcanedev\LaravelNestedSet\Eloquent;
2
3
use Arcanedev\LaravelNestedSet\Utilities\NestedSet;
4
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
5
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\Relation;
8
use Illuminate\Database\Query\Builder;
9
use InvalidArgumentException;
10
use RuntimeException;
11
12
/**
13
 * Class AncestorsRelation
14
 *
15
 * @package  Arcanedev\LaravelNestedSet\Eloquent
16
 * @author   ARCANEDEV <[email protected]>
17
 */
18
class AncestorsRelation extends Relation
19
{
20
    /* -----------------------------------------------------------------
21
     |  Properties
22
     | -----------------------------------------------------------------
23
     */
24
    /**
25
     * @var \Arcanedev\LaravelNestedSet\NodeTrait|\Illuminate\Database\Eloquent\Model
26
     */
27
    protected $parent;
28
29
    /**
30
     * @var QueryBuilder
31
     */
32
    protected $query;
33
34
    /* -----------------------------------------------------------------
35
     |  Constructor
36
     | -----------------------------------------------------------------
37
     */
38
    /**
39
     * AncestorsRelation constructor.
40
     *
41
     * @param  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  $builder
42
     * @param  \Illuminate\Database\Eloquent\Model|mixed          $model
43
     */
44 3
    public function __construct(QueryBuilder $builder, Model $model)
45
    {
46 3
        if ( ! NestedSet::isNode($model))
47 1
            throw new InvalidArgumentException('Model must be node.');
48
49 3
        parent::__construct($builder, $model);
50 3
    }
51
52
    /**
53
     * Add the constraints for a relationship count query.
54
     *
55
     * @param  \Illuminate\Database\Eloquent\Builder  $query
56
     * @param  \Illuminate\Database\Eloquent\Builder  $parentQuery
57
     *
58
     * @return \Illuminate\Database\Eloquent\Builder
59
     */
60
    public function getRelationExistenceCountQuery(EloquentBuilder $query, EloquentBuilder $parentQuery)
61
    {
62
        throw new RuntimeException('Cannot count ancestors, use depth functionality instead');
63
    }
64
65
    /**
66
     * Add the constraints for an internal relationship existence query.
67
     *
68
     * Essentially, these queries compare on column names like whereColumn.
69
     *
70
     * @param  \Illuminate\Database\Eloquent\Builder  $query
71
     * @param  \Illuminate\Database\Eloquent\Builder  $parentQuery
72
     * @param  array|mixed                            $columns
73
     *
74
     * @return \Illuminate\Database\Eloquent\Builder
75
     */
76
    public function getRelationExistenceQuery(EloquentBuilder $query, EloquentBuilder $parentQuery, $columns = [ '*' ])
77
    {
78
        $query->select($columns);
0 ignored issues
show
Bug introduced by
The method select() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean createSelectWithConstraint()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
79
80
        $table = $query->getModel()->getTable();
81
82
        $query->from($table.' as '.$hash = $this->getRelationSubSelectHash());
83
84
        $grammar = $query->getQuery()->getGrammar();
85
        $table   = $grammar->wrapTable($table);
86
        $hash    = $grammar->wrapTable($hash);
87
88
        $parentIdName = $grammar->wrap($this->parent->getParentIdName());
0 ignored issues
show
Bug introduced by
The method getParentIdName does only exist in Arcanedev\LaravelNestedSet\NodeTrait, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
89
90
        return $query->whereRaw("{$hash}.{$parentIdName} = {$table}.{$parentIdName}");
91
    }
92
93
    /**
94
     * @param  \Illuminate\Database\Eloquent\Builder  $query
95
     * @param  \Illuminate\Database\Eloquent\Builder  $parentQuery
96
     * @param  array|mixed                            $columns
97
     *
98
     * @return mixed
99
     */
100
    public function getRelationQuery(EloquentBuilder $query, EloquentBuilder $parentQuery, $columns = [ '*' ])
101
    {
102
        return $this->getRelationExistenceQuery($query, $parentQuery, $columns);
103
    }
104
105
    /**
106
     * Get a relationship join table hash.
107
     *
108
     * @return string
109
     */
110
    public function getRelationSubSelectHash()
111
    {
112
        return 'self_'.md5(microtime(true));
113
    }
114
115
    /**
116
     * Set the base constraints on the relation query.
117
     */
118 3
    public function addConstraints()
119
    {
120 3
        if ( ! static::$constraints) return;
121
122 3
        $this->query->whereAncestorOf($this->parent)->defaultOrder();
123 3
    }
124
125
    /**
126
     * Set the constraints for an eager load of the relation.
127
     *
128
     * @param  array  $models
129
     */
130
    public function addEagerConstraints(array $models)
131
    {
132
        $model = $this->query->getModel();
133
        $table = $model->getTable();
134
        $key   = $model->getKeyName();
135
136
        $grammar = $this->query->getQuery()->getGrammar();
137
        $table   = $grammar->wrapTable($table);
138
        $hash    = $grammar->wrap($this->getRelationSubSelectHash());
139
        $key     = $grammar->wrap($key);
140
        $lft     = $grammar->wrap($this->parent->getLftName());
0 ignored issues
show
Bug introduced by
The method getLftName does only exist in Arcanedev\LaravelNestedSet\NodeTrait, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
141
        $rgt     = $grammar->wrap($this->parent->getRgtName());
0 ignored issues
show
Bug introduced by
The method getRgtName does only exist in Arcanedev\LaravelNestedSet\NodeTrait, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
142
143
        $sql = "$key IN (SELECT DISTINCT($key) FROM {$table} INNER JOIN "
144
            . "(SELECT {$lft}, {$rgt} FROM {$table} WHERE {$key} IN (" . implode(',', $this->getKeys($models))
145
            . ")) AS $hash ON {$table}.{$lft} <= {$hash}.{$lft} AND {$table}.{$rgt} >= {$hash}.{$rgt})";
146
147
        $this->query
0 ignored issues
show
Documentation Bug introduced by
The method whereNested does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
148
            ->whereNested(function (Builder $inner) use ($sql) {
149
                $inner->whereRaw($sql);
150
            })
151
            ->orderBy('lft', 'ASC');
152
    }
153
154
    /**
155
     * Initialize the relation on a set of models.
156
     *
157
     * @param  array   $models
158
     * @param  string  $relation
159
     *
160
     * @return array
161
     */
162
    public function initRelation(array $models, $relation)
163
    {
164
        return $models;
165
    }
166
167
    /**
168
     * Match the eagerly loaded results to their parents.
169
     *
170
     * @param  array                                     $models
171
     * @param  \Illuminate\Database\Eloquent\Collection  $results
172
     * @param  string                                    $relation
173
     *
174
     * @return array
175
     */
176
    public function match(array $models, EloquentCollection $results, $relation)
177
    {
178
        foreach ($models as $model) {
179
            $model->setRelation($relation, $this->getAncestorsForModel($model, $results));
180
        }
181
182
        return $models;
183
    }
184
185
    /**
186
     * Get the results of the relationship.
187
     *
188
     * @return mixed
189
     */
190
    public function getResults()
191
    {
192
        return $this->query->get();
193
    }
194
195
    /**
196
     * Get ancestors for the given model.
197
     *
198
     * @param  \Illuminate\Database\Eloquent\Model       $model
199
     * @param  \Illuminate\Database\Eloquent\Collection  $results
200
     *
201
     * @return \Illuminate\Database\Eloquent\Collection
202
     */
203
    protected function getAncestorsForModel(Model $model, EloquentCollection $results)
204
    {
205
        $result = $this->related->newCollection();
206
207
        foreach ($results as $ancestor) {
208
            if ($ancestor->isAncestorOf($model))
209
                $result->push($ancestor);
210
        }
211
212
        return $result;
213
    }
214
}
215