- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
196 lines
6.3 KiB
PHP
196 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\LiveComponents;
|
|
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\LiveComponents\Attributes\DataProvider;
|
|
use ReflectionClass;
|
|
|
|
/**
|
|
* DataProviderResolver - Resolves DataProvider implementations
|
|
*
|
|
* Uses the Discovery system to find classes with #[DataProvider] attribute
|
|
* and resolves the correct provider instance based on interface and name.
|
|
*
|
|
* Example:
|
|
* ```php
|
|
* $provider = $resolver->resolve(ChartDataProvider::class, 'demo');
|
|
* // Returns instance of DemoChartDataProvider
|
|
* ```
|
|
*/
|
|
final class DataProviderResolver
|
|
{
|
|
/**
|
|
* Cache: interface + name => provider class
|
|
* @var array<string, class-string>
|
|
*/
|
|
private array $cache = [];
|
|
|
|
public function __construct(
|
|
private readonly DiscoveryRegistry $discoveryRegistry,
|
|
private readonly Container $container
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Resolve a data provider by interface and name
|
|
*
|
|
* @template T
|
|
* @param class-string<T> $interface The provider interface
|
|
* @param string $name The provider name (e.g., 'demo', 'database')
|
|
* @return T|null The resolved provider instance or null if not found
|
|
*/
|
|
public function resolve(string $interface, string $name): ?object
|
|
{
|
|
error_log("[DataProviderResolver::resolve] START: interface=$interface, name=$name");
|
|
|
|
// Check cache first
|
|
$cacheKey = $interface . '::' . $name;
|
|
if (isset($this->cache[$cacheKey])) {
|
|
error_log("[DataProviderResolver::resolve] Cache HIT: $cacheKey");
|
|
|
|
return $this->container->get($this->cache[$cacheKey]);
|
|
}
|
|
|
|
error_log("[DataProviderResolver::resolve] Cache MISS, calling findProviderClass");
|
|
|
|
// Find provider class via discovery
|
|
$providerClass = $this->findProviderClass($interface, $name);
|
|
|
|
error_log("[DataProviderResolver::resolve] findProviderClass returned: " . ($providerClass ?? 'NULL'));
|
|
|
|
if ($providerClass === null) {
|
|
return null;
|
|
}
|
|
|
|
// Cache the result
|
|
$this->cache[$cacheKey] = $providerClass;
|
|
|
|
// Resolve instance from container
|
|
return $this->container->get($providerClass);
|
|
}
|
|
|
|
/**
|
|
* Find provider class that implements interface with given name
|
|
*
|
|
* @param class-string $interface
|
|
* @param string $name
|
|
* @return class-string|null
|
|
*/
|
|
private function findProviderClass(string $interface, string $name): ?string
|
|
{
|
|
// Get all classes with DataProvider attribute from AttributeRegistry
|
|
$discoveredAttributes = $this->discoveryRegistry->attributes()->get(DataProvider::class);
|
|
|
|
// DEBUG: Write to file
|
|
file_put_contents('/tmp/resolver-debug.log', sprintf(
|
|
"[%s] Looking for: interface=%s, name=%s, found=%d attributes\n",
|
|
date('Y-m-d H:i:s'),
|
|
$interface,
|
|
$name,
|
|
count($discoveredAttributes)
|
|
), FILE_APPEND);
|
|
|
|
foreach ($discoveredAttributes as $discoveredAttribute) {
|
|
// Get class name as string from ClassName value object
|
|
$className = $discoveredAttribute->className->getFullyQualified();
|
|
|
|
file_put_contents('/tmp/resolver-debug.log', sprintf(
|
|
" Checking: class=%s\n",
|
|
$className
|
|
), FILE_APPEND);
|
|
|
|
// Get provider name from attribute arguments
|
|
$arguments = $discoveredAttribute->arguments;
|
|
if (! isset($arguments['name'])) {
|
|
file_put_contents('/tmp/resolver-debug.log', " SKIP: no 'name' argument\n", FILE_APPEND);
|
|
|
|
continue; // Skip if no name argument (shouldn't happen with our attribute)
|
|
}
|
|
|
|
file_put_contents('/tmp/resolver-debug.log', sprintf(
|
|
" name='%s' (looking for '%s')\n",
|
|
$arguments['name'],
|
|
$name
|
|
), FILE_APPEND);
|
|
|
|
// Check if name matches
|
|
if ($arguments['name'] !== $name) {
|
|
file_put_contents('/tmp/resolver-debug.log', " SKIP: name mismatch\n", FILE_APPEND);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Check if class implements the interface
|
|
$reflection = new ReflectionClass($className);
|
|
$implements = $reflection->implementsInterface($interface);
|
|
file_put_contents('/tmp/resolver-debug.log', sprintf(
|
|
" implements %s? %s\n",
|
|
$interface,
|
|
$implements ? 'YES' : 'NO'
|
|
), FILE_APPEND);
|
|
|
|
if ($implements) {
|
|
file_put_contents('/tmp/resolver-debug.log', " MATCH! Returning $className\n", FILE_APPEND);
|
|
|
|
return $className;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if a provider exists
|
|
*
|
|
* @param class-string $interface
|
|
* @param string $name
|
|
*/
|
|
public function has(string $interface, string $name): bool
|
|
{
|
|
$cacheKey = $interface . '::' . $name;
|
|
|
|
if (isset($this->cache[$cacheKey])) {
|
|
return true;
|
|
}
|
|
|
|
return $this->findProviderClass($interface, $name) !== null;
|
|
}
|
|
|
|
/**
|
|
* Get all available provider names for an interface
|
|
*
|
|
* @param class-string $interface
|
|
* @return array<string> List of provider names
|
|
*/
|
|
public function getAvailableProviders(string $interface): array
|
|
{
|
|
$providers = [];
|
|
|
|
// Get all classes with DataProvider attribute from AttributeRegistry
|
|
$discoveredAttributes = $this->discoveryRegistry->attributes()->get(DataProvider::class);
|
|
|
|
foreach ($discoveredAttributes as $discoveredAttribute) {
|
|
// Get class name as string from ClassName value object
|
|
$className = $discoveredAttribute->className->getFullyQualified();
|
|
|
|
// Check if class implements the interface
|
|
$reflection = new ReflectionClass($className);
|
|
if (! $reflection->implementsInterface($interface)) {
|
|
continue;
|
|
}
|
|
|
|
// Get provider name from arguments
|
|
$arguments = $discoveredAttribute->arguments;
|
|
if (isset($arguments['name'])) {
|
|
$providers[] = $arguments['name'];
|
|
}
|
|
}
|
|
|
|
return array_unique($providers);
|
|
}
|
|
}
|