- 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.
236 lines
5.2 KiB
PHP
236 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core\ValueObjects;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Immutable PHP namespace value object with validation and operations
|
|
*/
|
|
final readonly class PhpNamespace
|
|
{
|
|
private string $namespace;
|
|
|
|
private function __construct(string $namespace)
|
|
{
|
|
// Empty namespace is valid (global namespace)
|
|
if ($namespace === '') {
|
|
$this->namespace = '';
|
|
return;
|
|
}
|
|
|
|
// Remove leading/trailing backslashes
|
|
$namespace = trim($namespace, '\\');
|
|
|
|
if (! $this->isValidNamespace($namespace)) {
|
|
throw new InvalidArgumentException("Invalid namespace: {$namespace}");
|
|
}
|
|
|
|
$this->namespace = $namespace;
|
|
}
|
|
|
|
/**
|
|
* Create from namespace string
|
|
*/
|
|
public static function fromString(string $namespace): self
|
|
{
|
|
return new self($namespace);
|
|
}
|
|
|
|
/**
|
|
* Create from class name
|
|
*/
|
|
public static function fromClass(string $className): self
|
|
{
|
|
$className = ltrim($className, '\\');
|
|
$lastBackslash = strrpos($className, '\\');
|
|
|
|
if ($lastBackslash === false) {
|
|
return new self(''); // Global namespace
|
|
}
|
|
|
|
return new self(substr($className, 0, $lastBackslash));
|
|
}
|
|
|
|
/**
|
|
* Create from namespace parts
|
|
* @param array<string> $parts
|
|
*/
|
|
public static function fromParts(array $parts): self
|
|
{
|
|
return new self(implode('\\', $parts));
|
|
}
|
|
|
|
/**
|
|
* Create global namespace
|
|
*/
|
|
public static function global(): self
|
|
{
|
|
return new self('');
|
|
}
|
|
|
|
/**
|
|
* Get namespace as string
|
|
*/
|
|
public function toString(): string
|
|
{
|
|
return $this->namespace;
|
|
}
|
|
|
|
/**
|
|
* Get namespace parts
|
|
* @return array<string>
|
|
*/
|
|
public function parts(): array
|
|
{
|
|
if ($this->namespace === '') {
|
|
return [];
|
|
}
|
|
|
|
return explode('\\', $this->namespace);
|
|
}
|
|
|
|
/**
|
|
* Get namespace depth (number of levels)
|
|
*/
|
|
public function depth(): int
|
|
{
|
|
if ($this->namespace === '') {
|
|
return 0;
|
|
}
|
|
|
|
return count($this->parts());
|
|
}
|
|
|
|
/**
|
|
* Check if this is the global namespace
|
|
*/
|
|
public function isGlobal(): bool
|
|
{
|
|
return $this->namespace === '';
|
|
}
|
|
|
|
/**
|
|
* Get parent namespace
|
|
*/
|
|
public function parent(): ?self
|
|
{
|
|
$parts = $this->parts();
|
|
|
|
if (count($parts) <= 1) {
|
|
return null; // No parent or already at global namespace
|
|
}
|
|
|
|
array_pop($parts);
|
|
|
|
return self::fromParts($parts);
|
|
}
|
|
|
|
/**
|
|
* Append namespace segment
|
|
*/
|
|
public function append(string $segment): self
|
|
{
|
|
if ($this->namespace === '') {
|
|
return new self($segment);
|
|
}
|
|
|
|
return new self($this->namespace . '\\' . ltrim($segment, '\\'));
|
|
}
|
|
|
|
/**
|
|
* Check if namespace starts with given prefix
|
|
*/
|
|
public function startsWith(string|self $prefix): bool
|
|
{
|
|
$prefixStr = $prefix instanceof self ? $prefix->toString() : $prefix;
|
|
$prefixStr = trim($prefixStr, '\\');
|
|
|
|
if ($prefixStr === '') {
|
|
return true; // All namespaces start with global namespace
|
|
}
|
|
|
|
return str_starts_with($this->namespace, $prefixStr);
|
|
}
|
|
|
|
/**
|
|
* Check if namespace ends with given suffix
|
|
*/
|
|
public function endsWith(string|self $suffix): bool
|
|
{
|
|
$suffixStr = $suffix instanceof self ? $suffix->toString() : $suffix;
|
|
$suffixStr = trim($suffixStr, '\\');
|
|
|
|
if ($suffixStr === '') {
|
|
return true;
|
|
}
|
|
|
|
return str_ends_with($this->namespace, $suffixStr);
|
|
}
|
|
|
|
/**
|
|
* Compare for equality
|
|
*/
|
|
public function equals(self $other): bool
|
|
{
|
|
return $this->namespace === $other->namespace;
|
|
}
|
|
|
|
/**
|
|
* Convert to a fully qualified class name (with leading backslash)
|
|
*/
|
|
public function toFqcn(string $className): string
|
|
{
|
|
if ($this->namespace === '') {
|
|
return '\\' . $className;
|
|
}
|
|
|
|
return '\\' . $this->namespace . '\\' . $className;
|
|
}
|
|
|
|
/**
|
|
* Convert to file path (for PSR-4 autoloading)
|
|
*/
|
|
public function toPath(): string
|
|
{
|
|
if ($this->namespace === '') {
|
|
return '';
|
|
}
|
|
|
|
return str_replace('\\', '/', $this->namespace);
|
|
}
|
|
|
|
/**
|
|
* String representation
|
|
*/
|
|
public function __toString(): string
|
|
{
|
|
return $this->namespace;
|
|
}
|
|
|
|
/**
|
|
* Validate namespace format
|
|
*/
|
|
private function isValidNamespace(string $namespace): bool
|
|
{
|
|
// Empty namespace is valid (global namespace)
|
|
if ($namespace === '') {
|
|
return true;
|
|
}
|
|
|
|
// Each part must be a valid PHP identifier
|
|
$parts = explode('\\', $namespace);
|
|
|
|
foreach ($parts as $part) {
|
|
// Must start with letter or underscore, followed by letters, numbers, or underscores
|
|
if (! preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $part)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|