RWOverdijk / AssetManager

AssetManager written for zf2. Managing assets for zend framework 2
BSD 2-Clause "Simplified" License
211 stars 83 forks source link

PHP Fatal error: Uncaught TypeError: Argument 2 passed to AssetManager\Service\AssetCacheManager::setCache() must implement interface Assetic\Asset\AssetInterface, null given #194

Closed dranzd closed 7 years ago

dranzd commented 8 years ago

I am using rwoverdijk/assetmanager version 1.4.4 in my application and utilizing the AliasPathStackResolver.

I'm having an issue with alias configuration wherein two aliases that points to the same directory throws a fatal error. Below is some information of what I did and am not sure if I was using the feature correctly or if there's a bug.

Please let me know if you need more information.

I have this configuration


'asset_manager' => [
    ...

    'resolver_configs' => [
        'aliases' => [
            'css/'           => dirname(dirname(__DIR__)) . '/public/css/',
            'fonts/'         => dirname(dirname(__DIR__)) . '/public/fonts/',
            'images/'        => dirname(dirname(__DIR__)) . '/public/images/',  // note: same path as "public-images/" alias
            'js/'            => dirname(dirname(__DIR__)) . '/public/js/',
            'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',  // note: same path as "images/" alias
        ],
    ],
    'caching' => [
        'default' => [
            'cache' => 'AssetManager\\Cache\\FilePathCache',
            'options' => [
                'dir' => 'data/cache/assets',
            ],
        ],
    ],

    ...

],

and I have an html like

<a href="">
    <img class="img-responsive" src="<?= $this->basePath('public-images/zf2-logo.png')?>" />
</a>

and when I run this command

sudo -u www-data php index.php assetmanager warmup --purge --verbose

I get this error

PHP Fatal error:  Uncaught TypeError: Argument 2 passed to AssetManager\Service\AssetCacheManager::setCache() must implement interface Assetic\Asset\AssetInterface, null given, called in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Controller/ConsoleController.php on line 85 and defined in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Service/AssetCacheManager.php:49
Stack trace:
#0 /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Controller/ConsoleController.php(85): AssetManager\Service\AssetCacheManager->setCache('public-images/favicon.ico', NULL)
#1 /to/app/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractActionController.php(83): AssetManager\Controller\ConsoleController->warmupAction()
#2 [internal function]: Zend\Mvc\Controller\AbstractActionController->onDispatch(Object(Zend\Mvc\MvcEvent))
#3 /to/app/vendor/zendframework/zen in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Service/AssetCacheManager.php on line 49

If I remove the alias "public-images" in the configuration, it works alright.

I don't know if I understand how the AliasPathStackResolver works but if I replace the part


class AssetManager\Resolver\AliasPathStackResolver

    ...

    public function resolve($name)
    {
        if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
            return null;
        }

        foreach ($this->aliases as $alias => $path) {
            if (strpos($name, $alias) === false) {                                      // replace this
                continue;
            }

            $name = substr_replace($name, '', 0, strlen($alias));

            $file = new SplFileInfo($path . $name);

            if ($file->isReadable() && !$file->isDir()) {
                $filePath = $file->getRealPath();
                $mimeType = $this->getMimeResolver()->getMimeType($filePath);
                $asset    = new FileAsset($filePath);

                $asset->mimetype = $mimeType;

                return $asset;
            }
        }

        return null;
    }

with

class AssetManager\Resolver\AliasPathStackResolver

    ...

    public function resolve($name)
    {
        if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
            return null;
        }

        foreach ($this->aliases as $alias => $path) {
            if (strpos($name, $alias) === false || 0 !== strpos($name, $alias)) {       // with this
                continue;
            }

            $name = substr_replace($name, '', 0, strlen($alias));

            $file = new SplFileInfo($path . $name);

            if ($file->isReadable() && !$file->isDir()) {
                $filePath = $file->getRealPath();
                $mimeType = $this->getMimeResolver()->getMimeType($filePath);
                $asset    = new FileAsset($filePath);

                $asset->mimetype = $mimeType;

                return $asset;
            }
        }

        return null;
    }

then having the "public-images/" alias works.

RWOverdijk commented 8 years ago

Interesting.

I think this happens because it's a key / value pair from alias. So your second alias overwrites the first one. Can you confirm this by changing the name of the alias? Or trying to fetch a dependency from the first defined alias?

dranzd commented 8 years ago

I tried to change the aliases as you suggested like below:

(1) First try From

    'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

to

    'my-images/'     => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

then the command works without the fatal error.

(2) Second try from

    'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

to

    'images/'           => dirname(dirname(__DIR__)) . '/public/images/',
    'my-public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

then the command throws a fatal error.

(3) Third try from

    'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

to

    'images/'               => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images-sample/' => dirname(dirname(__DIR__)) . '/public/images/',

then the command works without the fatal error.

(4) Third try from

    'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

to

    'images-sample/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images-sample/' => dirname(dirname(__DIR__)) . '/public/images/',

then the command throws a fatal error.

dranzd commented 8 years ago

I tried to debug it with steps provided below.

(1) Removed all asset_manager configurations and retain only the two aliases that are in question

    'asset_manager' => [
        'resolver_configs' => [
            'aliases' => [
                'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
                'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',
            ],
        ],
        'caching' => [
            'default' => [
                'cache' => 'AssetManager\\Cache\\FilePathCache',
                'options' => [
                    'dir' => 'data/cache/assets',
                ],
            ],
        ],
    ],

(2) Removed all files under the directory

    dirname(dirname(__DIR__)) . '/public/images/'

except for one file

favicon.ico

(3) Ran the command

    sudo -u www-data php index.php assetmanager warmup --purge --verbose

that generates the debugging output below

(3.1) https://github.com/RWOverdijk/AssetManager/blob/master/src/AssetManager/Controller/ConsoleController.php#L80

    $collection = $this->assetManager->getResolver()->collect();print_r($collection);
    Collecting all assets...
    Array
    (
        [0] => images/favicon.ico
        [1] => public-images/favicon.ico
    )
    Collected 2 assets, warming up...

(3.2) https://github.com/RWOverdijk/AssetManager/blob/master/src/AssetManager/Resolver/AliasPathStackResolver.php#L134

    public function resolve($name)
    {
        if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
            return null;
        }
print_r(PHP_EOL . PHP_EOL . PHP_EOL . "RESOLVING {$name}");
print_r($this->aliases);
        foreach ($this->aliases as $alias => $path) {
print_r(PHP_EOL . PHP_EOL . "RESOLVE: {$name}, ALIAS: {$alias}, STRPOS: " . strpos($name, $alias));

            if (strpos($name, $alias) === false) {
                continue;
            }

            $name = substr_replace($name, '', 0, strlen($alias));

            $file = new SplFileInfo($path . $name);

print_r(PHP_EOL . PHP_EOL . "FILENAME: {$name}, FILE: {$file}");
            if ($file->isReadable() && !$file->isDir()) {
                $filePath = $file->getRealPath();
                $mimeType = $this->getMimeResolver()->getMimeType($filePath);
                $asset    = new FileAsset($filePath);

                $asset->mimetype = $mimeType;

print_r(PHP_EOL . PHP_EOL . "ASSET FOUND");
                return $asset;
            }
        }

print_r(PHP_EOL . PHP_EOL . "ASSET NOT FOUND");
        return null;
    }
    RESOLVING images/favicon.ico
    Array
    (
        [images/] => /to/app/public/images/
        [public-images/] => /to/app/public/images/
    )

    RESOLVE: images/favicon.ico, ALIAS: images/, STRPOS: 0

    FILENAME: favicon.ico, FILE: /to/app/public/images/favicon.ico

    ASSET FOUND
    RESOLVING public-images/favicon.ico
    Array
    (
        [images/] => /to/app/public/images/
        [public-images/] => /to/app/public/images/
    )

    RESOLVE: public-images/favicon.ico, ALIAS: images/, STRPOS: 7                   <-- // Should this be processed?  Is it correct that
                                                                                        // the name to resolve "public/images" matches
    FILENAME: images/favicon.ico, FILE: /to/app/public/images/images/favicon.ico        // the alias "images/"? Or it should match only the
                                                                                        // alias "public/images"?
    RESOLVE: images/favicon.ico, ALIAS: public-images/, STRPOS: 

    ASSET NOT FOUND
PHP Fatal error:  Uncaught TypeError: Argument 2 passed to AssetManager\Service\AssetCacheManager::setCache() must implement interface Assetic\Asset\AssetInterface, null given, called in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Controller/ConsoleController.php on line 85 and defined in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Service/AssetCacheManager.php:49
Stack trace:
#0 /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Controller/ConsoleController.php(85): AssetManager\Service\AssetCacheManager->setCache('public-images/f...', NULL)
#1 /to/app/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractActionController.php(83): AssetManager\Controller\ConsoleController->warmupAction()
#2 [internal function]: Zend\Mvc\Controller\AbstractActionController->onDispatch(Object(Zend\Mvc\MvcEvent))
#3 /to/app/vendor/zendframework/zen in /to/app/vendor/rwoverdijk/assetmanager/src/AssetManager/Service/AssetCacheManager.php on line 49
RWOverdijk commented 8 years ago

I think it has to do with the way we replace aliases. They're not literal and try to replace parts of a path I think. I'll have to dig into this further.

Currently I'm occupied with other OS projects, so I don't have a huge amount of time to share. If you manage to figure out what's causing this before I do, that'd be of great help!

RWOverdijk commented 8 years ago

@dranzd Did you come up with a solution for this? Is this still relevant?

dranzd commented 8 years ago

@RWOverdijk I've only provided a workaround solution just so my app would work. I've renamed my alias "images/" to "my-images/" so that it will not conflict with the other alias "public-images/".

I believe this is still relevant. Having aliases configured like below should work without an error.

    'images/'        => dirname(dirname(__DIR__)) . '/public/images/',
    'public-images/' => dirname(dirname(__DIR__)) . '/public/images/',

I've provided details about the issue on previous comments and the suggested solution. Please close this issue if you think this is not relevant. I can make it work using different alias name to avoid the conflict.

dranzd commented 7 years ago

@RWOverdijk @wshafer Thanks for fixing that!

wshafer commented 7 years ago

@dranzd - You're welcome. Please note that the V2 in wshafer/assetmanager-core is still being worked on. I should be releasing a beta soon, but you can always upgrade to wshafer/assetmanager: dev-master right now. If you do please let me know if you have any issues.