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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions