Skip to content

DI not accepting null value of Nette config parameter if parameter is Object #283

@rootpd

Description

@rootpd

(the original description of the issue is outdated and there's no issue with Structure, please read the discussion below)

Version: 3.0.13

Bug Description

Configuration of Nette extension can be configured by getConfigSchema() which returns Nette\Schema\Elements\Structure. The actual config is then returned as stdClass.

Consider using this config in the extension:

return Expect::structure([
    'redis_client_factory' => Expect::structure([
        'prefix' => Expect::string()->dynamic()->nullable(),
        'replication' => Expect::structure([
            'service' => Expect::string()->dynamic(),
            'sentinels' => Expect::arrayOf(Expect::string())->dynamic()
        ])
    ])
]);

If I don't provide any configuration value to the redis_client_factory.prefix, the default (null) is used. All is fine to this point.

The issue is that the DI container reports, that the redis_client_factory.prefix is missing, even if it is nullable. The reason why this happen is this snippet:

di/src/DI/Helpers.php

Lines 72 to 78 in 5f0b849

if (is_array($val) && array_key_exists($key, $val)) {
$val = $val[$key];
} elseif ($val instanceof DynamicParameter) {
$val = new DynamicParameter($val . '[' . var_export($key, true) . ']');
} else {
throw new Nette\InvalidArgumentException(sprintf("Missing parameter '%s'.", $part));
}

In the past, DI could work with the assumption that the config is always array, but it's not true anymore. Now our code throws an exception from the else branch.

  • The first condition evaluates to false, because our config ($val) is not an array, but stdClass
  • Theoretical attempt to use array_key_exists() with stdClass would work for now, but it is deprecated - you'd get "array_key_exists(): Using array_key_exists() on objects is deprecated. Use isset() or property_exists() instead".

Steps To Reproduce

The fastest would be to try to use such extension in your Nette application. Use the snippet from above in your testing extension.

FooExtension
<?php

namespace Crm\FooModule\DI;

use Nette;
use Nette\DI\CompilerExtension;
use Nette\Schema\Expect;

final class FooModuleExtension extends CompilerExtension 
{
    public function getConfigSchema(): Nette\Schema\Schema
    {
        return Expect::structure([
            'redis_client_factory' => Expect::structure([
                'prefix' => Expect::string()->dynamic()->nullable(),
                'replication' => Expect::structure([
                    'service' => Expect::string()->dynamic(),
                    'sentinels' => Expect::arrayOf(Expect::string())->dynamic()
                ])
            ])
        ]);
    }
}

Now enable the extension in your Nette application:

extensions:
	foo: Crm\FooModule\DI\FooModuleExtension

Expected Behavior

Since the property was defined as nullable(), DI should consider "no value" defaulting to null as a valid extension configuration.

Possible Solution

If I understand the code correctly, another elseif branch for stdClass using property_exists() should be sufficient, but I can't tell for sure.

Thanks for checking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions