-
-
Notifications
You must be signed in to change notification settings - Fork 143
feat(core): introduce tagged configurations #1105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pull Request Test Coverage Report for Build 14205916051Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
|
I really like the idea, but would like to brainstorm some more. Here are my thoughts:
// sqlite.config.php
return tag(new SqliteConfig(), 'sqlite');Since config objects are registered in the container via the
|
|
I agree with the variable having to be specified in the config not being ideal. However, it's not that bad: for configurations like the database config, we could have a different name than To be fair, I don't like the wrapper function. It's not convenient to type nor pretty: return tag(new SQLiteConfig(
path: __DIR__ . '/backup.sqlite',
), 'backup');Also, no, this unfortunately not fix the final class DatabaseInitializer implements Initializer
{
// ...
#[Singleton]
public function initialize(ClassReflector $class, Container $container, ?string $tag = null): Database
{
return new GenericDatabase(
$container->get(Connection::class, $tag),
$container->get(TransactionManager::class, $tag),
);
}
}Even with |
|
@brendt as an alternative, I just imagined this syntax: final class DatabaseInitializer implements Initializer
{
#[CurrentTag]
private ?string $tag = null;
#[Singleton]
public function initialize(Container $container): Database
{
return new GenericDatabase(
$container->get(Connection::class, $this->tag),
$container->get(TransactionManager::class, $this->tag),
);
}
}This fixes the annoying signature changes, while still allowing to forward the tag the dependency is being initialized with. The dependency would need |
|
@brendt the last commit is a proof of concept of the database working with multiple tagged configs. I feel like the API is still confusing but at least it's not verbose anymore because dynamic initializers are no longer needed: the current tag is injected into a property of the initializer, thanks to the Overall I think the pattern (tagged configs and passthrough tags) is good but the API/code should be cleaned. Would love your help in this regard |
|
Ah, we could add an interface? I don't oppose the changes to |
Just to make sure, have you noticed the
Here is what I did before the last commit: Basically, the needs are as follow:
In the commit above, the user resolves However, I personally am not a fan of having to use dynamic initializer just because we need a tag, which is why I added |
|
Hey @brendt, this is ready for review. If possible I'd like you to play with the feature, see how it stands conceptually, criticize the API—because this would be a fairly important part of future Tempest APIs. The important parts:
|
|
Ok but I still don't understand this part: Let me write up another PR to illustrate what I mean though, because I feel like we're going in circles. |
|
Here's my PR: https://github.com/tempestphp/tempest-framework/pull/1120/files What does your implementation do with the attributes that can't be solved with the interface? |
It's completely unrelated 😅 Your The rest of the implementation is less about tagged configurations and more about giving the framework the ability to initialize objects and their own dependencies with the same tag. I recommend you try this PR out locally: create multiple database configs and inject them in a class with their own tag |
|
Cross posting from Discord DMs I gave it some more thought overnight. I've got two concerns:
#[AllowDynamicTags]
final class DatabaseInitializer implements Initializer
{
#[CurrentTag]
private ?string $tag; // @phpstan-ignore-line this is injectedCould be changed to this: final class DatabaseInitializer implements Initializer
{
private ?Tag $tag;That feels a lot more in line to me with the standard way of how the container works. We could also write it like this to be more explicit: #[Inject]
private ?Tag $tag;
final class DependencyWithDynamicTag
{
public ?Tag $tag;
public function __construct(
#[ForwardTag]
public readonly SubdependencyWithDynamicTag $subependency,
) {}
}
final class SubdependencyWithDynamicTag
{
public ?Tag $tag;
}I'd rather write this: final class DependencyWithDynamicTag
{
public ?Tag $tag;
public function __construct(
public readonly SubdependencyWithDynamicTag $subependency,
) {}
}
final class SubdependencyWithDynamicTag
{
#[InjectFromParent]
public ?Tag $tag;
}IDK, that feels more explicit to me, but maybe I'm overthinking it. |
|
Closing this in favor of #1120 |
Tagged configurations
This pull request adds support for tagged configurations.
This is a new concept that attempts to solve the "multiple X" issue at the foundation level. This could be used, for instance, to support multiple databases by simply defining tagged configurations, and injecting a tagged instance of
Database:Further considerations
Right now, this can only work because
DynamicInitializers have been modified to accept a$tag. An initializer can determine if it will initialize a class as usual, but it can also resolve dependencies using the given tag, allowing to pass it down to the chain of dependencies.This is quite verbose, since it would require an initializer for every class or interface for a feature that would implement this concept.
To avoid that, I added
#[AllowDynamicTags]and#[ForwardTag]. The former prevents a class from not being resolved when it has a tag but no initializer and is not registered yet. The latter resolves the marked parameter using the same tag that was used for the parent class.This is the least verbose pattern I could come up with.
I'm wondering though, if normal
Initializershould also receive the tag. This would allow not using aDynamicInitializerfor theDatabase, for instance. However, the API would become more confusing, with#[Singleton]accepting a tag.Tags as enums
I have taken the opportunity to support enums in the
#[Tag]attribute and all container methods accepting a$tagparameter, as this is usually better than specifying plain strings, which are prone to typos and not easily refactored.Proof of concept
I included a proof of concept of the internal usage of tagged configurations by implementing it on Vite.
Currently, it's only possible to have one Vite configuration in a Tempest project—this means a you can't build multiple manifests or have multiple development servers while using the back-end integrations.
This is solved by specifying the
tagproperty inViteConfig, as well as replacing the defaultbridgeConfigFile,buildDirectoryandmanifest:Related to #339