Replies: 3 comments
-
Tom, All we really want to know is if your hair is still as wild as when you rolled the Dice. ,Mykl
Meanwhile, for many years practically everything we run (and everything we write) happens via PSR autoloader.
|
Beta Was this translation helpful? Give feedback.
-
Hi @TRPB, I maintain a very large PHP codebase that we adopted Dice into and it has been great as we have been slowly migrating from global/static vars, and tightly coupled logic, to using dependency injection, but also using Dice as a factory to bridge the gap in the legacy codebase. I agree that the single class has grown a little unwieldy to maintain, with more complexity than is desirable in a single class/file/function. I think a new version of Dice can easily rely on an autoloader being used. I recently been considering trying to refactor Dice and would love to help author a v5, or even a spin-off from Dice, that takes some of its principals and modernizes it, adds PSR Container support, and makes it even easier to use with modern static type checking tools like https://psalm.dev/ I would imagine that this would require PHP 8 even 8.1 as a requirement I have some experience writing code that compiles down into a PHP file, and then calls it for better performance, this does have some benefits, however, sometimes parsing JSON is just as fast. That being said the array syntax is not that user-friendly in terms of IDE autocomplete support, and static type checkers can find problems in your config before you even finish editing. I see a different way of configuring the class though rather than registering individual handlers, I see it more as using those classes directly to configure Dice. Something like this: <?php
class Dice
{
/**
* @param class-string|Dice\Instance $name Class name for rule
*/
public function withRule(string|Dice\Instance $name, RuleInterface $rules)
{
}
}
interface RuleInterface
{
/**
* @param Dice $di Current instance of Dice
* @param Closure(Dice\Object $input):Dice\Object $next Call next rule
* @param Dice\Instance $rule Instance that we are targeting, could be named, or a class/interface
* @param Dice\Object $input Object that has been created by a previous step in the DI chain
* @returns Dice\Object Returned value
*/
public function apply(
Dice $di,
Closure $next,
Dice\Instance $rule,
Dice\Object $input,
Dice\Context $context,
): Dice\Object;
}
/** Example Share Implementation **/
class Share implements RuleInterface
{
public function __construct(
public ShareScope $scope,
public string|Dice\Instance|null $instance = null,
) {
}
public static function all(): Share
{
return new self(ShareScope::ALL);
}
/** @var Dice\Object[] $instances */
public $instances = [];
public function apply(
Dice $di,
Closure $next,
Dice\Instance $rule,
Dice\Object $input,
Dice\Context $context,
): Dice\Object {
$scope = match ($this->scope) {
ShareScope::ALL => "*",
ShareScope::INSTANCE => $context->instanceHash($this->rule),
};
return $this->instances[$scope] ??= $next($input);
}
}
/** Example Dice Configuration **/
$db2 = Dice\Instance::named('$database2');
$di = (new Dice())
->withRule(
ServerRequestInterface::class,
/* We could skip needing the configuration for Dice\Call and just get the type using reflection on closure arguments and inject them */
Dice\Instance::create(ServerRequestFactory::class),
Dice\Call::chain(
fn(
ServerRequestFactory $factory,
): ServerRequestInterface => $factory->fromGlobals(),
),
Dice\Share::all(),
)
->withRule(
\Psr\Http\Client\ClientInterface::class,
Dice\Instance::create(\GuzzleHttp\Client::class),
Dice\Share::all(),
)
->withRule(
ModelInterface::class,
Dice\Share::instanceOf(ControllerInterface::class),
)
->withRule(
$db2,
Dice\Call::chain(
fn(DatabaseConfig $cfg): PDO => new PDO("sqlite:{$cfg->path}"),
),
)
->withRule(A::class, Dice\Substitute::for(PDO::class, $db2))
->withRule(
Dice\Instance::all(),
Dice\Substitute::for(PDO::class, CustomPDO::class),
); Example JSON configuration file (or we could do some mapping between the current config syntax and the new version) {
"PsrRequestServerRequestInterface": [
{ "DiceInstance::create": ["NyholmPSR15ServerRequestFactory"] },
{ "DiceCall::chain": ["fromGlobals"] },
{ "DiceShare::all": [] }
],
"PsrHttpClientClientInterface": [
{ "DiceInstance::create": ["GuzzleHttpClient"] },
{ "DiceShare:all": [] }
],
"$database2": [{ "...": [] }],
"*": [{ "DiceSubstitute::for": ["PDO", "ProjectCustomPDO"] }]
}
So I see the pros and cons as follows:
[1] This could also include flattening some of the expensive middleware into inline PHP code. Sorry about the wall of a post, however, I do see a future for a more composable, flexible DI, based on the Dice principles. |
Beta Was this translation helpful? Give feedback.
-
I’d also be happy to help with this as I can. Somehow it seems the existence of PSR-14 evaded my awareness until just recently. I am currently undertaking a rewrite of my personal event library from the ground up to follow that spec, and I have found that it was unafraid to go into more depth than the spec for DI containers. So far I am finding it to be valuable, and other PSR-14 implementations to be educational. I mention all that to say that I would build a new Dice using one or two concepts from there, such as configuring the main container with ConfigProvider objects or something similar, rather than directly as in the past. SRP would have us change a lot here. |
Beta Was this translation helpful? Give feedback.
-
When I started Dice we were on PHP 5.3 (I think) and file I/O and parsing was the biggest single bottleneck. As such, Dice was a single class for optimal performance, at the expense of some reduced maintainability.
However, the PHP language was a lot simpler as well, there were fewer ways to represent a method header, fewer type hints, etc. Back when I started the project the whole thing was about ~100 lines. That has grown out of necessity to support new language features and type hints, as a result, the project is now far more complex than a single class should be.
The project is showing its age and needs an update. There are 3 main goals I have in mind:
1. Code refactor
My plan is to break the features up into classes and configure Dice like so:
We'd then provide a method for constructing Dice with the default configuration (or.. and I'm just throwing this out there.. could we use Dice to configure a Dice instance with the default configuration?!)
Exactly how this would work will need some investigation and proper design, but ultimately it would make the project easier to work on for newcomers, more easily extensible (we could offer a minimal configurations which don't offer all the features but run faster and have less memory overhead, for example)
There are a couple of downsides to this. I know a few people are using the project because it is just one class to require and doing so would require a PSR compliant autoloader or composer. I don't think that's such an issue in 2022 as it was back in 2008 but we could still offer a single file with its own autoloader if that was desirable.
2. Compliation
The fastest containers all compile the rules into PHP for the best performance. While the idea of code generating code always makes me cringe, it's probably a worthwhile tradeoff here for performance. Ultimately I had hoped that we'd start to see proper adoption of application servers (e.g. https://github.com/Level-2/Aphplication ) by now, that's not the case and compilation is probably the best option.
3. Dice is just an API
Really, all of this is somewhat irrelevant. As long as the rules.json format is properly documented, anyone can make a container which follows it. Despite the age of the project, and rather biased as the project author, I do genuinely think it's better than the bigger alternatives. Symfony DI started supporting stuff Dice has been doing for over 10 years. They even renamed 'scope: prototype' to 'shared: true'. No idea if Dice had any influence, but I feel it was ahead of the curve for quite some time in many ways.
I've started this as a discussion, any thoughts and ideas from any users or anyone who happens to see this are welcome.
Beta Was this translation helpful? Give feedback.
All reactions