Completed
Push — master ( 3f1f9d...ab54c8 )
by Ingo
08:52
created

Hierarchy::getDescendantIDList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Hierarchy;
4
5
use SilverStripe\Admin\LeftAndMain;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\ORM\DataList;
8
use SilverStripe\ORM\SS_List;
9
use SilverStripe\ORM\ValidationResult;
10
use SilverStripe\ORM\ArrayList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataExtension;
13
use SilverStripe\Versioned\Versioned;
14
use Exception;
15
16
/**
17
 * DataObjects that use the Hierarchy extension can be be organised as a hierarchy, with children and parents. The most
18
 * obvious example of this is SiteTree.
19
 *
20
 * @property int $ParentID
21
 * @property DataObject|Hierarchy $owner
22
 * @method DataObject Parent()
23
 */
24
class Hierarchy extends DataExtension
25
{
26
27
    /**
28
     * Cache for {@see numChildren()}
29
     *
30
     * @var int
31
     */
32
    protected $_cache_numChildren = null;
33
34
    /**
35
     * Cache for {@see Children()}
36
     *
37
     * @var SS_List
38
     */
39
    protected $_cache_children = null;
40
41
    /**
42
     * The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
43
     * this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
44
     * lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
45
     * children, the actual node count will be 50 (all root nodes plus first expanded child).
46
     *
47
     * @config
48
     * @var int
49
     */
50
    private static $node_threshold_total = 50;
51
52
    /**
53
     * Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
54
     * server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
55
     * this value typically won't display any children, although this is configurable through the $nodeCountCallback
56
     * parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
57
     *
58
     * @config
59
     * @var int
60
     */
61
    private static $node_threshold_leaf = 250;
62
63
    /**
64
     * A list of classnames to exclude from display in both the CMS and front end
65
     * displays. ->Children() and ->AllChildren affected.
66
     * Especially useful for big sets of pages like listings
67
     * If you use this, and still need the classes to be editable
68
     * then add a model admin for the class
69
     * Note: Does not filter subclasses (non-inheriting)
70
     *
71
     * @var array
72
     * @config
73
     */
74
    private static $hide_from_hierarchy = array();
75
76
    /**
77
     * A list of classnames to exclude from display in the page tree views of the CMS,
78
     * unlike $hide_from_hierarchy above which effects both CMS and front end.
79
     * Especially useful for big sets of pages like listings
80
     * If you use this, and still need the classes to be editable
81
     * then add a model admin for the class
82
     * Note: Does not filter subclasses (non-inheriting)
83
     *
84
     * @var array
85
     * @config
86
     */
87
    private static $hide_from_cms_tree = array();
88
89
    public static function get_extra_config($class, $extension, $args)
0 ignored issues
show
Unused Code introduced by
The parameter $extension is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
90
    {
91
        return array(
92
            'has_one' => array('Parent' => $class)
93
        );
94
    }
95
96
    /**
97
     * Validate the owner object - check for existence of infinite loops.
98
     *
99
     * @param ValidationResult $validationResult
100
     */
101
    public function validate(ValidationResult $validationResult)
102
    {
103
        // The object is new, won't be looping.
104
        /** @var DataObject|Hierarchy $owner */
105
        $owner = $this->owner;
106
        if (!$owner->ID) {
107
            return;
108
        }
109
        // The object has no parent, won't be looping.
110
        if (!$owner->ParentID) {
111
            return;
112
        }
113
        // The parent has not changed, skip the check for performance reasons.
114
        if (!$owner->isChanged('ParentID')) {
0 ignored issues
show
Bug introduced by
The method isChanged does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
115
            return;
116
        }
117
118
        // Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
119
        $node = $owner;
120
        while ($node && $node->ParentID) {
121
            if ((int)$node->ParentID === (int)$owner->ID) {
122
                // Hierarchy is looping.
123
                $validationResult->addError(
124
                    _t(
125
                        'Hierarchy.InfiniteLoopNotAllowed',
126
                        'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
127
                        'First argument is the class that makes up the hierarchy.',
128
                        array('type' => $owner->class)
129
                    ),
130
                    'bad',
131
                    'INFINITE_LOOP'
132
                );
133
                break;
134
            }
135
            $node = $node->Parent();
136
        }
137
    }
138
139
140
    /**
141
     * Get a list of this DataObject's and all it's descendants IDs.
142
     *
143
     * @return int[]
144
     */
145
    public function getDescendantIDList()
146
    {
147
        $idList = array();
148
        $this->loadDescendantIDListInto($idList);
149
        return $idList;
150
    }
151
152
    /**
153
     * Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
154
     *
155
     * @param array $idList Array to put results in.
156
     * @param DataObject|Hierarchy $node
157
     */
158
    protected function loadDescendantIDListInto(&$idList, $node = null)
159
    {
160
        if (!$node) {
161
            $node = $this->owner;
162
        }
163
        $children = $node->AllChildren();
0 ignored issues
show
Bug introduced by
The method AllChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
164
        foreach ($children as $child) {
165
            if (!in_array($child->ID, $idList)) {
166
                $idList[] = $child->ID;
167
                $this->loadDescendantIDListInto($idList, $child);
168
            }
169
        }
170
    }
171
172
    /**
173
     * Get the children for this DataObject filtered by canView()
174
     *
175
     * @return SS_List
176
     */
177
    public function Children()
178
    {
179
        if ($this->_cache_children) {
180
            return $this->_cache_children;
181
        }
182
183
        $this->_cache_children = $this
0 ignored issues
show
Bug introduced by
The method stageChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
184
            ->owner
185
            ->stageChildren(false)
186
            ->filterByCallback(function (DataObject $record) {
187
                return $record->canView();
188
            });
189
        return $this->_cache_children;
190
    }
191
192
    /**
193
     * Return all children, including those 'not in menus'.
194
     *
195
     * @return DataList
196
     */
197
    public function AllChildren()
198
    {
199
        return $this->owner->stageChildren(true);
0 ignored issues
show
Bug introduced by
The method stageChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
200
    }
201
202
    /**
203
     * Return all children, including those that have been deleted but are still in live.
204
     * - Deleted children will be marked as "DeletedFromStage"
205
     * - Added children will be marked as "AddedToStage"
206
     * - Modified children will be marked as "ModifiedOnStage"
207
     * - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
208
     *
209
     * @return ArrayList
210
     */
211
    public function AllChildrenIncludingDeleted()
212
    {
213
        $stageChildren = $this->owner->stageChildren(true);
0 ignored issues
show
Bug introduced by
The method stageChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
214
215
        // Add live site content that doesn't exist on the stage site, if required.
216
        if ($this->owner->hasExtension(Versioned::class)) {
0 ignored issues
show
Bug introduced by
The method hasExtension does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
217
            // Next, go through the live children.  Only some of these will be listed
218
            $liveChildren = $this->owner->liveChildren(true, true);
0 ignored issues
show
Bug introduced by
The method liveChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
219
            if ($liveChildren) {
220
                $merged = new ArrayList();
221
                $merged->merge($stageChildren);
222
                $merged->merge($liveChildren);
223
                $stageChildren = $merged;
224
            }
225
        }
226
        $this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren);
0 ignored issues
show
Bug introduced by
The method extend does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
227
        return $stageChildren;
228
    }
229
230
    /**
231
     * Return all the children that this page had, including pages that were deleted from both stage & live.
232
     *
233
     * @return DataList
234
     * @throws Exception
235
     */
236
    public function AllHistoricalChildren()
237
    {
238
        if (!$this->owner->hasExtension(Versioned::class)) {
0 ignored issues
show
Bug introduced by
The method hasExtension does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
239
            throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
240
        }
241
242
        $baseTable = $this->owner->baseTable();
0 ignored issues
show
Bug introduced by
The method baseTable does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
243
        $parentIDColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ParentID');
0 ignored issues
show
Bug introduced by
The method getSchema does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
244
        return Versioned::get_including_deleted(
245
            $this->owner->baseClass(),
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
246
            [ $parentIDColumn => $this->owner->ID ],
247
            "\"{$baseTable}\".\"ID\" ASC"
248
        );
249
    }
250
251
    /**
252
     * Return the number of children that this page ever had, including pages that were deleted.
253
     *
254
     * @return int
255
     */
256
    public function numHistoricalChildren()
257
    {
258
        return $this->AllHistoricalChildren()->count();
259
    }
260
261
    /**
262
     * Return the number of direct children. By default, values are cached after the first invocation. Can be
263
     * augumented by {@link augmentNumChildrenCountQuery()}.
264
     *
265
     * @param bool $cache Whether to retrieve values from cache
266
     * @return int
267
     */
268
    public function numChildren($cache = true)
269
    {
270
        // Load if caching
271
        if ($cache && isset($this->_cache_numChildren)) {
272
            return $this->_cache_numChildren;
273
        }
274
275
        // We call stageChildren(), because Children() has canView() filtering
276
        $children = (int)$this->owner->stageChildren(true)->Count();
0 ignored issues
show
Bug introduced by
The method stageChildren does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
277
278
        // Save if caching
279
        if ($cache) {
280
            $this->_cache_numChildren = $children;
281
        }
282
        return $children;
283
    }
284
285
    /**
286
     * Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
287
     *
288
     * @return bool
289
     */
290
    public function showingCMSTree()
291
    {
292
        if (!Controller::has_curr() || !class_exists(LeftAndMain::class)) {
293
            return false;
294
        }
295
        $controller = Controller::curr();
296
        return $controller instanceof LeftAndMain
0 ignored issues
show
Bug introduced by
The class SilverStripe\Admin\LeftAndMain does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
297
            && in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
298
    }
299
300
    /**
301
     * Return children in the stage site.
302
     *
303
     * @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
304
     *                      extension is applied to {@link SiteTree}.
305
     * @return DataList
306
     */
307
    public function stageChildren($showAll = false)
308
    {
309
        $hideFromHierarchy = $this->owner->config()->hide_from_hierarchy;
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
310
        $hideFromCMSTree = $this->owner->config()->hide_from_cms_tree;
311
        $staged = DataObject::get($this->ownerBaseClass)
312
                ->filter('ParentID', (int)$this->owner->ID)
313
                ->exclude('ID', (int)$this->owner->ID);
314
        if ($hideFromHierarchy) {
315
            $staged = $staged->exclude('ClassName', $hideFromHierarchy);
316
        }
317
        if ($hideFromCMSTree && $this->showingCMSTree()) {
318
            $staged = $staged->exclude('ClassName', $hideFromCMSTree);
319
        }
320
        if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<SilverStripe\ORM\Hierarchy\Hierarchy>; however, SilverStripe\ORM\DataObjectSchema::fieldSpec() does only seem to accept string|object<SilverStripe\ORM\DataObject>, 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...
Bug Best Practice introduced by
The expression \SilverStripe\ORM\DataOb...->owner, 'ShowInMenus') of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
321
            $staged = $staged->filter('ShowInMenus', 1);
322
        }
323
        $this->owner->extend("augmentStageChildren", $staged, $showAll);
0 ignored issues
show
Bug introduced by
The method extend does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
324
        return $staged;
325
    }
326
327
    /**
328
     * Return children in the live site, if it exists.
329
     *
330
     * @param bool $showAll              Include all of the elements, even those not shown in the menus. Only
331
     *                                   applicable when extension is applied to {@link SiteTree}.
332
     * @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
333
     * @return DataList
334
     * @throws Exception
335
     */
336
    public function liveChildren($showAll = false, $onlyDeletedFromStage = false)
337
    {
338
        if (!$this->owner->hasExtension(Versioned::class)) {
0 ignored issues
show
Bug introduced by
The method hasExtension does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
339
            throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
340
        }
341
342
        $hideFromHierarchy = $this->owner->config()->hide_from_hierarchy;
0 ignored issues
show
Bug introduced by
The method config does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
343
        $hideFromCMSTree = $this->owner->config()->hide_from_cms_tree;
344
        $children = DataObject::get($this->owner->baseClass())
0 ignored issues
show
Bug introduced by
The method baseClass does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
345
            ->filter('ParentID', (int)$this->owner->ID)
346
            ->exclude('ID', (int)$this->owner->ID)
347
            ->setDataQueryParam(array(
348
                'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
349
                'Versioned.stage' => 'Live'
350
            ));
351
        if ($hideFromHierarchy) {
352
            $children = $children->exclude('ClassName', $hideFromHierarchy);
353
        }
354
        if ($hideFromCMSTree && $this->showingCMSTree()) {
355
            $children = $children->exclude('ClassName', $hideFromCMSTree);
356
        }
357
        if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<SilverStripe\ORM\Hierarchy\Hierarchy>; however, SilverStripe\ORM\DataObjectSchema::fieldSpec() does only seem to accept string|object<SilverStripe\ORM\DataObject>, 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...
Bug Best Practice introduced by
The expression \SilverStripe\ORM\DataOb...->owner, 'ShowInMenus') of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
358
            $children = $children->filter('ShowInMenus', 1);
359
        }
360
361
        return $children;
362
    }
363
364
    /**
365
     * Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
366
     * is returned.
367
     *
368
     * @param string $filter
369
     * @return DataObject
370
     */
371
    public function getParent($filter = null)
372
    {
373
        $parentID = $this->owner->ParentID;
374
        if (empty($parentID)) {
375
            return null;
376
        }
377
        $idSQL = $this->owner->getSchema()->sqlColumnForField($this->ownerBaseClass, 'ID');
0 ignored issues
show
Bug introduced by
The method getSchema does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
378
        return DataObject::get_one($this->ownerBaseClass, array(
379
            array($idSQL => $parentID),
380
            $filter
381
        ));
382
    }
383
384
    /**
385
     * Return all the parents of this class in a set ordered from the closest to furtherest parent.
386
     *
387
     * @param bool $includeSelf
388
     * @return ArrayList
389
     */
390
    public function getAncestors($includeSelf = false)
391
    {
392
        $ancestors = new ArrayList();
393
        $object = $this->owner;
394
395
        if ($includeSelf) {
396
            $ancestors->push($object);
397
        }
398
        while ($object = $object->getParent()) {
399
            $ancestors->push($object);
400
        }
401
402
        return $ancestors;
403
    }
404
405
    /**
406
     * Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
407
     *
408
     * @param string $separator
409
     * @return string
410
     */
411
    public function getBreadcrumbs($separator = ' &raquo; ')
412
    {
413
        $crumbs = array();
414
        $ancestors = array_reverse($this->owner->getAncestors()->toArray());
0 ignored issues
show
Bug introduced by
The method getAncestors does only exist in SilverStripe\ORM\Hierarchy\Hierarchy, but not in SilverStripe\ORM\DataObject.

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...
415
        /** @var DataObject $ancestor */
416
        foreach ($ancestors as $ancestor) {
417
            $crumbs[] = $ancestor->getTitle();
418
        }
419
        $crumbs[] = $this->owner->getTitle();
0 ignored issues
show
Bug introduced by
The method getTitle does only exist in SilverStripe\ORM\DataObject, but not in SilverStripe\ORM\Hierarchy\Hierarchy.

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...
420
        return implode($separator, $crumbs);
421
    }
422
423
    /**
424
     * Flush all Hierarchy caches:
425
     * - Children (instance)
426
     * - NumChildren (instance)
427
     * - Marked (global)
428
     * - Expanded (global)
429
     * - TreeOpened (global)
430
     */
431
    public function flushCache()
432
    {
433
        $this->_cache_children = null;
434
        $this->_cache_numChildren = null;
435
    }
436
}
437