ExecutionFilter::readCache()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 4
nop 1
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace Agavi\Filter;
3
4
// +---------------------------------------------------------------------------+
5
// | This file is part of the Agavi package.                                   |
6
// | Copyright (c) 2005-2011 the Agavi Project.                                |
7
// | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr.    |
8
// |                                                                           |
9
// | For the full copyright and license information, please view the LICENSE   |
10
// | file that was distributed with this source code. You can also view the    |
11
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
12
// |   vi: set noexpandtab:                                                    |
13
// |   Local Variables:                                                        |
14
// |   indent-tabs-mode: t                                                     |
15
// |   End:                                                                    |
16
// +---------------------------------------------------------------------------+
17
use Agavi\Config\Config;
18
use Agavi\Config\ConfigCache;
19
use Agavi\Dispatcher\ExecutionContainer;
20
use Agavi\Exception\AgaviException;
21
use Agavi\Exception\InitializationException;
22
use Agavi\Exception\UncacheableException;
23
use Agavi\Exception\ViewException;
24
use Agavi\Request\RequestDataHolder;
25
use Agavi\Response\Response;
26
use Agavi\User\SecurityUser;
27
use Agavi\Util\ArrayPathDefinition;
28
use Agavi\Util\Toolkit;
29
use Agavi\View\View;
30
31
/**
32
 * The ExecutionFilter is the last filter registered for each filter chain.
33
 * This filter does all controller and view execution.
34
 *
35
 * @package    agavi
36
 * @subpackage filter
37
 *
38
 * @author     David Zülke <[email protected]>
39
 * @author     Sean Kerr <[email protected]>
40
 * @copyright  Authors
41
 * @copyright  The Agavi Project
42
 *
43
 * @since      0.9.0
44
 *
45
 * @version    $Id$
46
 */
47
class ExecutionFilter extends Filter implements ControllerFilterInterface
48
{
49
    /*
50
	 * The directory inside %core.cache_dir% where cached stuff is stored.
51
	 */
52
    const CACHE_SUBDIR = 'content';
53
54
    /*
55
	 * The name of the file that holds the cached controller data.
56
	 * Minuses because these are not allowed in an output type name.
57
	 */
58
    const CONTROLLER_CACHE_ID = '4-8-15-16-23-42';
59
60
    /*
61
	 * Constants for the cache callback event types.
62
	 */
63
    const CACHE_CALLBACK_CONTROLLER_NOT_CACHED = 0;
64
    const CACHE_CALLBACK_CONTROLLER_CACHE_GONE = 1;
65
    const CACHE_CALLBACK_VIEW_NOT_CACHEABLE = 2;
66
    const CACHE_CALLBACK_VIEW_NOT_CACHED = 3;
67
    const CACHE_CALLBACK_OUTPUT_TYPE_NOT_CACHEABLE = 4;
68
    const CACHE_CALLBACK_VIEW_CACHE_GONE = 5;
69
    const CACHE_CALLBACK_CONTROLLER_CACHE_USELESS = 6;
70
    const CACHE_CALLBACK_VIEW_CACHE_WRITTEN = 7;
71
    const CACHE_CALLBACK_CONTROLLER_CACHE_WRITTEN = 8;
72
    
73
    /**
74
     * Method that's called when a cacheable Controller/View with a stale cache is
75
     * about to be run.
76
     * Can be used to prevent stampede situations where many requests to an controller
77
     * with an out-of-date cache are run in parallel, slowing down everything.
78
     * For instance, you could set a flag into memcached with the groups of the
79
     * controller that's currently run, and in checkCache check for those and return
80
     * an old, stale cache until the flag is gone.
81
     *
82
     * @param      int                     $eventType The type of the event that occurred.
83
     *                                     See CACHE_CALLBACK_* constants.
84
     * @param      array                   $groups The groups.
85
     * @param      array                   $configThe caching configuration.
0 ignored issues
show
Documentation introduced by
There is no parameter named $configThe. Did you maybe mean $config?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
86
     * @param      ExecutionContainer      $container The current execution container.
87
     *
88
     * @author     David Zülke <[email protected]>
89
     * @since      1.0.0
90
     */
91
    public function startedCacheCreationCallback($eventType, array $groups, array $config, ExecutionContainer $container)
0 ignored issues
show
Unused Code introduced by
The parameter $eventType 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 $groups 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 $config 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 $container 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...
92
    {
93
    }
94
    
95
    /**
96
     * Method that's called when an Controller/View that was assumed to be cacheable
97
     * turned out not to be (because the View or Output Type isn't).
98
     *
99
     * @see        ExecutionFilter::startedCacheCreationCallback()
100
     *
101
     * @param      int                     $eventType The type of the event that occurred.
102
     *                                     See CACHE_CALLBACK_* constants.
103
     * @param      array                   $groups The groups.
104
     * @param      array                   $config The caching configuration.
105
     * @param      ExecutionContainer      $container The current execution container.
106
     *
107
     * @author     David Zülke <[email protected]>
108
     * @since      1.0.0
109
     */
110
    public function abortedCacheCreationCallback($eventType, array $groups, array $config, ExecutionContainer $container)
0 ignored issues
show
Unused Code introduced by
The parameter $eventType 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 $groups 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 $config 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 $container 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...
111
    {
112
    }
113
    
114
    /**
115
     * Method that's called when a cacheable Controller/View with a stale cache has
116
     * finished execution and all caches are written.
117
     *
118
     * @see        ExecutionFilter::startedCacheCreationCallback()
119
     *
120
     * @param      int                     $eventType The type of the event that occurred.
121
     *                                     See CACHE_CALLBACK_* constants.
122
     * @param      array                   $groups The groups.
123
     * @param      array                   $config The caching configuration.
124
     * @param      ExecutionContainer      $container The current execution container.
125
     *
126
     * @author     David Zülke <[email protected]>
127
     * @since      1.0.0
128
     */
129
    public function finishedCacheCreationCallback($eventType, array $groups, array $config, ExecutionContainer $container)
0 ignored issues
show
Unused Code introduced by
The parameter $eventType 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 $groups 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 $config 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 $container 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...
130
    {
131
    }
132
    
133
    /**
134
     * Check if a cache exists and is up-to-date
135
     *
136
     * @param      array  $groups An array of cache groups
137
     * @param      string $lifetime The lifetime of the cache as a strtotime relative string
138
     *                    without the leading plus sign.
139
     *
140
     * @return     bool true, if the cache is up to date, otherwise false
141
     *
142
     * @author     David Zülke <[email protected]>
143
     * @since      0.11.0
144
     */
145
    public function checkCache(array $groups, $lifetime = null)
146
    {
147
        foreach ($groups as &$group) {
148
            $group = base64_encode($group);
149
        }
150
        $filename = Config::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache';
151
        $isReadable = is_readable($filename);
152
        if ($lifetime === null || !$isReadable) {
153
            return $isReadable;
154
        } else {
155
            $expiry = strtotime('+' . $lifetime, filemtime($filename));
156
            if ($expiry !== false) {
157
                return $isReadable && ($expiry >= time());
158
            } else {
159
                return false;
160
            }
161
        }
162
    }
163
164
    /**
165
     * Read the contents of a cache
166
     *
167
     * @param      array $groups An array of cache groups
168
     *
169
     * @return     array The cache data
170
     *
171
     * @author     David Zülke <[email protected]>
172
     * @since      0.11.0
173
     */
174
    public function readCache(array $groups)
175
    {
176
        foreach ($groups as &$group) {
177
            $group = base64_encode($group);
178
        }
179
        $filename = Config::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache';
180
        $data = @file_get_contents($filename);
181
        if ($data !== false) {
182
            return unserialize($data);
183
        } else {
184
            throw new AgaviException(sprintf('Failed to read cache file "%s"', $filename));
185
        }
186
    }
187
188
    /**
189
     * Write cache content
190
     *
191
     * @param      array  $groups An array of cache groups
192
     * @param      array  $data The cache data
193
     * @param      string $lifetime The lifetime of the cache as a strtotime relative string
194
     *                    without the leading plus sign.
195
     *
196
     * @return     bool The result of the write operation
197
     *
198
     * @author     David Zülke <[email protected]>
199
     * @since      0.11.0
200
     */
201
    public function writeCache(array $groups, $data, $lifetime = null)
0 ignored issues
show
Unused Code introduced by
The parameter $lifetime 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...
202
    {
203
        // lifetime is not used in this implementation!
204
        
205
        foreach ($groups as &$group) {
206
            $group = base64_encode($group);
207
        }
208
        @mkdir(Config::get('core.cache_dir') . DIRECTORY_SEPARATOR  . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, array_slice($groups, 0, -1)), 0777, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
209
        return file_put_contents(Config::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache', serialize($data), LOCK_EX);
210
    }
211
212
    /**
213
     * Flushes the cache for a group
214
     *
215
     * @param      array $groups An array of cache groups
216
     *
217
     * @author     David Zülke <[email protected]>
218
     * @since      0.11.0
219
     */
220
    public static function clearCache(array $groups = array())
221
    {
222
        foreach ($groups as &$group) {
223
            $group = base64_encode($group);
224
        }
225
        $path = self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups);
226
        if (is_file(Config::get('core.cache_dir') . DIRECTORY_SEPARATOR . $path . '.cefcache')) {
227
            Toolkit::clearCache($path . '.cefcache');
228
        } else {
229
            Toolkit::clearCache($path);
230
        }
231
    }
232
233
    /**
234
     * Builds an array of cache groups using the configuration and a container.
235
     *
236
     * @param      array              $groups The group array from the configuration.
237
     * @param      ExecutionContainer $container The execution container.
238
     *
239
     * @return     array An array of groups.
240
     *
241
     * @author     David Zülke <[email protected]>
242
     * @since      0.11.0
243
     */
244
    public function determineGroups(array $groups, ExecutionContainer $container)
245
    {
246
        $retval = array();
247
248
        foreach ($groups as $group) {
249
            $group += array('name' => null, 'source' => null, 'namespace' => null);
250
            $val = $this->getVariable($group['name'], $group['source'], $group['namespace'], $container);
251
            
252
            if (is_object($val) && is_callable(array($val, '__toString'))) {
253
                $val = $val->__toString();
254
            } elseif (is_object($val)) {
255
                $val = spl_object_hash($val);
256
            }
257
            
258
            if ($val === null || $val === false || $val === '') {
259
                $val = '0';
260
            }
261
            
262
            if (!is_scalar($val)) {
263
                throw new UncacheableException('Group value is not a scalar, cannot construct a meaningful string representation.');
264
            }
265
            
266
            $retval[] = $val;
267
        }
268
269
        $retval[] = $container->getModuleName() . '_' . $container->getControllerName();
270
271
        return $retval;
272
    }
273
274
    /**
275
     * Read a variable from the given source and, optionally, namespace.
276
     *
277
     * @param      string $name The variable name.
278
     * @param      string $source The optional variable source.
279
     * @param      string $namespace The optional namespace in the source.
280
     * @param      ExecutionContainer $container The container to use, if necessary.
281
     *
282
     * @return     mixed The variable.
283
     *
284
     * @author     David Zülke <[email protected]>
285
     * @since      0.11.0
286
     */
287
    public function getVariable($name, $source = 'string', $namespace = null, ExecutionContainer $container = null)
288
    {
289
        $val = $name;
290
        
291
        switch ($source) {
292
            case 'callback':
293
                $val = $container->getControllerInstance()->$name();
0 ignored issues
show
Bug introduced by
It seems like $container is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
294
                break;
295
            case 'configuration_directive':
296
                $val = Config::get($name);
297
                break;
298
            case 'constant':
299
                $val = constant($name);
300
                break;
301
            case 'container_parameter':
302
                $val = $container->getParameter($name);
303
                break;
304
            case 'global_request_data':
305
                $val = $this->context->getRequest()->getRequestData()->get($namespace ? $namespace : RequestDataHolder::SOURCE_PARAMETERS, $name);
306
                break;
307
            case 'locale':
308
                $val = $this->context->getTranslationManager()->getCurrentLocaleIdentifier();
309
                break;
310
            case 'request_attribute':
311
                $val = $this->context->getRequest()->getAttribute($name, $namespace);
312
                break;
313
            case 'request_data':
314
                $val = $container->getRequestData()->get($namespace ? $namespace : RequestDataHolder::SOURCE_PARAMETERS, $name);
315
                break;
316
            case 'request_parameter':
317
                $val = $this->context->getRequest()->getRequestData()->getParameter($name);
318
                break;
319
            case 'user_attribute':
320
                $val = $this->context->getUser()->getAttribute($name, $namespace);
321
                break;
322
            case 'user_authenticated':
323
                if (($user = $this->context->getUser()) instanceof SecurityUser) {
324
                    $val = $user->isAuthenticated();
325
                }
326
                break;
327
            case 'user_credential':
328
                if (($user = $this->context->getUser()) instanceof SecurityUser) {
329
                    $val = $user->hasCredentials($name);
330
                }
331
                break;
332
            case 'user_parameter':
333
                $val = $this->context->getUser()->getParameter($name);
334
                break;
335
        }
336
        
337
        return $val;
338
    }
339
340
    /**
341
     * Execute this filter.
342
     *
343
     * @param      FilterChain        $filterChain The filter chain.
344
     * @param      ExecutionContainer $container The current execution container.
345
     *
346
     * @throws     InitializationException If an error occurs during
347
     *                                                 View initialization.
348
     * @throws     ViewException           If an error occurs while
349
     *                                                 executing the View.
350
     *
351
     * @author     David Zülke <[email protected]>
352
     * @author     Felix Gilcher <[email protected]>
353
     * @author     Sean Kerr <[email protected]>
354
     * @since      0.9.0
355
     */
356
    public function execute(FilterChain $filterChain, ExecutionContainer $container)
357
    {
358
        // $lm = $this->context->getLoggerManager();
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
359
360
        // get the context, Dispatcher and validator manager
361
        $dispatcher = $this->context->getDispatcher();
362
363
        // get the current controller information
364
        $controllerName = $container->getControllerName();
365
        $moduleName = $container->getModuleName();
366
        
367
        // the controller instance
368
        $controllerInstance = $container->getControllerInstance();
369
370
        $request = $this->context->getRequest();
371
372
        $isCacheable = false;
373
        $cachingDotXml = Toolkit::evaluateModuleDirective(
374
            $moduleName,
375
            'agavi.cache.path',
376
            array(
377
                'moduleName' => $moduleName,
378
                'controllerName' => $controllerName,
379
            )
380
        );
381
382
        $config = array();
383
384
        if ($this->getParameter('enable_caching', true) && is_readable($cachingDotXml)) {
385
            // $lm->log('Caching enabled, configuration file found, loading...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
386
            // no _once please!
387
            include(ConfigCache::checkConfig($cachingDotXml, $this->context->getName()));
388
        }
389
390
        $isControllerCached = false;
391
392
        if ($isCacheable) {
393
            try {
394
                $groups = $this->determineGroups($config['groups'], $container);
395
                $controllerGroups = array_merge($groups, array(self::CONTROLLER_CACHE_ID));
396
            } catch (UncacheableException $e) {
397
                // a group callback threw an exception. that means we're not allowed t cache
398
                $isCacheable = false;
399
            }
400
            if ($isCacheable) {
401
                // this is not wrapped in the try/catch block above as it might throw an exception itself
402
                $isControllerCached = $this->checkCache(array_merge($groups, array(self::CONTROLLER_CACHE_ID)), $config['lifetime']);
403
            
404
                if (!$isControllerCached) {
405
                    // cacheable, but controller is not cached. notify our callback so it can prevent the stampede that follows
406
                    $this->startedCacheCreationCallback(self::CACHE_CALLBACK_CONTROLLER_NOT_CACHED, $controllerGroups, $config, $container);
0 ignored issues
show
Bug introduced by
The variable $controllerGroups 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...
407
                }
408
            }
409
        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
410
            // $lm->log('Controller is not cacheable!');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
411
        }
412
413
        if ($isControllerCached) {
414
            // $lm->log('Controller is cached, loading...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
415
            // cache/dir/4-8-15-16-23-42 contains the controller cache
416
            try {
417
                $controllerCache = $this->readCache($controllerGroups);
418
                // and restore controller attributes
419
                $controllerInstance->setAttributes($controllerCache['controller_attributes']);
420
            } catch (\Exception $e) {
421
                // cacheable, but controller is not cached. notify our callback so it can prevent the stampede that follows
422
                $this->startedCacheCreationCallback(self::CACHE_CALLBACK_CONTROLLER_CACHE_GONE, $controllerGroups, $config, $container);
423
                $isControllerCached = false;
424
            }
425
        }
426
        
427
        $isViewCached = false;
428
        $rememberTheView = null;
429
        
430
        while (true) {
431
            if (!$isControllerCached) {
432
                $controllerCache = array();
433
            
434
                // $lm->log('Controller not cached, executing...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
435
                // execute the Controller and get the View to execute
436
                list($controllerCache['view_module'], $controllerCache['view_name']) = $container->runController();
437
                
438
                // check if we've just run the controller again after a previous cache read revealed that the view is not cached for this output type and we need to go back to square one due to the lack of controller attribute caching configuration...
439
                // if yes: is the view module/name that we got just now different from what was in the cache?
440
                if (isset($rememberTheView) && $controllerCache != $rememberTheView) {
441
                    // yup. clear it!
442
                    $ourClass = get_class($this);
443
                    $ourClass::clearCache($groups);
0 ignored issues
show
Bug introduced by
The variable $groups 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...
444
                }
445
                
446
                // check if the returned view is cacheable
447
                if ($isCacheable && is_array($config['views']) && !in_array(array('module' => $controllerCache['view_module'], 'name' => $controllerCache['view_name']), $config['views'], true)) {
448
                    $isCacheable = false;
449
                    $this->abortedCacheCreationCallback(self::CACHE_CALLBACK_VIEW_NOT_CACHEABLE, $controllerGroups, $config, $container);
450
                    
451
                    // so that view is not cacheable? okay then:
452
                    // check if we've just run the controller again after a previous cache read revealed that the view is not cached for this output type and we need to go back to square one due to the lack of controller attribute caching configuration...
453
                    // 'cause then we need to flush all those existing caches - obviously, that data is stale now, as we learned, since we are not allowed to cache anymore for the view that was returned now
454
                    if (isset($rememberTheView)) {
455
                        // yup. clear it!
456
                        $ourClass = get_class($this);
457
                        $ourClass::clearCache($groups);
458
                    }
459
                    // $lm->log('Returned View is not cleared for caching, setting cacheable status to false.');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
460
                } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
461
                    // $lm->log('Returned View is cleared for caching, proceeding...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
462
                }
463
464
                $controllerAttributes = $controllerInstance->getAttributes();
465
            }
466
467
            // clear the response
468
            $response = $container->getResponse();
469
            $response->clear();
470
471
            // clear any forward set, it's ze view's job
472
            $container->clearNext();
473
474
            if ($controllerCache['view_name'] !== View::NONE) {
475
                $container->setViewModuleName($controllerCache['view_module']);
0 ignored issues
show
Bug introduced by
The variable $controllerCache 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...
476
                $container->setViewName($controllerCache['view_name']);
477
478
                $key = $request->toggleLock();
479
                try {
480
                    $viewInstance = $container->getViewInstance();
481
                } catch (\Exception $e) {
482
                    // we caught an exception... unlock the request and rethrow!
483
                    $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 478 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
484
                    throw $e;
485
                }
486
                $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 478 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
487
488
                $outputType = $container->getOutputType()->getName();
489
490
                if ($isCacheable) {
491
                    if (isset($config['output_types'][$otConfig = $outputType]) || isset($config['output_types'][$otConfig = '*'])) {
492
                        $otConfig = $config['output_types'][$otConfig];
493
                        
494
                        $viewGroups = array_merge($groups, array($outputType));
495
496
                        if ($isControllerCached) {
497
                            $isViewCached = $this->checkCache($viewGroups, $config['lifetime']);
498
                            if (!$isViewCached) {
499
                                // cacheable, but view is not cached. notify our callback so it can prevent the stampede that follows
500
                                $this->startedCacheCreationCallback(self::CACHE_CALLBACK_VIEW_NOT_CACHED, $viewGroups, $config, $container);
501
                            }
502
                        }
503
                    } else {
504
                        $this->abortedCacheCreationCallback(self::CACHE_CALLBACK_OUTPUT_TYPE_NOT_CACHEABLE, $controllerGroups, $config, $container);
505
                        $isCacheable = false;
506
                    }
507
                }
508
509
                if ($isViewCached) {
510
                    // $lm->log('View is cached, loading...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
511
                    try {
512
                        $viewCache = $this->readCache($viewGroups);
0 ignored issues
show
Bug introduced by
The variable $viewGroups 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...
513
                    } catch (AgaviException $e) {
514
                        $this->startedCacheCreationCallback(self::CACHE_CALLBACK_VIEW_CACHE_GONE, $viewGroups, $config, $container);
515
                        $isViewCached = false;
516
                    }
517
                }
518
                if (!$isViewCached) {
519
                    // view not cached
520
                    // has the cache config a list of controller attributes?
521
                    if ($isControllerCached && !$config['controller_attributes']) {
522
                        // no. that means we must run the controller again!
523
                        $isControllerCached = false;
524
                        
525
                        if ($isCacheable) {
526
                            // notify our callback so it can remove the lock that's on the view
527
                            // but only if we're still marked as cacheable (if not, then that means the OT is not cacheable, so there wouldn't be a $viewGroups)
528
                            $this->abortedCacheCreationCallback(self::CACHE_CALLBACK_CONTROLLER_CACHE_USELESS, $viewGroups, $config, $container);
529
                        }
530
                        // notify our callback so it can prevent the stampede that follows
531
                        $this->startedCacheCreationCallback(self::CACHE_CALLBACK_CONTROLLER_CACHE_USELESS, $controllerGroups, $config, $container);
532
                        
533
                        // but remember the view info, just in case it differs if we run the controller again now
534
                        $rememberTheView = array(
535
                            'view_module' => $controllerCache['view_module'],
536
                            'view_name' => $controllerCache['view_name'],
537
                        );
538
                        continue;
539
                    }
540
                
541
                    $viewCache = array();
542
                    $viewCache['next'] = $this->executeView($container);
543
                }
544
545
                if ($viewCache['next'] instanceof ExecutionContainer) {
546
                    // $lm->log('Forwarding request, skipping rendering...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
547
                    $container->setNext($viewCache['next']);
0 ignored issues
show
Bug introduced by
The variable $viewCache 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...
548
                } else {
549
                    $output = array();
550
                    $nextOutput = null;
551
                
552
                    if ($isViewCached) {
553
                        $layers = $viewCache['layers'];
554
                        /** @var Response $response */
555
                        $response = $viewCache['response'];
556
                        $container->setResponse($response);
557
558
                        foreach ($viewCache['template_variables'] as $name => $value) {
559
                            $viewInstance->setAttribute($name, $value);
560
                        }
561
562 View Code Duplication
                        foreach ($viewCache['request_attributes'] as $requestAttribute) {
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...
563
                            $request->setAttribute($requestAttribute['name'], $requestAttribute['value'], $requestAttribute['namespace']);
564
                        }
565
                    
566
                        foreach ($viewCache['request_attribute_namespaces'] as $ranName => $ranValues) {
567
                            $request->setAttributes($ranValues, $ranName);
568
                        }
569
570
                        $nextOutput = $response->getContent();
571
                    } else {
572
                        if ($viewCache['next'] !== null) {
573
                            // response content was returned from view execute()
574
                            $response->setContent($nextOutput = $viewCache['next']);
575
                            $viewCache['next'] = null;
576
                        }
577
578
                        $layers = $viewInstance->getLayers();
579
580
                        if ($isCacheable) {
581
                            $viewCache['template_variables'] = array();
582
                            foreach ($otConfig['template_variables'] as $varName) {
0 ignored issues
show
Bug introduced by
The variable $otConfig 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...
583
                                $viewCache['template_variables'][$varName] = $viewInstance->getAttribute($varName);
584
                            }
585
586
                            $viewCache['response'] = clone $response;
587
588
                            $viewCache['layers'] = array();
589
590
                            $viewCache['slots'] = array();
591
592
                            $lastCacheableLayer = -1;
593
                            if (is_array($otConfig['layers'])) {
594
                                if (count($otConfig['layers'])) {
595
                                    for ($i = count($layers)-1; $i >= 0; $i--) {
596
                                        $layer = $layers[$i];
597
                                        $layerName = $layer->getName();
598
                                        if (isset($otConfig['layers'][$layerName])) {
599
                                            if (is_array($otConfig['layers'][$layerName])) {
600
                                                $lastCacheableLayer = $i - 1;
601
                                            } else {
602
                                                $lastCacheableLayer = $i;
603
                                            }
604
                                        }
605
                                    }
606
                                }
607
                            } else {
608
                                $lastCacheableLayer = count($layers) - 1;
609
                            }
610
611
                            for ($i = $lastCacheableLayer + 1; $i < count($layers); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
612
                                // $lm->log('Adding non-cacheable layer "' . $layers[$i]->getName() . '" to list');
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
613
                                $viewCache['layers'][] = clone $layers[$i];
614
                            }
615
                        }
616
                    }
617
618
                    $attributes =& $viewInstance->getAttributes();
619
620
                    // whether or not we should assign the previous' layer's output to the $slots array
621
                    $assignInnerToSlots = $this->getParameter('assign_inner_to_slots', false);
622
                    
623
                    // $lm->log('Starting rendering...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
624
                    for ($i = 0; $i < count($layers); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
625
                        $layer = $layers[$i];
626
                        $layerName = $layer->getName();
627
                        // $lm->log('Running layer "' . $layerName . '"...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
628
                        foreach ($layer->getSlots() as $slotName => $slotContainer) {
629
                            if ($isViewCached && isset($viewCache['slots'][$layerName][$slotName])) {
630
                                // $lm->log('Loading cached slot "' . $slotName . '"...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
631
                                $slotResponse = $viewCache['slots'][$layerName][$slotName];
632
                            } else {
633
                                // $lm->log('Running slot "' . $slotName . '"...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
634
                                $slotResponse = $slotContainer->execute();
635
                                if ($isCacheable && !$isViewCached && isset($otConfig['layers'][$layerName]) && is_array($otConfig['layers'][$layerName]) && in_array($slotName, $otConfig['layers'][$layerName])) {
636
                                    // $lm->log('Adding response of slot "' . $slotName . '" to cache...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
637
                                    $viewCache['slots'][$layerName][$slotName] = $slotResponse;
638
                                }
639
                            }
640
                            // set the presentation data as a template attribute
641
                            ArrayPathDefinition::setValue($slotName, $output, $slotResponse->getContent());
642
                            // and merge the other slot's response (this used to be conditional and done only when the content was not null)
643
                            // $lm->log('Merging in response from slot "' . $slotName . '"...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
644
                            $response->merge($slotResponse);
645
                        }
646
                        $moreAssigns = array(
647
                            'container' => $container,
648
                            'inner' => $nextOutput,
649
                            'request_data' => $container->getRequestData(),
650
                            'response' => $response,
651
                            'validation_manager' => $container->getValidationManager(),
652
                            'view' => $viewInstance,
653
                        );
654
                        // lock the request. can't be done outside the loop for the whole run, see #628
655
                        $key = $request->toggleLock();
656
                        try {
657
                            $nextOutput = $layer->getRenderer()->render($layer, $attributes, $output, $moreAssigns);
658
                        } catch (\Exception $e) {
659
                            // we caught an exception... unlock the request and rethrow!
660
                            $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 655 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
661
                            throw $e;
662
                        }
663
                        // and unlock the request again
664
                        $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 655 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
665
666
                        $response->setContent($nextOutput);
667
668
                        if ($isCacheable && !$isViewCached && $i === $lastCacheableLayer) {
0 ignored issues
show
Bug introduced by
The variable $lastCacheableLayer 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...
669
                            $viewCache['response'] = clone $response;
670
                        }
671
672
                        $output = array();
673
                        if ($assignInnerToSlots) {
674
                            $output[$layer->getName()] = $nextOutput;
675
                        }
676
                    }
677
                }
678
679
                if ($isCacheable && !$isViewCached) {
680
                    // we're writing the view cache first. this is just in case we get into a situation with really bad timing on the leap of a second
681
                    $viewCache['request_attributes'] = array();
682 View Code Duplication
                    foreach ($otConfig['request_attributes'] as $requestAttribute) {
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...
683
                        $viewCache['request_attributes'][] = $requestAttribute + array('value' => $request->getAttribute($requestAttribute['name'], $requestAttribute['namespace']));
684
                    }
685
                    $viewCache['request_attribute_namespaces'] = array();
686
                    foreach ($otConfig['request_attribute_namespaces'] as $requestAttributeNamespace) {
687
                        $viewCache['request_attribute_namespaces'][$requestAttributeNamespace] = $request->getAttributes($requestAttributeNamespace);
688
                    }
689
690
                    $this->writeCache($viewGroups, $viewCache, $config['lifetime']);
691
692
                    // notify callback that the execution has finished and caches have been written
693
                    $this->finishedCacheCreationCallback(self::CACHE_CALLBACK_VIEW_CACHE_WRITTEN, $viewGroups, $config, $container);
694
                    // $lm->log('Writing View cache...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
695
                }
696
            }
697
        
698
            // controller cache writing must occur here, so controllers that return View::NONE also get their cache written
699
            if ($isCacheable && !$isControllerCached) {
700
                $controllerCache['controller_attributes'] = array();
701
                foreach ($config['controller_attributes'] as $attributeName) {
702
                    $controllerCache['controller_attributes'][$attributeName] = $controllerAttributes[$attributeName];
0 ignored issues
show
Bug introduced by
The variable $controllerAttributes 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...
703
                }
704
705
                // $lm->log('Writing Controller cache...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
706
707
                $this->writeCache($controllerGroups, $controllerCache, $config['lifetime']);
708
            
709
                // notify callback that the execution has finished and caches have been written
710
                $this->finishedCacheCreationCallback(self::CACHE_CALLBACK_CONTROLLER_CACHE_WRITTEN, $controllerGroups, $config, $container);
711
            }
712
            
713
            // we're done here. bai.
714
            break;
715
        }
716
    }
717
718
    /**
719
     * Execute the Controller
720
     *
721
     * @param      ExecutionContainer $container The current execution container.
722
     *
723
     * @return     mixed The processed View information returned by the Controller.
724
     *
725
     * @author     David Zülke <[email protected]>
726
     * @author     Felix Gilcher <[email protected]>
727
     * @since      0.11.0
728
     *
729
     * @deprecated since 1.0.0, use ExecutionContainer::runController()
730
     */
731
    protected function runController(ExecutionContainer $container)
732
    {
733
        return $container->runController();
734
    }
735
    
736
    /**
737
     * execute this containers view instance
738
     *
739
     * @return     mixed the view's result
740
     *
741
     * @author     David Zülke <[email protected]>
742
     * @author     Felix Gilcher <[email protected]>
743
     * @since      1.0.0
744
     */
745
    protected function executeView(ExecutionContainer $container)
746
    {
747
        $outputType = $container->getOutputType()->getName();
748
        $request = $this->context->getRequest();
749
        $viewInstance = $container->getViewInstance();
750
        
751
        // $lm->log('View is not cached, executing...');
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
752
        // view initialization completed successfully
753
        $executeMethod = 'execute' . $outputType;
754
        if (!is_callable(array($viewInstance, $executeMethod))) {
755
            $executeMethod = 'execute';
756
        }
757
        $key = $request->toggleLock();
758
        try {
759
            $viewResult = $viewInstance->$executeMethod($container->getRequestData());
760
        } catch (\Exception $e) {
761
            // we caught an exception... unlock the request and rethrow!
762
            $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 757 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
763
            throw $e;
764
        }
765
        $request->toggleLock($key);
0 ignored issues
show
Bug introduced by
It seems like $key defined by $request->toggleLock() on line 757 can also be of type boolean; however, Agavi\Request\Request::toggleLock() does only seem to accept string|null, 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...
766
        return $viewResult;
767
    }
768
}
769