GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — integration (#2604)
by Brendan
04:24
created

Administration   C

Complexity

Total Complexity 69

Size/Duplication

Total Lines 526
Duplicated Lines 1.14 %

Coupling/Cohesion

Components 3
Dependencies 10

Importance

Changes 8
Bugs 1 Features 0
Metric Value
c 8
b 1
f 0
dl 6
loc 526
rs 5.6445
wmc 69
lcom 3
cbo 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A instance() 0 8 2
A getCurrentPageURL() 0 4 1
A display() 0 56 3
F __buildPage() 0 104 22
A isLoggedIn() 0 10 4
A errorPageNotFound() 0 8 1
A __canAccessAlerts() 0 8 4
B checkCoreForUpdates() 0 28 4
B checkExtensionsForUpdates() 0 17 6
D getPageCallback() 6 131 20

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Administration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Administration, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
    /**
4
     * @package core
5
     */
6
7
    /**
8
     * The Administration class is an instance of Symphony that controls
9
     * all backend pages. These pages are HTMLPages are usually generated
10
     * using XMLElement before being rendered as HTML. These pages do not
11
     * use XSLT. The Administration is only accessible by logged in Authors
12
     */
13
    class Administration extends Symphony
14
    {
15
        /**
16
         * The class representation of the current Symphony backend page,
17
         * which is a subclass of the `HTMLPage` class. Symphony uses a convention
18
         * of prefixing backend page classes with 'content'. ie. 'contentBlueprintsSections'
19
         *
20
         * @var HTMLPage
21
         */
22
        public $Page;
23
        /**
24
         * The path of the current page, ie. '/blueprints/sections/'
25
         *
26
         * @var string
27
         */
28
        private $_currentPage = null;
29
        /**
30
         * An associative array of the page's callback, including the keys
31
         * 'driver', which is a lowercase version of `$this->_currentPage`
32
         * with any slashes removed, 'classname', which is the name of the class
33
         * for this page, 'pageroot', which is the root page for the given page, (ie.
34
         * excluding /saved/, /created/ or any sub pages of the current page that are
35
         * handled using the _switchboard function.
36
         *
37
         * @see toolkit.AdministrationPage#__switchboard()
38
         * @var array
39
         */
40
        private $_callback = null;
41
42
        /**
43
         * Overrides the default Symphony constructor to add XSRF checking
44
         */
45
        protected function __construct()
46
        {
47
            parent::__construct();
48
49
            // Ensure the request is legitimate. RE: #1874
50
            if (self::isXSRFEnabled()) {
51
                XSRF::validateRequest();
52
            }
53
        }
54
55
        /**
56
         * This function returns an instance of the Administration
57
         * class. It is the only way to create a new Administration, as
58
         * it implements the Singleton interface
59
         *
60
         * @return Administration
61
         */
62
        public static function instance()
63
        {
64
            if (!(self::$_instance instanceof Administration)) {
65
                self::$_instance = new Administration;
66
            }
67
68
            return self::$_instance;
69
        }
70
71
        /**
72
         * Returns the current Page path, excluding the domain and Symphony path.
73
         *
74
         * @return string
75
         *  The path of the current page, ie. '/blueprints/sections/'
76
         */
77
        public function getCurrentPageURL()
78
        {
79
            return $this->_currentPage;
80
        }
81
82
        /**
83
         * Called by index.php, this function is responsible for rendering the current
84
         * page on the Frontend. Two delegates are fired, AdminPagePreGenerate and
85
         * AdminPagePostGenerate. This function runs the Profiler for the page build
86
         * process.
87
         *
88
         * @uses AdminPagePreBuild
89
         * @uses AdminPagePreGenerate
90
         * @uses AdminPagePostGenerate
91
         * @see  core.Symphony#__buildPage()
92
         * @see  boot.getCurrentPage()
93
         * @param string $page
94
         *  The result of getCurrentPage, which returns the $_GET['symphony-page']
95
         *  variable.
96
         * @throws Exception
97
         * @throws SymphonyErrorPage
98
         * @return string
99
         *  The HTML of the page to return
100
         */
101
        public function display($page)
102
        {
103
            Symphony::Profiler()->sample('Page build process started');
104
105
            /**
106
             * Immediately before building the admin page. Provided with the page parameter
107
             *
108
             * @delegate AdminPagePreBuild
109
             * @since Symphony 2.6.0
110
             * @param string $context
111
             *  '/backend/'
112
             * @param string $page
113
             *  The result of getCurrentPage, which returns the $_GET['symphony-page']
114
             *  variable.
115
             */
116
            Symphony::ExtensionManager()->notifyMembers('AdminPagePreBuild', '/backend/', array('page' => $page));
117
118
            $this->__buildPage($page);
119
120
            // Add XSRF token to form's in the backend
121
            if (self::isXSRFEnabled() && isset($this->Page->Form)) {
122
                $this->Page->Form->prependChild(XSRF::formToken());
123
            }
124
125
            /**
126
             * Immediately before generating the admin page. Provided with the page object
127
             *
128
             * @delegate AdminPagePreGenerate
129
             * @param string $context
130
             *  '/backend/'
131
             * @param HTMLPage $oPage
132
             *  An instance of the current page to be rendered, this will usually be a class that
133
             *  extends HTMLPage. The Symphony backend uses a convention of contentPageName
134
             *  as the class that extends the HTMLPage
135
             */
136
            Symphony::ExtensionManager()->notifyMembers('AdminPagePreGenerate', '/backend/',
137
                array('oPage' => &$this->Page));
138
139
            $output = $this->Page->generate();
140
141
            /**
142
             * Immediately after generating the admin page. Provided with string containing page source
143
             *
144
             * @delegate AdminPagePostGenerate
145
             * @param string $context
146
             *  '/backend/'
147
             * @param string $output
148
             *  The resulting backend page HTML as a string, passed by reference
149
             */
150
            Symphony::ExtensionManager()->notifyMembers('AdminPagePostGenerate', '/backend/',
151
                array('output' => &$output));
152
153
            Symphony::Profiler()->sample('Page built');
154
155
            return $output;
156
        }
157
158
        /**
159
         * Given the URL path of a Symphony backend page, this function will
160
         * attempt to resolve the URL to a Symphony content page in the backend
161
         * or a page provided by an extension. This function checks to ensure a user
162
         * is logged in, otherwise it will direct them to the login page
163
         *
164
         * @param string $page
165
         *  The URL path after the root of the Symphony installation, including a starting
166
         *  slash, such as '/login/'
167
         * @throws SymphonyErrorPage
168
         * @throws Exception
169
         * @return HTMLPage
170
         */
171
        private function __buildPage($page)
172
        {
173
            $is_logged_in = self::isLoggedIn();
174
175
            if (empty($page) || is_null($page)) {
176
                if (!$is_logged_in) {
177
                    $page = "/login";
178
                } else {
179
                    // Will redirect an Author to their default area of the Backend
180
                    // Integers are indicative of section's, text is treated as the path
181
                    // to the page after `SYMPHONY_URL`
182
                    $default_area = null;
183
184
                    if (is_numeric(Symphony::Author()->get('default_area'))) {
185
                        $default_section = SectionManager::fetch(Symphony::Author()->get('default_area'));
186
187
                        if ($default_section instanceof Section) {
188
                            $section_handle = $default_section->get('handle');
189
                        }
190
191
                        if (!$section_handle) {
0 ignored issues
show
Bug introduced by
The variable $section_handle does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
192
                            $all_sections = SectionManager::fetch();
193
194
                            if (!empty($all_sections)) {
195
                                $section_handle = $all_sections[0]->get('handle');
196
                            } else {
197
                                $section_handle = null;
198
                            }
199
                        }
200
201
                        if (!is_null($section_handle)) {
202
                            $default_area = "/publish/{$section_handle}/";
203
                        }
204
                    } elseif (!is_null(Symphony::Author()->get('default_area'))) {
205
                        $default_area = preg_replace('/^' . preg_quote(SYMPHONY_URL, '/') . '/i', '',
206
                            Symphony::Author()->get('default_area'));
207
                    }
208
209
                    if (is_null($default_area)) {
210
                        if (Symphony::Author()->isDeveloper()) {
211
                            $all_sections = SectionManager::fetch();
212
                            $section_handle = !empty($all_sections) ? $all_sections[0]->get('handle') : null;
213
214
                            if (!is_null($section_handle)) {
215
                                // If there are sections created, redirect to the first one (sortorder)
216
                                redirect(SYMPHONY_URL . "/publish/{$section_handle}/");
217
                            } else {
218
                                // If there are no sections created, default to the Section page
219
                                redirect(SYMPHONY_URL . '/blueprints/sections/');
220
                            }
221
                        } else {
222
                            redirect(SYMPHONY_URL . "/system/authors/edit/" . Symphony::Author()->get('id') . "/");
223
                        }
224
                    } else {
225
                        redirect(SYMPHONY_URL . $default_area);
226
                    }
227
                }
228
            }
229
230
            if (!$this->_callback = $this->getPageCallback($page)) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getPageCallback($page) can also be of type false. However, the property $_callback is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
231
                if ($page === '/publish/') {
232
                    $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
233
                    $section = current($sections);
234
                    redirect(SYMPHONY_URL . '/publish/' . $section->get('handle'));
235
                } else {
236
                    $this->errorPageNotFound();
237
                }
238
            }
239
240
            require_once($this->_callback['driver_location']);
241
            $this->Page = new $this->_callback['classname'];
242
243
            if (!$is_logged_in && $this->_callback['driver'] !== 'login') {
244
                if (is_callable(array($this->Page, 'handleFailedAuthorisation'))) {
245
                    $this->Page->handleFailedAuthorisation();
246
                } else {
247
                    include_once(CONTENT . '/content.login.php');
248
                    $this->Page = new contentLogin;
249
250
                    // Include the query string for the login, RE: #2324
251
                    if ($queryString = $this->Page->__buildQueryString(array('symphony-page', 'mode'),
252
                        FILTER_SANITIZE_STRING)
253
                    ) {
254
                        $page .= '?' . $queryString;
255
                    }
256
                    $this->Page->build(array('redirect' => $page));
257
                }
258
            } else {
259
                if (!is_array($this->_callback['context'])) {
260
                    $this->_callback['context'] = array();
261
                }
262
263
                if ($this->__canAccessAlerts()) {
264
                    // Can the core be updated?
265
                    $this->checkCoreForUpdates();
266
                    // Do any extensions need updating?
267
                    $this->checkExtensionsForUpdates();
268
                }
269
270
                $this->Page->build($this->_callback['context']);
271
            }
272
273
            return $this->Page;
274
        }
275
276
        /**
277
         * Overrides the Symphony isLoggedIn function to allow Authors
278
         * to become logged into the backend when `$_REQUEST['auth-token']`
279
         * is present. This logs an Author in using the loginFromToken function.
280
         * A token may be 6 or 8 characters in length in the backend. A 6 or 16 character token
281
         * is used for forget password requests, whereas the 8 character token is used to login
282
         * an Author into the page
283
         *
284
         * @see core.Symphony#loginFromToken()
285
         * @return boolean
286
         */
287
        public static function isLoggedIn()
288
        {
289
            if (isset($_REQUEST['auth-token']) && $_REQUEST['auth-token'] && in_array(strlen($_REQUEST['auth-token']),
290
                    array(6, 8, 16))
291
            ) {
292
                return self::loginFromToken($_REQUEST['auth-token']);
293
            }
294
295
            return parent::isLoggedIn();
296
        }
297
298
        /**
299
         * This function resolves the string of the page to the relevant
300
         * backend page class. The path to the backend page is split on
301
         * the slashes and the resulting pieces used to determine if the page
302
         * is provided by an extension, is a section (index or entry creation)
303
         * or finally a standard Symphony content page. If no page driver can
304
         * be found, this function will return false.
305
         *
306
         * @uses AdminPagePostCallback
307
         * @param string $page
308
         *  The full path (including the domain) of the Symphony backend page
309
         * @return array|boolean
310
         *  If successful, this function will return an associative array that at the
311
         *  very least will return the page's classname, pageroot, driver, driver_location
312
         *  and context, otherwise this will return false.
313
         */
314
        public function getPageCallback($page = null)
315
        {
316
            if (!$page && !is_null($this->_callback)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $page of type string|null is loosely compared to false; 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...
317
                return $this->_callback;
318
            } elseif (!$page && is_null($this->_callback)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $page of type string|null is loosely compared to false; 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...
319
                trigger_error(__('Cannot request a page callback without first specifying the page.'));
320
            }
321
322
            $this->_currentPage = SYMPHONY_URL . preg_replace('/\/{2,}/', '/', $page);
323
            $bits = preg_split('/\//', trim($page, '/'), 3, PREG_SPLIT_NO_EMPTY);
324
            $callback = array(
325
                'driver' => null,
326
                'driver_location' => null,
327
                'context' => array(),
328
                'classname' => null,
329
                'pageroot' => null
330
            );
331
332
            // Login page, /symphony/login/
333
            if ($bits[0] === 'login') {
334
                $callback['driver'] = 'login';
335
                $callback['driver_location'] = CONTENT . '/content.login.php';
336
                $callback['classname'] = 'contentLogin';
337
                $callback['pageroot'] = '/login/';
338
339
                // Extension page, /symphony/extension/{extension_name}/
340
            } elseif ($bits[0] === 'extension' && isset($bits[1])) {
341
                $extension_name = $bits[1];
342
                $bits = preg_split('/\//', trim($bits[2], '/'), 2, PREG_SPLIT_NO_EMPTY);
343
344
                // check if extension is enabled, if it's not, pretend the extension doesn't
345
                // even exist. #2367
346
                if (!ExtensionManager::isInstalled($extension_name)) {
347
                    return false;
348
                }
349
350
                $callback['driver'] = 'index';
351
                $callback['classname'] = 'contentExtension' . ucfirst($extension_name) . 'Index';
352
                $callback['pageroot'] = '/extension/' . $extension_name . '/';
353
                $callback['extension'] = $extension_name;
354
355
                if (isset($bits[0])) {
356
                    $callback['driver'] = $bits[0];
357
                    $callback['classname'] = 'contentExtension' . ucfirst($extension_name) . ucfirst($bits[0]);
358
                    $callback['pageroot'] .= $bits[0] . '/';
359
                }
360
361 View Code Duplication
                if (isset($bits[1])) {
362
                    $callback['context'] = preg_split('/\//', $bits[1], -1, PREG_SPLIT_NO_EMPTY);
363
                }
364
365
                $callback['driver_location'] = EXTENSIONS . '/' . $extension_name . '/content/content.' . $callback['driver'] . '.php';
366
                // Extensions won't be part of the autoloader chain, so first try to require them if they are available.
367
                if (!is_file($callback['driver_location'])) {
368
                    return false;
369
                } else {
370
                    require_once $callback['driver_location'];
371
                }
372
373
                // Publish page, /symphony/publish/{section_handle}/
374
            } elseif ($bits[0] === 'publish') {
375
                if (!isset($bits[1])) {
376
                    return false;
377
                }
378
379
                $callback['driver'] = 'publish';
380
                $callback['driver_location'] = CONTENT . '/content.publish.php';
381
                $callback['pageroot'] = '/' . $bits[0] . '/' . $bits[1] . '/';
382
                $callback['classname'] = 'contentPublish';
383
384
                // Everything else
385
            } else {
386
                $callback['driver'] = ucfirst($bits[0]);
387
                $callback['pageroot'] = '/' . $bits[0] . '/';
388
389
                if (isset($bits[1])) {
390
                    $callback['driver'] = $callback['driver'] . ucfirst($bits[1]);
391
                    $callback['pageroot'] .= $bits[1] . '/';
392
                }
393
394
                $callback['classname'] = 'content' . $callback['driver'];
395
                $callback['driver'] = strtolower($callback['driver']);
396
                $callback['driver_location'] = CONTENT . '/content.' . $callback['driver'] . '.php';
397
            }
398
399
            // Parse the context
400
            if (isset($callback['classname'])) {
401
                $page = new $callback['classname'];
402
403
                // Named context
404
                if (method_exists($page, 'parseContext')) {
405
                    $page->parseContext($callback['context'], $bits);
406
407
                    // Default context
408 View Code Duplication
                } elseif (isset($bits[2])) {
409
                    $callback['context'] = preg_split('/\//', $bits[2], -1, PREG_SPLIT_NO_EMPTY);
410
                }
411
            }
412
413
            /**
414
             * Immediately after determining which class will resolve the current page, this
415
             * delegate allows extension to modify the routing or provide additional information.
416
             *
417
             * @since Symphony 2.3.1
418
             * @delegate AdminPagePostCallback
419
             * @param string $context
420
             *  '/backend/'
421
             * @param string $page
422
             *  The current URL string, after the SYMPHONY_URL constant (which is `/symphony/`
423
             *  at the moment.
424
             * @param array $parts
425
             *  An array representation of `$page`
426
             * @param array $callback
427
             *  An associative array that contains `driver`, `pageroot`, `classname` and
428
             *  `context` keys. The `driver_location` is the path to the class to render this
429
             *  page, `driver` should be the view to render, the `classname` the name of the
430
             *  class, `pageroot` the rootpage, before any extra URL params and `context` can
431
             *  provide additional information about the page
432
             */
433
            Symphony::ExtensionManager()->notifyMembers('AdminPagePostCallback', '/backend/', array(
434
                'page' => $this->_currentPage,
435
                'parts' => $bits,
436
                'callback' => &$callback
437
            ));
438
439
            if (!isset($callback['driver_location']) || !is_file($callback['driver_location'])) {
440
                return false;
441
            }
442
443
            return $callback;
444
        }
445
446
        /**
447
         * If a page is not found in the Symphony backend, this function should
448
         * be called which will raise a customError to display the default Symphony
449
         * page not found template
450
         */
451
        public function errorPageNotFound()
452
        {
453
            $this->throwCustomError(
454
                __('The page you requested does not exist.'),
455
                __('Page Not Found'),
456
                Page::HTTP_STATUS_NOT_FOUND
457
            );
458
        }
459
460
        /**
461
         * This function determines whether an administrative alert can be
462
         * displayed on the current page. It ensures that the page exists,
463
         * and the user is logged in and a developer
464
         *
465
         * @since Symphony 2.2
466
         * @return boolean
467
         */
468
        private function __canAccessAlerts()
469
        {
470
            if ($this->Page instanceof AdministrationPage && self::isLoggedIn() && Symphony::Author()->isDeveloper()) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $this->Page insta...uthor()->isDeveloper();.
Loading history...
471
                return true;
472
            }
473
474
            return false;
475
        }
476
477
        /**
478
         * Scan the install directory to look for new migrations that can be applied
479
         * to update this version of Symphony. If one if found, a new Alert is added
480
         * to the page.
481
         *
482
         * @since Symphony 2.5.2
483
         * @return boolean
484
         *  Returns true if there is an update available, false otherwise.
485
         */
486
        public function checkCoreForUpdates()
487
        {
488
            // Is there even an install directory to check?
489
            if ($this->isInstallerAvailable() === false) {
490
                return false;
491
            }
492
493
            try {
494
                // The updater contains a version higher than the current Symphony version.
495
                if ($this->isUpgradeAvailable()) {
496
                    $message = __('An update has been found in your installation to upgrade Symphony to %s.',
497
                            array($this->getMigrationVersion())) . ' <a href="' . URL . '/install/">' . __('View update.') . '</a>';
498
499
                    // The updater contains a version lower than the current Symphony version.
500
                    // The updater is the same version as the current Symphony install.
501
                } else {
502
                    $message = __('Your Symphony installation is up to date, but the installer was still detected. For security reasons, it should be removed.') . ' <a href="' . URL . '/install/?action=remove">' . __('Remove installer?') . '</a>';
503
                }
504
505
                // Can't detect update Symphony version
506
            } catch (Exception $e) {
507
                $message = __('An update script has been found in your installation.') . ' <a href="' . URL . '/install/">' . __('View update.') . '</a>';
508
            }
509
510
            $this->Page->pageAlert($message, Alert::NOTICE);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class HTMLPage as the method pageAlert() does only exist in the following sub-classes of HTMLPage: AdministrationPage, ResourcesPage, contentBlueprintsDatasources, contentBlueprintsEvents, contentBlueprintsPages, contentBlueprintsSections, contentPublish, contentSystemAuthors, contentSystemExtensions, contentSystemPreferences. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
511
512
            return true;
513
        }
514
515
        /**
516
         * Checks all installed extensions to see any have an outstanding update. If any do
517
         * an Alert will be added to the current page directing the Author to the Extension page
518
         *
519
         * @since Symphony 2.5.2
520
         */
521
        public function checkExtensionsForUpdates()
522
        {
523
            $extensions = Symphony::ExtensionManager()->listInstalledHandles();
524
525
            if (is_array($extensions) && !empty($extensions)) {
526
                foreach ($extensions as $name) {
527
                    $about = Symphony::ExtensionManager()->about($name);
528
529
                    if (array_key_exists('status', $about) && in_array(EXTENSION_REQUIRES_UPDATE, $about['status'])) {
530
                        $this->Page->pageAlert(
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class HTMLPage as the method pageAlert() does only exist in the following sub-classes of HTMLPage: AdministrationPage, ResourcesPage, contentBlueprintsDatasources, contentBlueprintsEvents, contentBlueprintsPages, contentBlueprintsSections, contentPublish, contentSystemAuthors, contentSystemExtensions, contentSystemPreferences. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
531
                            __('An extension requires updating.') . ' <a href="' . SYMPHONY_URL . '/system/extensions/">' . __('View extensions') . '</a>'
532
                        );
533
                        break;
534
                    }
535
                }
536
            }
537
        }
538
    }
539