Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/vite-plugin-tempest/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Options } from './plugin'
import type { TempestViteConfiguration } from './types'
import { exec, php } from './utils'

const TEMPEST_BIN = 'tempest'
const VITE_CONFIG_COMMAND = 'vite:config'

export async function loadTempestConfiguration(): Promise<TempestViteConfiguration> {
export async function loadTempestConfiguration(options: Options = {}): Promise<TempestViteConfiguration> {
try {
const { stdout } = await exec(`${php.value} ${TEMPEST_BIN} ${VITE_CONFIG_COMMAND}`)
const { stdout } = await exec(`${php.value} ${TEMPEST_BIN} ${VITE_CONFIG_COMMAND} --tag=${options.tag ?? 'null'}`)
const json = stdout.match(/\{.*\}/s)

return JSON.parse(json?.[0] as string)
Expand Down
8 changes: 6 additions & 2 deletions packages/vite-plugin-tempest/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ const TEMPEST_ORIGIN_PLACEHOLDER = 'http://__tempest_placeholder__.test'

let exitHandlersBound = false

export default function tempest(): Plugin {
export interface Options {
tag?: string
}

export default function tempest(options: Options = {}): Plugin {
let viteDevServerUrl: DevelopmentServerUrl
let bridgeFilePath: string
let resolvedConfig: ResolvedConfig
Expand All @@ -30,7 +34,7 @@ export default function tempest(): Plugin {
name: 'tempest',
enforce: 'post',
config: async (config, { command, mode }) => {
tempestConfig = await loadTempestConfiguration()
tempestConfig = await loadTempestConfiguration(options)
userConfig = config

const ssr = !!userConfig.build?.ssr
Expand Down
13 changes: 13 additions & 0 deletions src/Tempest/Container/src/AllowDynamicTags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tempest\Container;

use Attribute;

/**
* Allows this class to be instanciated with any tag.
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class AllowDynamicTags
{
}
7 changes: 4 additions & 3 deletions src/Tempest/Container/src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
use Tempest\Reflection\ClassReflector;
use Tempest\Reflection\FunctionReflector;
use Tempest\Reflection\MethodReflector;
use UnitEnum;

interface Container
{
public function register(string $className, callable $definition): self;

public function unregister(string $className): self;

public function singleton(string $className, mixed $definition, ?string $tag = null): self;
public function singleton(string $className, mixed $definition, null|string|UnitEnum $tag = null): self;

public function config(object $config): self;

Expand All @@ -23,9 +24,9 @@ public function config(object $config): self;
* @param class-string<TClassName> $className
* @return null|TClassName
*/
public function get(string $className, ?string $tag = null, mixed ...$params): mixed;
public function get(string $className, null|string|UnitEnum $tag = null, mixed ...$params): mixed;

public function has(string $className, ?string $tag = null): bool;
public function has(string $className, null|string|UnitEnum $tag = null): bool;

public function invoke(ClassReflector|MethodReflector|FunctionReflector|callable|string $method, mixed ...$params): mixed;

Expand Down
13 changes: 13 additions & 0 deletions src/Tempest/Container/src/CurrentTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tempest\Container;

use Attribute;

/**
* Injects to the property the tag with which the current class has been resolved.
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class CurrentTag
{
}
13 changes: 13 additions & 0 deletions src/Tempest/Container/src/ForwardTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tempest\Container;

use Attribute;

/**
* Resolves this dependency using the tag with which the current class has been resolved.
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
final class ForwardTag
{
}
73 changes: 54 additions & 19 deletions src/Tempest/Container/src/GenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Tempest\Reflection\ParameterReflector;
use Tempest\Reflection\TypeReflector;
use Throwable;
use UnitEnum;

final class GenericContainer implements Container
{
Expand Down Expand Up @@ -87,12 +88,12 @@ public function unregister(string $className): self
return $this;
}

public function has(string $className, ?string $tag = null): bool
public function has(string $className, null|string|UnitEnum $tag = null): bool
{
return isset($this->definitions[$className]) || isset($this->singletons[$this->resolveTaggedName($className, $tag)]);
}

public function singleton(string $className, mixed $definition, ?string $tag = null): self
public function singleton(string $className, mixed $definition, null|string|UnitEnum $tag = null): self
{
$className = $this->resolveTaggedName($className, $tag);

Expand All @@ -103,16 +104,20 @@ public function singleton(string $className, mixed $definition, ?string $tag = n

public function config(object $config): self
{
$this->singleton($config::class, $config);
$tag = ($config instanceof TaggedConfig)
? $config->tag
: null;

$this->singleton($config::class, $config, $tag);

foreach (new ClassReflector($config)->getInterfaces() as $interface) {
$this->singleton($interface->getName(), $config);
$this->singleton($interface->getName(), $config, $tag);
}

return $this;
}

public function get(string $className, ?string $tag = null, mixed ...$params): ?object
public function get(string $className, null|string|UnitEnum $tag = null, mixed ...$params): ?object
{
$this->resolveChain();

Expand Down Expand Up @@ -232,7 +237,7 @@ public function addInitializer(ClassReflector|string $initializerClass): Contain
return $this;
}

private function resolve(string $className, ?string $tag = null, mixed ...$params): ?object
private function resolve(string $className, null|string|UnitEnum $tag = null, mixed ...$params): ?object
{
$class = new ClassReflector($className);

Expand Down Expand Up @@ -277,13 +282,14 @@ private function resolve(string $className, ?string $tag = null, mixed ...$param
return $object;
}

// If we're requesting a tagged dependency and haven't resolved it at this point, something's wrong
if ($tag) {
throw new CannotResolveTaggedDependency($this->chain, new Dependency($className), $tag);
// If we're requesting a non-dynamic tagged dependency and
// haven't resolved it at this point, something's wrong
if ($tag !== null && ! $class->getAttribute(AllowDynamicTags::class)) {
throw new CannotResolveTaggedDependency($this->chain, new Dependency($className), $this->resolveTag($tag));
}

// Finally, autowire the class.
return $this->autowire($className, ...$params);
return $this->autowire($className, $params, $tag);
}

private function initializerForBuiltin(TypeReflector $target, string $tag): ?Initializer
Expand All @@ -295,18 +301,25 @@ private function initializerForBuiltin(TypeReflector $target, string $tag): ?Ini
return null;
}

private function initializerForClass(ClassReflector $target, ?string $tag = null): null|Initializer|DynamicInitializer
private function initializerForClass(ClassReflector $target, null|string|UnitEnum $tag = null): null|Initializer|DynamicInitializer
{
// Initializers themselves can't be initialized,
// otherwise you'd end up with infinite loops
if ($target->getType()->matches(Initializer::class) || $target->getType()->matches(DynamicInitializer::class)) {
return null;
}

// If an initializer is registered for the specified dependency and tag, we use it.
if ($initializerClass = $this->initializers[$this->resolveTaggedName($target->getName(), $tag)] ?? null) {
return $this->resolve($initializerClass);
}

// If the dependency allows dynamic tags, we look for the original
// initializer (without tag) and we resolve it, specifying the dynamic tag.
if ($target->getAttribute(AllowDynamicTags::class) && ($initializerClass = $this->initializers[$target->getName()] ?? null)) {
return $this->resolve($initializerClass, $tag);
}

// Loop through the registered initializers to see if
// we have something to handle this class.
foreach ($this->dynamicInitializers as $initializerClass) {
Expand All @@ -323,7 +336,7 @@ private function initializerForClass(ClassReflector $target, ?string $tag = null
return null;
}

private function autowire(string $className, mixed ...$params): object
private function autowire(string $className, array $params, null|string|UnitEnum $tag): object
{
$classReflector = new ClassReflector($className);

Expand All @@ -340,7 +353,7 @@ private function autowire(string $className, mixed ...$params): object
: // Otherwise, use our autowireDependencies helper to automagically
// build up each parameter.
$classReflector->newInstanceArgs(
$this->autowireDependencies($constructor, $params),
$this->autowireDependencies($constructor, $params, $tag),
);

if (
Expand All @@ -352,6 +365,7 @@ private function autowire(string $className, mixed ...$params): object
}

foreach ($classReflector->getProperties() as $property) {
// Injects to the property the specified dependency
if ($property->hasAttribute(Inject::class) && ! $property->isInitialized($instance)) {
if ($property->hasAttribute(Lazy::class)) {
$property->set($instance, $property->getType()->asClass()->getReflection()->newLazyProxy(
Expand All @@ -361,6 +375,11 @@ private function autowire(string $className, mixed ...$params): object
$property->set($instance, $this->get($property->getType()->getName()));
}
}

// Injects to the property the tag the class has been resolved with
if ($property->hasAttribute(CurrentTag::class) && ! $property->isInitialized($instance)) {
$property->set($instance, $property->accepts(UnitEnum::class) ? $tag : $this->resolveTag($tag));
}
}

return $instance;
Expand All @@ -369,7 +388,7 @@ private function autowire(string $className, mixed ...$params): object
/**
* @return ParameterReflector[]
*/
private function autowireDependencies(MethodReflector|FunctionReflector $method, array $parameters = []): array
private function autowireDependencies(MethodReflector|FunctionReflector $method, array $parameters = [], null|string|UnitEnum $tag = null): array
{
$this->resolveChain()->add($method);

Expand All @@ -378,17 +397,24 @@ private function autowireDependencies(MethodReflector|FunctionReflector $method,
// Build the class by iterating through its
// dependencies and resolving them.
foreach ($method->getParameters() as $parameter) {
// If the `ForwardTag` attribute is used on a constructor parameter, we
// instantiate this parameter with the current tag. Otherwise we look
// for a `Tag` attribute, and if specified, we use this one instead.
$dependencyTag = $parameter->getAttribute(ForwardTag::class) && $tag
? $tag
: $parameter->getAttribute(Tag::class)?->name;

$dependencies[] = $this->clone()->autowireDependency(
parameter: $parameter,
tag: $parameter->getAttribute(Tag::class)?->name,
tag: $dependencyTag,
providedValue: $parameters[$parameter->getName()] ?? null,
);
}

return $dependencies;
}

private function autowireDependency(ParameterReflector $parameter, ?string $tag, mixed $providedValue = null): mixed
private function autowireDependency(ParameterReflector $parameter, null|string|UnitEnum $tag, mixed $providedValue = null): mixed
{
$parameterType = $parameter->getType();

Expand Down Expand Up @@ -428,7 +454,7 @@ private function autowireDependency(ParameterReflector $parameter, ?string $tag,
throw $lastThrowable ?? new CannotAutowireException($this->chain, new Dependency($parameter));
}

private function autowireObjectDependency(TypeReflector $type, ?string $tag, mixed $providedValue, bool $lazy): mixed
private function autowireObjectDependency(TypeReflector $type, null|string|UnitEnum $tag, mixed $providedValue, bool $lazy): mixed
{
// If the provided value is of the right type,
// don't waste time autowiring, return it!
Expand Down Expand Up @@ -525,10 +551,19 @@ public function __clone(): void
$this->chain = $this->chain?->clone();
}

private function resolveTaggedName(string $className, ?string $tag): string
private function resolveTag(null|string|UnitEnum $tag): ?string
{
if ($tag instanceof UnitEnum) {
return $tag->name;
}

return $tag;
}

private function resolveTaggedName(string $className, null|string|UnitEnum $tag): string
{
return $tag
? "{$className}#{$tag}"
? "{$className}#{$this->resolveTag($tag)}"
: $className;
}
}
3 changes: 2 additions & 1 deletion src/Tempest/Container/src/Singleton.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
namespace Tempest\Container;

use Attribute;
use UnitEnum;

#[Attribute]
final readonly class Singleton
{
public function __construct(
public ?string $tag = null,
public null|string|UnitEnum $tag = null,
) {}
}
12 changes: 12 additions & 0 deletions src/Tempest/Container/src/TaggedConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Tempest\Container;

use UnitEnum;

interface TaggedConfig
{
public null|UnitEnum|string $tag {
get;
}
}
3 changes: 2 additions & 1 deletion src/Tempest/Container/src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Tempest\Container\GenericContainer;
use Tempest\Reflection\FunctionReflector;
use Tempest\Reflection\MethodReflector;
use UnitEnum;

/**
* Retrieves an instance of the specified `$className` from the container.
Expand All @@ -14,7 +15,7 @@
* @param class-string<TClassName> $className
* @return TClassName
*/
function get(string $className, ?string $tag = null, mixed ...$params): object
function get(string $className, null|string|UnitEnum $tag = null, mixed ...$params): object
{
$container = GenericContainer::instance();

Expand Down
Loading
Loading