Completed
Push — authenticator-refactor ( 0a18bb...b9e528 )
by Simon
08:12
created

PermissionCheckboxSetField::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 5
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Forms\FormField;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\ORM\SS_List;
9
use SilverStripe\ORM\ArrayList;
10
use SilverStripe\ORM\DataObjectInterface;
11
use SilverStripe\View\Requirements;
12
use InvalidArgumentException;
13
14
/**
15
 * Shows a categorized list of available permissions (through {@link Permission::get_codes()}).
16
 * Permissions which are assigned to a given {@link Group} record
17
 * (either directly, inherited from parent groups, or through a {@link PermissionRole})
18
 * will be checked automatically. All checkboxes for "inherited" permissions will be readonly.
19
 *
20
 * The field can gets its assignment data either from {@link Group} or {@link PermissionRole} records.
21
 */
22
class PermissionCheckboxSetField extends FormField
23
{
24
25
    /**
26
     * @var array Filter certain permission codes from the output.
27
     * Useful to simplify the interface
28
     */
29
    protected $hiddenPermissions = array();
30
31
    /**
32
     * @var SS_List
33
     */
34
    protected $records = null;
35
36
    /**
37
     * @var array Array Nested array in same notation as {@link CheckboxSetField}.
38
     */
39
    protected $source = null;
40
41
    /**
42
     * @param String $name
43
     * @param String $title
44
     * @param String $managedClass
45
     * @param String $filterField
46
     * @param Group|SS_List $records One or more {@link Group} or {@link PermissionRole} records
47
     *  used to determine permission checkboxes.
48
     *  Caution: saveInto() can only be used with a single record, all inherited permissions will be marked readonly.
49
     *  Setting multiple groups only makes sense in a readonly context. (Optional)
50
     */
51
    public function __construct($name, $title, $managedClass, $filterField, $records = null)
52
    {
53
        $this->filterField = $filterField;
0 ignored issues
show
Documentation introduced by
The property filterField does not exist on object<SilverStripe\Secu...issionCheckboxSetField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
54
        $this->managedClass = $managedClass;
0 ignored issues
show
Documentation introduced by
The property managedClass does not exist on object<SilverStripe\Secu...issionCheckboxSetField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
55
56
        if ($records instanceof SS_List) {
57
            $this->records = $records;
58
        } elseif ($records instanceof Group) {
59
            $this->records = new ArrayList(array($records));
60
        } elseif ($records) {
61
            throw new InvalidArgumentException(
62
                '$record should be either a Group record, or a SS_List of Group records'
63
            );
64
        }
65
66
        // Get all available codes in the system as a categorized nested array
67
        $this->source = Permission::get_codes(true);
68
69
        parent::__construct($name, $title);
70
    }
71
72
    /**
73
     * @param array $codes
74
     */
75
    public function setHiddenPermissions($codes)
76
    {
77
        $this->hiddenPermissions = $codes;
78
    }
79
80
    /**
81
     * @return array
82
     */
83
    public function getHiddenPermissions()
84
    {
85
        return $this->hiddenPermissions;
86
    }
87
88
    /**
89
     * @param array $properties
90
     * @return string
91
     */
92
    public function Field($properties = array())
93
    {
94
        $uninheritedCodes = array();
95
        $inheritedCodes = array();
96
        $records = ($this->records) ? $this->records : new ArrayList();
97
98
        // Get existing values from the form record (assuming the formfield name is a join field on the record)
99
        if (is_object($this->form)) {
100
            $record = $this->form->getRecord();
101
            if ($record
102
                && ($record instanceof Group || $record instanceof PermissionRole)
103
                && !$records->find('ID', $record->ID)
104
            ) {
105
                $records->push($record);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\ORM\SS_List as the method push() does only exist in the following implementations of said interface: SilverStripe\Forms\FieldList, SilverStripe\ORM\ArrayList, SilverStripe\ORM\UnsavedRelationList.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
106
            }
107
        }
108
109
        // Get all 'inherited' codes not directly assigned to the group (which is stored in $values)
110
        foreach ($records as $record) {
111
            // Get all uninherited permissions
112
            $relationMethod = $this->name;
113
            foreach ($record->$relationMethod() as $permission) {
114
                if (!isset($uninheritedCodes[$permission->Code])) {
115
                    $uninheritedCodes[$permission->Code] = array();
116
                }
117
                $uninheritedCodes[$permission->Code][] = _t(
118
                    'SilverStripe\\Security\\PermissionCheckboxSetField.AssignedTo',
119
                    'assigned to "{title}"',
120
                    array('title' => $record->dbObject('Title')->forTemplate())
121
                );
122
            }
123
124
            // Special case for Group records (not PermissionRole):
125
            // Determine inherited assignments
126
            if ($record instanceof Group) {
127
                // Get all permissions from roles
128 View Code Duplication
                if ($record->Roles()->count()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
129
                    foreach ($record->Roles() as $role) {
130
                        /** @var PermissionRole $role */
131
                        foreach ($role->Codes() as $code) {
132
                            if (!isset($inheritedCodes[$code->Code])) {
133
                                $inheritedCodes[$code->Code] = array();
134
                            }
135
                            $inheritedCodes[$code->Code][] = _t(
136
                                'SilverStripe\\Security\\PermissionCheckboxSetField.FromRole',
137
                                'inherited from role "{title}"',
138
                                'A permission inherited from a certain permission role',
139
                                array('title' => $role->dbObject('Title')->forTemplate())
140
                            );
141
                        }
142
                    }
143
                }
144
145
                // Get from parent groups
146
                $parentGroups = $record->getAncestors();
147
                if ($parentGroups) {
148
                    foreach ($parentGroups as $parent) {
149
                        if (!$parent->Roles()->Count()) {
150
                            continue;
151
                        }
152
                        foreach ($parent->Roles() as $role) {
153 View Code Duplication
                            if ($role->Codes()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
                                foreach ($role->Codes() as $code) {
155
                                    if (!isset($inheritedCodes[$code->Code])) {
156
                                        $inheritedCodes[$code->Code] = array();
157
                                    }
158
                                    $inheritedCodes[$code->Code][] = _t(
159
                                        'SilverStripe\\Security\\PermissionCheckboxSetField.FromRoleOnGroup',
160
                                        'inherited from role "%s" on group "%s"',
161
                                        'A permission inherited from a role on a certain group',
162
                                        array('roletitle' => $role->dbObject('Title')->forTemplate(), 'grouptitle' => $parent->dbObject('Title')->forTemplate())
163
                                    );
164
                                }
165
                            }
166
                        }
167 View Code Duplication
                        if ($parent->Permissions()->Count()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
168
                            foreach ($parent->Permissions() as $permission) {
169
                                if (!isset($inheritedCodes[$permission->Code])) {
170
                                    $inheritedCodes[$permission->Code] = array();
171
                                }
172
                                $inheritedCodes[$permission->Code][] =
173
                                _t(
174
                                    'SilverStripe\\Security\\PermissionCheckboxSetField.FromGroup',
175
                                    'inherited from group "{title}"',
176
                                    'A permission inherited from a certain group',
177
                                    array('title' => $parent->dbObject('Title')->forTemplate())
178
                                );
179
                            }
180
                        }
181
                    }
182
                }
183
            }
184
        }
185
186
        $odd = 0;
187
        $options = '';
188
        $globalHidden = (array)Config::inst()->get('SilverStripe\\Security\\Permission', 'hidden_permissions');
189
        if ($this->source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->source of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
190
            $privilegedPermissions = Permission::config()->privileged_permissions;
0 ignored issues
show
Documentation introduced by
The property privileged_permissions does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
191
192
            // loop through all available categorized permissions and see if they're assigned for the given groups
193
            foreach ($this->source as $categoryName => $permissions) {
194
                $options .= "<li><h5>$categoryName</h5></li>";
195
                foreach ($permissions as $code => $permission) {
196
                    if (in_array($code, $this->hiddenPermissions)) {
197
                        continue;
198
                    }
199
                    if (in_array($code, $globalHidden)) {
200
                        continue;
201
                    }
202
203
                    $value = $permission['name'];
204
205
                    $odd = ($odd + 1) % 2;
206
                    $extraClass = $odd ? 'odd' : 'even';
207
                    $extraClass .= ' val' . str_replace(' ', '', $code);
208
                    $itemID = $this->ID() . '_' . preg_replace('/[^a-zA-Z0-9]+/', '', $code);
209
                    $disabled = $inheritMessage = '';
210
                    $checked = (isset($uninheritedCodes[$code]) || isset($inheritedCodes[$code]))
211
                        ? ' checked="checked"'
212
                        : '';
213
                    $title = $permission['help']
214
                        ? 'title="' . htmlentities($permission['help'], ENT_COMPAT, 'UTF-8') . '" '
215
                        : '';
216
217
                    if (isset($inheritedCodes[$code])) {
218
                        // disable inherited codes, as any saving logic would be too complicate to express in this
219
                        // interface
220
                        $disabled = ' disabled="true"';
221
                        $inheritMessage = ' (' . join(', ', $inheritedCodes[$code]) . ')';
222
                    } elseif ($this->records && $this->records->Count() > 1 && isset($uninheritedCodes[$code])) {
223
                        // If code assignments are collected from more than one "source group",
224
                        // show its origin automatically
225
                        $inheritMessage = ' (' . join(', ', $uninheritedCodes[$code]).')';
226
                    }
227
228
                    // Disallow modification of "privileged" permissions unless currently logged-in user is an admin
229
                    if (!Permission::check('ADMIN') && in_array($code, $privilegedPermissions)) {
230
                        $disabled = ' disabled="true"';
231
                    }
232
233
                    // If the field is readonly, always mark as "disabled"
234
                    if ($this->readonly) {
235
                        $disabled = ' disabled="true"';
236
                    }
237
238
                    $inheritMessage = '<small>' . $inheritMessage . '</small>';
239
                    $icon = ($checked) ? 'check-mark-circle' : 'cancel-circled';
240
241
                    // Inherited codes are shown as a gray x
242
                    if (Permission::check('ADMIN') && $code != 'ADMIN') {
243
                        $icon = 'disable-circled';
244
                    }
245
246
                    // If the field is readonly, add a span that will replace the disabled checkbox input
247
                    if ($this->readonly) {
248
                        $options .= "<li class=\"$extraClass\">"
249
                            . "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\""
250
                            . " value=\"$code\"$checked class=\"checkbox\" />"
251
                            . "<label {$title}for=\"$itemID\">"
252
                            . "<span class=\"font-icon-$icon\"></span>"
253
                            . "$value$inheritMessage</label>"
254
                            . "</li>\n";
255
                    } else {
256
                        $options .= "<li class=\"$extraClass\">"
257
                            . "<input id=\"$itemID\"$disabled name=\"$this->name[$code]\" type=\"checkbox\""
258
                            . " value=\"$code\"$checked class=\"checkbox\" />"
259
                            . "<label {$title}for=\"$itemID\">$value$inheritMessage</label>"
260
                            . "</li>\n";
261
                    }
262
                }
263
            }
264
        }
265
        if ($this->readonly) {
266
            return
0 ignored issues
show
Bug Best Practice introduced by
The return type of return "<ul id=\"{$this-... . $options . '</ul> '; (string) is incompatible with the return type of the parent method SilverStripe\Forms\FormField::Field of type SilverStripe\ORM\FieldType\DBHTMLText.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
267
                "<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" .
268
                "<li class=\"help\">" .
269
                _t(
270
                    'SilverStripe\\Security\\Permission.UserPermissionsIntro',
271
                    'Assigning groups to this user will adjust the permissions they have.'
272
                    . ' See the groups section for details of permissions on individual groups.'
273
                ) .
274
                "</li>" .
275
                $options .
276
                "</ul>\n";
277
        } else {
278
            return
0 ignored issues
show
Bug Best Practice introduced by
The return type of return "<ul id=\"{$this-... . $options . '</ul> '; (string) is incompatible with the return type of the parent method SilverStripe\Forms\FormField::Field of type SilverStripe\ORM\FieldType\DBHTMLText.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
279
                "<ul id=\"{$this->ID()}\" class=\"optionset checkboxsetfield{$this->extraClass()}\">\n" .
280
                $options .
281
                "</ul>\n";
282
        }
283
    }
284
285
    /**
286
     * Update the permission set associated with $record DataObject
287
     *
288
     * @param DataObjectInterface $record
289
     */
290
    public function saveInto(DataObjectInterface $record)
291
    {
292
        $fieldname = $this->name;
293
        $managedClass = $this->managedClass;
0 ignored issues
show
Documentation introduced by
The property managedClass does not exist on object<SilverStripe\Secu...issionCheckboxSetField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
294
295
        // Remove all "privileged" permissions if the currently logged-in user is not an admin
296
        $privilegedPermissions = Permission::config()->privileged_permissions;
0 ignored issues
show
Documentation introduced by
The property privileged_permissions does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
297
        if (!Permission::check('ADMIN')) {
298
            foreach ($this->value as $id => $bool) {
299
                if (in_array($id, $privilegedPermissions)) {
300
                    unset($this->value[$id]);
301
                }
302
            }
303
        }
304
305
        // remove all permissions and re-add them afterwards
306
        $permissions = $record->$fieldname();
307
        foreach ($permissions as $permission) {
308
            $permission->delete();
309
        }
310
311
        $schema = DataObject::getSchema();
312
        if ($fieldname && $record && (
313
            $schema->hasManyComponent(get_class($record), $fieldname)
0 ignored issues
show
Bug Best Practice introduced by
The expression $schema->hasManyComponen...s($record), $fieldname) 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...
314
            || $schema->manyManyComponent(get_class($record), $fieldname)
315
        )) {
316
            if (!$record->ID) {
0 ignored issues
show
Bug introduced by
Accessing ID on the interface SilverStripe\ORM\DataObjectInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
317
                $record->write(); // We need a record ID to write permissions
318
            }
319
320
            if ($this->value) {
321
                foreach ($this->value as $id => $bool) {
322
                    if ($bool) {
323
                        $perm = new $managedClass();
324
                        $perm->{$this->filterField} = $record->ID;
0 ignored issues
show
Bug introduced by
Accessing ID on the interface SilverStripe\ORM\DataObjectInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
325
                        $perm->Code = $id;
326
                        $perm->write();
327
                    }
328
                }
329
            }
330
        }
331
    }
332
333
    /**
334
     * @return PermissionCheckboxSetField_Readonly
335
     */
336
    public function performReadonlyTransformation()
337
    {
338
        $readonly = new PermissionCheckboxSetField_Readonly(
339
            $this->name,
340
            $this->title,
341
            $this->managedClass,
342
            $this->filterField,
343
            $this->records
344
        );
345
346
        return $readonly;
347
    }
348
}
349