- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
124 lines
3.1 KiB
PHP
124 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core\ValueObjects;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Represents a city name with optional state/province
|
|
* Immutable value object for address handling
|
|
*/
|
|
final readonly class City
|
|
{
|
|
public function __construct(
|
|
public string $name,
|
|
public ?string $state = null,
|
|
public ?CountryCode $country = null
|
|
) {
|
|
if (trim($name) === '') {
|
|
throw new InvalidArgumentException('City name cannot be empty');
|
|
}
|
|
}
|
|
|
|
public static function create(string $name, ?string $state = null, ?CountryCode $country = null): self
|
|
{
|
|
return new self($name, $state, $country);
|
|
}
|
|
|
|
public function withState(string $state): self
|
|
{
|
|
return new self($this->name, $state, $this->country);
|
|
}
|
|
|
|
public function withCountry(CountryCode $country): self
|
|
{
|
|
return new self($this->name, $this->state, $country);
|
|
}
|
|
|
|
public function withoutState(): self
|
|
{
|
|
return new self($this->name, null, $this->country);
|
|
}
|
|
|
|
public function format(bool $includeState = true, bool $includeCountry = false): string
|
|
{
|
|
$formatted = $this->name;
|
|
|
|
if ($includeState && $this->state !== null && trim($this->state) !== '') {
|
|
$formatted .= ', ' . $this->state;
|
|
}
|
|
|
|
if ($includeCountry && $this->country !== null) {
|
|
$formatted .= ', ' . ($this->country->getCountryName() ?? $this->country->value);
|
|
}
|
|
|
|
return $formatted;
|
|
}
|
|
|
|
public function getDisplayName(): string
|
|
{
|
|
return $this->format(true, false);
|
|
}
|
|
|
|
public function getFullName(): string
|
|
{
|
|
return $this->format(true, true);
|
|
}
|
|
|
|
public function hasState(): bool
|
|
{
|
|
return $this->state !== null && trim($this->state) !== '';
|
|
}
|
|
|
|
public function hasCountry(): bool
|
|
{
|
|
return $this->country !== null;
|
|
}
|
|
|
|
public function equals(self $other): bool
|
|
{
|
|
return $this->name === $other->name
|
|
&& $this->state === $other->state
|
|
&& (
|
|
($this->country === null && $other->country === null) ||
|
|
($this->country !== null && $other->country !== null && $this->country->value === $other->country->value)
|
|
);
|
|
}
|
|
|
|
public function toString(): string
|
|
{
|
|
return $this->getDisplayName();
|
|
}
|
|
|
|
public function __toString(): string
|
|
{
|
|
return $this->getDisplayName();
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'name' => $this->name,
|
|
'state' => $this->state,
|
|
'country' => $this->country?->value,
|
|
'display_name' => $this->getDisplayName(),
|
|
'full_name' => $this->getFullName(),
|
|
];
|
|
}
|
|
|
|
public static function fromArray(array $data): self
|
|
{
|
|
$country = isset($data['country'])
|
|
? CountryCode::fromString($data['country'])
|
|
: null;
|
|
|
|
return new self(
|
|
$data['name'],
|
|
$data['state'] ?? null,
|
|
$country
|
|
);
|
|
}
|
|
}
|