diff --git a/docs/strategy/ai-driven-strategy.md b/docs/strategy/ai-driven-strategy.md index 084ade7b..c908a995 100644 --- a/docs/strategy/ai-driven-strategy.md +++ b/docs/strategy/ai-driven-strategy.md @@ -67,8 +67,13 @@ Three pillars: 2. **Reliability by construction.** Features come from proven, tested, reversible spec modules — not freehand generation. Mistakes cost one `rewind`. -3. **You own the code.** Real PHP, real database, exportable, no lock-in. This - is the answer to both no-code (lock-in) and JS builders (throwaway). +3. **You own the outcome — not source-as-a-chore.** For an outcome-seeker, + meaningful ownership is *control of the running service* (publish, update, + roll back, custom domain, take it down), *your data* (exportable), and the + *right to take the code and leave* (the escape hatch that keeps us honest and + lock-in-free). They never have to touch the code day-to-day — but they can + always claim it. This answers both no-code (lock-in) and the JS builders + (throwaway, and you still have to operate it). See §10. ## 4. The "X, Y, Z features" product: a capability catalog @@ -104,10 +109,12 @@ This reframes the roadmap: **stop building "framework features"; start building | 2 | **Intent compiler (Gii-for-AI)**, framework-agnostic | Boilerplate elimination; the Yii lever | A → B substrate | CLI, no lock-in | One spec → CRUD + persistence + OpenAPI + SDK + tests | the 90-second build | | 3 | **Stripe payments kit** (builds on #1) | Correct billing plumbing nobody owns generically | A + B | module | Idempotent webhooks, state machine, reconciliation, sandbox | "subscriptions that don't double-charge" | | 4 | **MCP "ship a feature" server** (generative + reversible) | Agent operability in *any* project | B | point your agent at it | Agent scaffolds / migrates / rewinds anywhere | agent live-builds an endpoint on camera | -| 5 | **Capability catalog + composer** ("describe app → working app") | The Tier-B destination | B | hosted / agent-driven | Natural-language app assembly from proven modules | the flagship demo | +| 5 | **Ship / deploy (reversible)** | "built" isn't "working" until it's reachable; deploy is unowned in the framework world and scary for agents | A + B | `altair ship` / managed publish | Push-button deploy + migrations; **rollback = `rewind` for prod** | "agent ships to a live URL, then rolls back in one command" | +| 6 | **Capability catalog + composer** ("describe app → working app") | The Tier-B destination | B | hosted / agent-driven | Natural-language app assembly from proven modules | the flagship demo | -**Sequence:** #1 → #2 → (#3 ∥ #4) → #5. Each ships real value and a marketing -moment on its own; #5 is the synthesis everything builds toward. +**Sequence:** #1 → #2 → (#3 ∥ #4) → #5 → #6. Each ships real value and a +marketing moment on its own; #5 (shipping) turns "built" into "live," and #6 is +the synthesis everything builds toward. **First move:** #1 (webhook+idempotency+outbox). It's cheap to adopt, broadly useful beyond payments, demonstrable in 60 seconds, fills a *real* gap, and is @@ -152,10 +159,13 @@ purest showcase. ## 9. Open questions to resolve before building #5 -1. **Hosted vs. self-hosted** for the "describe → app" experience? Hosted = - monetization + control of the experience; self-hosted = ownership purity. - (Likely: hosted experience that *exports* an owned, self-hostable codebase — - best of both.) +1. **Hosted vs. self-hosted** — *resolving toward hybrid.* A hosted "publish it, + it's live now" experience (instant gratification + monetization) with a + **sacred export escape hatch** to an owned, self-hostable codebase. Tier B + owns the running service + data + the right to leave; they don't operate the + code day-to-day. Honest constraint: a fully *managed* PaaS is a company-sized + build — start by *generating* a deploy pipeline to existing platforms (Fly, + Railway, Render, VPS via Docker/IaC), not by building our own cloud. See §10. 2. **Which five catalog modules ship first?** Decide by "most-requested real app features," not by what's easiest to build. 3. **Brand** for the Tier-B experience — probably distinct from "Altair the @@ -163,6 +173,37 @@ purest showcase. --- +## 10. Creation + shipping: deployment as a reversible primitive + +A working app nobody can reach isn't working. **"Describe → built → live"** in +one motion is the whole Tier-B promise — creation and shipping are one flow, not +two products. Deployment slots into the existing thesis instead of bolting on: + +- **Reversible by construction.** A deploy is just another journaled mutation: + `ship` records an event; **`rollback` is `rewind` for production.** The agent + ships boldly because undo is one command — the property that makes scaffolding + safe makes deployment safe. +- **Both tiers, one engine.** Tier A: `altair ship` deploys an *owned* app to + *their* infra (build, migrate, zero-downtime swap, rollback). Tier B: the same + engine behind a managed "publish it" button — describe → live, export intact. +- **Ownership, reframed (Pillar 3).** "They don't own the code, they own the + power to publish it" is right for Tier B — *with* the right to claim the code + and leave. Operational control + data + escape hatch is the ownership that + matters to someone who can't read the source. + +**Honest sequencing.** Do **not** start by building a managed PaaS — infra, +secrets, billing, scaling, on-call, and compliance are a company unto themselves +and wrong for a solo+agent flagship. Start by *generating* a deploy pipeline +(Dockerfile + IaC + one `altair ship` command) targeting existing platforms; add +a thin managed layer or a hosting partnership later, only once demand is proven. + +**Security.** Deploy touches secrets and mutates production — both +outward/high-stakes. Reuse the event log's secret **Scrubber**, require explicit +confirmation before prod deploys (no silent agent push-to-prod), and keep every +deploy/rollback in the journal for audit. + +--- + ### TL;DR Two markets: developers who want less effort, and a bigger group who won't learn @@ -171,5 +212,9 @@ making the framework's *operator* an agent. The unclaimed seam is **reliable, owned, maintainable AI-built apps** — which our deterministic, reversible, spec-driven core is uniquely suited to. Build it as a **catalog of agent-installable capability modules**; each module is a cheap developer tool -*and* a building block for "describe → app." Start with webhook+idempotency, then -the intent compiler. Honest benchmarks and live demos are the marketing. +*and* a building block for "describe → app." Creation isn't done until it ships: +deployment is built in and reversible (**rollback = rewind**), so "describe → +built → live" is one motion — and for Tier B, ownership means control of the +running service plus the right to leave, not babysitting source. Start with +webhook+idempotency, then the intent compiler. Honest benchmarks and live demos +are the marketing. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 714fcd3e..a625464e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -56,65 +56,10 @@ parameters: path: src/Altair/Common/Support/Arr.php - - message: "#^Class Altair\\\\Configuration\\\\Collection\\\\ConfigurationCollection extends generic class Altair\\\\Structure\\\\Set but does not specify its types\\: TValue$#" - count: 1 - path: src/Altair/Configuration/Collection/ConfigurationCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\AliasesCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" + message: "#^Method Altair\\\\Container\\\\Collection\\\\AliasesCollection\\:\\:define\\(\\) should return Altair\\\\Container\\\\Collection\\\\AliasesCollection but returns Altair\\\\Structure\\\\Contracts\\\\MapInterface\\\\.$#" count: 1 path: src/Altair/Container/Collection/AliasesCollection.php - - - message: "#^Method Altair\\\\Container\\\\Collection\\\\AliasesCollection\\:\\:define\\(\\) should return Altair\\\\Container\\\\Collection\\\\AliasesCollection but returns Altair\\\\Structure\\\\Contracts\\\\MapInterface\\\\.$#" - count: 1 - path: src/Altair/Container/Collection/AliasesCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\ClassDefinitionsCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/ClassDefinitionsCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\DelegatesCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/DelegatesCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\ParameterDefinitionsCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/ParameterDefinitionsCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\PreparesCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/PreparesCollection.php - - - - message: "#^Class Altair\\\\Container\\\\Collection\\\\SharesCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/SharesCollection.php - - - - message: "#^Method Altair\\\\Container\\\\Collection\\\\SharesCollection\\:\\:shareClass\\(\\) return type has no value type specified in iterable type Altair\\\\Structure\\\\Contracts\\\\MapInterface\\.$#" - count: 1 - path: src/Altair/Container/Collection/SharesCollection.php - - - - message: "#^Method Altair\\\\Container\\\\Collection\\\\SharesCollection\\:\\:shareClass\\(\\) return type with generic interface Altair\\\\Structure\\\\Contracts\\\\MapInterface does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/SharesCollection.php - - - - message: "#^Method Altair\\\\Container\\\\Collection\\\\SharesCollection\\:\\:shareInstance\\(\\) return type has no value type specified in iterable type Altair\\\\Structure\\\\Contracts\\\\MapInterface\\.$#" - count: 1 - path: src/Altair/Container/Collection/SharesCollection.php - - - - message: "#^Method Altair\\\\Container\\\\Collection\\\\SharesCollection\\:\\:shareInstance\\(\\) return type with generic interface Altair\\\\Structure\\\\Contracts\\\\MapInterface does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Container/Collection/SharesCollection.php - - message: "#^Call to an undefined method ReflectionFunctionAbstract\\:\\:invokeArgs\\(\\)\\.$#" count: 1 @@ -135,36 +80,6 @@ parameters: count: 1 path: src/Altair/Cookie/Collection/CookieCollection.php - - - message: "#^Generic type Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\ in PHPDoc tag @return specifies 2 template types, but interface Altair\\\\Structure\\\\Contracts\\\\VectorInterface supports only 1\\: TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - - - message: "#^Method Altair\\\\Cookie\\\\Collection\\\\CookieCollection\\:\\:pairsToArray\\(\\) has parameter \\$pairs with generic class Altair\\\\Structure\\\\Pair but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - - - message: "#^Method Altair\\\\Cookie\\\\Collection\\\\CookieCollection\\:\\:values\\(\\) should return Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\ but returns Altair\\\\Structure\\\\Vector\\\\.$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - - - message: "#^PHPDoc tag @var for variable \\$pair contains generic class Altair\\\\Structure\\\\Pair but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - - - message: "#^Return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Cookie\\\\Collection\\\\CookieCollection\\:\\:values\\(\\) should be compatible with return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Structure\\\\Contracts\\\\MapInterface\\\\:\\:values\\(\\)$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - - - message: "#^Return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Cookie\\\\Collection\\\\CookieCollection\\:\\:values\\(\\) should be compatible with return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Structure\\\\Map\\\\:\\:values\\(\\)$#" - count: 1 - path: src/Altair/Cookie/Collection/CookieCollection.php - - message: "#^Unsafe usage of new static\\(\\)\\.$#" count: 1 @@ -175,36 +90,6 @@ parameters: count: 1 path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - message: "#^Generic type Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\ in PHPDoc tag @return specifies 2 template types, but interface Altair\\\\Structure\\\\Contracts\\\\VectorInterface supports only 1\\: TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - - message: "#^Method Altair\\\\Cookie\\\\Collection\\\\SetCookieCollection\\:\\:pairsToArray\\(\\) has parameter \\$pairs with generic class Altair\\\\Structure\\\\Pair but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - - message: "#^Method Altair\\\\Cookie\\\\Collection\\\\SetCookieCollection\\:\\:values\\(\\) should return Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\ but returns Altair\\\\Structure\\\\Vector\\\\.$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - - message: "#^PHPDoc tag @var for variable \\$pair contains generic class Altair\\\\Structure\\\\Pair but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - - message: "#^Return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Cookie\\\\Collection\\\\SetCookieCollection\\:\\:values\\(\\) should be compatible with return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Structure\\\\Contracts\\\\MapInterface\\\\:\\:values\\(\\)$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - - - message: "#^Return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Cookie\\\\Collection\\\\SetCookieCollection\\:\\:values\\(\\) should be compatible with return type \\(Altair\\\\Structure\\\\Contracts\\\\VectorInterface\\\\) of method Altair\\\\Structure\\\\Map\\\\:\\:values\\(\\)$#" - count: 1 - path: src/Altair/Cookie/Collection/SetCookieCollection.php - - message: "#^Unsafe usage of new static\\(\\)\\.$#" count: 1 @@ -245,31 +130,16 @@ parameters: count: 1 path: src/Altair/Courier/Strategy/CommandRunnerMiddlewareStrategy.php - - - message: "#^Class Altair\\\\Courier\\\\Support\\\\MessageCommandMap extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Courier/Support/MessageCommandMap.php - - message: "#^PHPDoc tag @var for property Altair\\\\Http\\\\Base\\\\Payload\\:\\:\\$settingsCollection with type Altair\\\\Structure\\\\Map\\|null is not subtype of native type Altair\\\\Http\\\\Collection\\\\SettingsCollection\\.$#" count: 1 path: src/Altair/Http/Base/Payload.php - - - message: "#^Class Altair\\\\Http\\\\Collection\\\\HttpStatusCollection implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Http/Collection/HttpStatusCollection.php - - message: "#^Instanceof between array and Traversable will always evaluate to false\\.$#" count: 1 path: src/Altair/Http/Collection/HttpStatusCollection.php - - - message: "#^Method Altair\\\\Http\\\\Collection\\\\HttpStatusCollection\\:\\:getIterator\\(\\) return type with generic class ArrayIterator does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Http/Collection/HttpStatusCollection.php - - message: "#^Method Altair\\\\Http\\\\Collection\\\\HttpStatusCollection\\:\\:getResponseClass\\(\\) should return string but returns int\\.$#" count: 1 @@ -290,26 +160,6 @@ parameters: count: 1 path: src/Altair/Http/Collection/HttpStatusCollection.php - - - message: "#^Class Altair\\\\Http\\\\Collection\\\\InputCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Http/Collection/InputCollection.php - - - - message: "#^Class Altair\\\\Http\\\\Collection\\\\MiddlewareCollection extends generic class Altair\\\\Structure\\\\Queue but does not specify its types\\: TValue$#" - count: 1 - path: src/Altair/Http/Collection/MiddlewareCollection.php - - - - message: "#^Class Altair\\\\Http\\\\Collection\\\\RouteCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Http/Collection/RouteCollection.php - - - - message: "#^Class Altair\\\\Http\\\\Collection\\\\SettingsCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Http/Collection/SettingsCollection.php - - message: "#^Class Lcobucci\\\\Jose\\\\Parsing\\\\Decoder not found\\.$#" count: 1 @@ -405,51 +255,16 @@ parameters: count: 1 path: src/Altair/Http/Support/FormatNegotiator.php - - - message: "#^Method Altair\\\\Messaging\\\\Configuration\\\\MessengerConfiguration\\:\\:collectTransportFactories\\(\\) return type with generic interface Symfony\\\\Component\\\\Messenger\\\\Transport\\\\TransportFactoryInterface does not specify its types\\: TTransport$#" - count: 1 - path: src/Altair/Messaging/Configuration/MessengerConfiguration.php - - - - message: "#^PHPDoc tag @var for variable \\$instance contains generic interface Symfony\\\\Component\\\\Messenger\\\\Transport\\\\TransportFactoryInterface but does not specify its types\\: TTransport$#" - count: 1 - path: src/Altair/Messaging/Configuration/MessengerConfiguration.php - - - - message: "#^Method Altair\\\\Messaging\\\\Transport\\\\TransportRegistry\\:\\:__construct\\(\\) has parameter \\$factory with generic interface Symfony\\\\Component\\\\Messenger\\\\Transport\\\\TransportFactoryInterface but does not specify its types\\: TTransport$#" - count: 1 - path: src/Altair/Messaging/Transport/TransportRegistry.php - - - - message: "#^Method Altair\\\\Middleware\\\\Runner\\:\\:__construct\\(\\) has parameter \\$queue with generic class Altair\\\\Structure\\\\Queue but does not specify its types\\: TValue$#" - count: 1 - path: src/Altair/Middleware/Runner.php - - - - message: "#^Property Altair\\\\Persistence\\\\Cycle\\\\CycleRepository\\:\\:\\$repository with generic interface Cycle\\\\ORM\\\\RepositoryInterface does not specify its types\\: TEntity$#" - count: 1 - path: src/Altair/Persistence/Cycle/CycleRepository.php - - message: "#^Unable to resolve the template type TEntity in call to method Cycle\\\\ORM\\\\ORMInterface\\:\\:getRepository\\(\\)$#" count: 1 path: src/Altair/Persistence/Cycle/CycleRepository.php - - - message: "#^Class Altair\\\\Sanitation\\\\Collection\\\\FilterCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Sanitation/Collection/FilterCollection.php - - message: "#^PHPDoc tag @return with type mixed is not subtype of native type int\\|null\\.$#" count: 1 path: src/Altair/Sanitation/Filter/IntegerFilter.php - - - message: "#^Method Altair\\\\Sanitation\\\\FiltersRunner\\:\\:__construct\\(\\) has parameter \\$queue with generic class Altair\\\\Structure\\\\Queue but does not specify its types\\: TValue$#" - count: 1 - path: src/Altair/Sanitation/FiltersRunner.php - - message: "#^PHPDoc tag @param for parameter \\$resolver with type callable is not subtype of native type Altair\\\\Sanitation\\\\Contracts\\\\ResolverInterface\\|null\\.$#" count: 1 @@ -520,11 +335,6 @@ parameters: count: 2 path: src/Altair/Structure/Stack.php - - - message: "#^Class Altair\\\\Validation\\\\Collection\\\\RuleCollection extends generic class Altair\\\\Structure\\\\Map but does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Altair/Validation/Collection/RuleCollection.php - - message: "#^Binary operation \"\\*\\=\" between non\\-empty\\-string and 2 results in an error\\.$#" count: 1 @@ -540,11 +350,6 @@ parameters: count: 1 path: src/Altair/Validation/Rule/IsbnRule.php - - - message: "#^Method Altair\\\\Validation\\\\RulesRunner\\:\\:__construct\\(\\) has parameter \\$queue with generic class Altair\\\\Structure\\\\Queue but does not specify its types\\: TValue$#" - count: 1 - path: src/Altair/Validation/RulesRunner.php - - message: "#^PHPDoc tag @param for parameter \\$resolver with type callable is not subtype of native type Altair\\\\Validation\\\\Contracts\\\\ResolverInterface\\|null\\.$#" count: 1 diff --git a/src/Altair/Configuration/Collection/ConfigurationCollection.php b/src/Altair/Configuration/Collection/ConfigurationCollection.php index cd96fd6c..98057b25 100644 --- a/src/Altair/Configuration/Collection/ConfigurationCollection.php +++ b/src/Altair/Configuration/Collection/ConfigurationCollection.php @@ -17,6 +17,9 @@ use Altair\Structure\Set; use Override; +/** + * @extends Set + */ class ConfigurationCollection extends Set implements ConfigurationInterface { /** diff --git a/src/Altair/Container/Collection/AliasesCollection.php b/src/Altair/Container/Collection/AliasesCollection.php index cdf87f64..e7050a72 100644 --- a/src/Altair/Container/Collection/AliasesCollection.php +++ b/src/Altair/Container/Collection/AliasesCollection.php @@ -15,6 +15,9 @@ use Altair\Container\Traits\NameNormalizerTrait; use Altair\Structure\Map; +/** + * @extends Map + */ class AliasesCollection extends Map { use NameNormalizerTrait; diff --git a/src/Altair/Container/Collection/ClassDefinitionsCollection.php b/src/Altair/Container/Collection/ClassDefinitionsCollection.php index f1e133e4..6540b30a 100644 --- a/src/Altair/Container/Collection/ClassDefinitionsCollection.php +++ b/src/Altair/Container/Collection/ClassDefinitionsCollection.php @@ -11,6 +11,10 @@ namespace Altair\Container\Collection; +use Altair\Container\Definition; use Altair\Structure\Map; +/** + * @extends Map + */ class ClassDefinitionsCollection extends Map {} diff --git a/src/Altair/Container/Collection/DelegatesCollection.php b/src/Altair/Container/Collection/DelegatesCollection.php index 4abc78af..e510ce29 100644 --- a/src/Altair/Container/Collection/DelegatesCollection.php +++ b/src/Altair/Container/Collection/DelegatesCollection.php @@ -13,4 +13,7 @@ use Altair\Structure\Map; +/** + * @extends Map + */ class DelegatesCollection extends Map {} diff --git a/src/Altair/Container/Collection/ParameterDefinitionsCollection.php b/src/Altair/Container/Collection/ParameterDefinitionsCollection.php index 3e081bbe..9376bf71 100644 --- a/src/Altair/Container/Collection/ParameterDefinitionsCollection.php +++ b/src/Altair/Container/Collection/ParameterDefinitionsCollection.php @@ -13,4 +13,7 @@ use Altair\Structure\Map; +/** + * @extends Map + */ class ParameterDefinitionsCollection extends Map {} diff --git a/src/Altair/Container/Collection/PreparesCollection.php b/src/Altair/Container/Collection/PreparesCollection.php index 7ffdcbc8..d61c0515 100644 --- a/src/Altair/Container/Collection/PreparesCollection.php +++ b/src/Altair/Container/Collection/PreparesCollection.php @@ -13,4 +13,7 @@ use Altair\Structure\Map; +/** + * @extends Map + */ class PreparesCollection extends Map {} diff --git a/src/Altair/Container/Collection/SharesCollection.php b/src/Altair/Container/Collection/SharesCollection.php index 2282baec..4bf246ad 100644 --- a/src/Altair/Container/Collection/SharesCollection.php +++ b/src/Altair/Container/Collection/SharesCollection.php @@ -16,10 +16,16 @@ use Altair\Structure\Contracts\MapInterface; use Altair\Structure\Map; +/** + * @extends Map + */ class SharesCollection extends Map { use NameNormalizerTrait; + /** + * @return MapInterface + */ public function shareClass(string $name, AliasesCollection $aliasesCollection): MapInterface { [, $normalizedName] = $aliasesCollection->resolve($name); @@ -27,6 +33,9 @@ public function shareClass(string $name, AliasesCollection $aliasesCollection): return $this->put($normalizedName, $this[$normalizedName] ?? null); } + /** + * @return MapInterface + */ public function shareInstance(object $instance, AliasesCollection $aliasesCollection): MapInterface { $normalizedName = $this->normalizeName($instance::class); diff --git a/src/Altair/Cookie/Collection/CookieCollection.php b/src/Altair/Cookie/Collection/CookieCollection.php index 62d18a83..28dd3238 100644 --- a/src/Altair/Cookie/Collection/CookieCollection.php +++ b/src/Altair/Cookie/Collection/CookieCollection.php @@ -116,7 +116,7 @@ public function sort(?callable $comparator = null): MapInterface /** * {@inheritDoc} * - * @return VectorInterface + * @return VectorInterface */ #[Override] public function values(): VectorInterface @@ -169,7 +169,7 @@ protected function lookupValue($value): ?PairInterface /** * Converts pairs to array. * - * @param array $pairs + * @param array> $pairs * * @return array */ @@ -177,7 +177,7 @@ protected function lookupValue($value): ?PairInterface protected function pairsToArray($pairs): array { $array = []; - /** @var Pair $pair */ + /** @var Pair $pair */ foreach ($pairs as $pair) { $array[$pair->key] = (string) $pair->value; } diff --git a/src/Altair/Cookie/Collection/SetCookieCollection.php b/src/Altair/Cookie/Collection/SetCookieCollection.php index 8caca4f2..1e3b118c 100644 --- a/src/Altair/Cookie/Collection/SetCookieCollection.php +++ b/src/Altair/Cookie/Collection/SetCookieCollection.php @@ -116,7 +116,7 @@ public function sort(?callable $comparator = null): MapInterface /** * {@inheritDoc} * - * @return VectorInterface + * @return VectorInterface */ #[Override] public function values(): VectorInterface @@ -174,7 +174,7 @@ protected function lookupValue($value): ?PairInterface /** * Converts pairs to array. * - * @param array $pairs + * @param array> $pairs * * @return array */ @@ -182,7 +182,7 @@ protected function lookupValue($value): ?PairInterface protected function pairsToArray($pairs): array { $array = []; - /** @var Pair $pair */ + /** @var Pair $pair */ foreach ($pairs as $pair) { $array[$pair->key] = (string) $pair->value; } diff --git a/src/Altair/Courier/Support/MessageCommandMap.php b/src/Altair/Courier/Support/MessageCommandMap.php index d0278fa3..99420907 100644 --- a/src/Altair/Courier/Support/MessageCommandMap.php +++ b/src/Altair/Courier/Support/MessageCommandMap.php @@ -11,6 +11,10 @@ namespace Altair\Courier\Support; +use Altair\Courier\Contracts\CommandInterface; use Altair\Structure\Map; +/** + * @extends Map|CommandInterface> + */ class MessageCommandMap extends Map {} diff --git a/src/Altair/Http/Collection/HttpStatusCollection.php b/src/Altair/Http/Collection/HttpStatusCollection.php index 31fb20bb..e53015d4 100644 --- a/src/Altair/Http/Collection/HttpStatusCollection.php +++ b/src/Altair/Http/Collection/HttpStatusCollection.php @@ -22,6 +22,9 @@ use ReflectionClass; use Traversable; +/** + * @implements IteratorAggregate + */ class HttpStatusCollection implements Countable, IteratorAggregate { /** @@ -53,6 +56,8 @@ public function count(): int /** * {@inheritDoc} + * + * @return ArrayIterator */ #[Override] public function getIterator(): ArrayIterator diff --git a/src/Altair/Http/Collection/InputCollection.php b/src/Altair/Http/Collection/InputCollection.php index 3c128372..78582f93 100644 --- a/src/Altair/Http/Collection/InputCollection.php +++ b/src/Altair/Http/Collection/InputCollection.php @@ -13,4 +13,9 @@ use Altair\Structure\Map; +/** + * Maps a request input name (attribute, body, cookie, query or upload key) to its value. + * + * @extends Map + */ class InputCollection extends Map {} diff --git a/src/Altair/Http/Collection/MiddlewareCollection.php b/src/Altair/Http/Collection/MiddlewareCollection.php index c6171a33..e3251c3c 100644 --- a/src/Altair/Http/Collection/MiddlewareCollection.php +++ b/src/Altair/Http/Collection/MiddlewareCollection.php @@ -13,4 +13,9 @@ use Altair\Structure\Queue; +/** + * An ordered queue of PSR-15 middleware references (FQCN string, instance, or [class, method] pair). + * + * @extends Queue + */ class MiddlewareCollection extends Queue {} diff --git a/src/Altair/Http/Collection/RouteCollection.php b/src/Altair/Http/Collection/RouteCollection.php index 4085dd35..f0693fc3 100644 --- a/src/Altair/Http/Collection/RouteCollection.php +++ b/src/Altair/Http/Collection/RouteCollection.php @@ -13,4 +13,9 @@ use Altair\Structure\Map; +/** + * Maps a "METHOD /path" route key to its handler reference (an action FQCN). + * + * @extends Map + */ class RouteCollection extends Map {} diff --git a/src/Altair/Http/Collection/SettingsCollection.php b/src/Altair/Http/Collection/SettingsCollection.php index 5116e905..facc8ec5 100644 --- a/src/Altair/Http/Collection/SettingsCollection.php +++ b/src/Altair/Http/Collection/SettingsCollection.php @@ -13,4 +13,9 @@ use Altair\Structure\Map; +/** + * Maps a setting name to its value. + * + * @extends Map + */ class SettingsCollection extends Map {} diff --git a/src/Altair/Introspection/Inspector/ConfigInspector.php b/src/Altair/Introspection/Inspector/ConfigInspector.php index 10d85c9f..d0cf3af6 100644 --- a/src/Altair/Introspection/Inspector/ConfigInspector.php +++ b/src/Altair/Introspection/Inspector/ConfigInspector.php @@ -60,7 +60,7 @@ public function dump(bool $maskSecrets = true): InspectionTable $rows[] = [ 'source' => 'container', 'key' => '$' . $name, - 'value' => $this->renderValue((string) $name, $value, $maskSecrets, $patterns), + 'value' => $this->renderValue($name, $value, $maskSecrets, $patterns), ]; } diff --git a/src/Altair/Introspection/Inspector/ContainerInspector.php b/src/Altair/Introspection/Inspector/ContainerInspector.php index 0cf80528..3149e11a 100644 --- a/src/Altair/Introspection/Inspector/ContainerInspector.php +++ b/src/Altair/Introspection/Inspector/ContainerInspector.php @@ -98,7 +98,7 @@ public function inspectRealized(?string $filter = null): InspectionTable continue; // null placeholder — registered but not yet built. } - $id = $this->displayName((string) $name); + $id = $this->displayName($name); if ($needle !== null && !str_contains(strtolower($id), $needle)) { continue; @@ -136,7 +136,7 @@ public function inspectOne(string $id): InspectionTable // Container collections are keyed by lower-cased class names — match the same normalization. $lookupKey = strtolower(ltrim($id, '\\')); - $aliasTarget = isset($aliases[$lookupKey]) ? (string) $aliases[$lookupKey] : null; + $aliasTarget = $aliases[$lookupKey] ?? null; $resolved = $aliasTarget ?? ltrim($id, '\\'); $kind = match (true) { @@ -189,7 +189,7 @@ public function collectBindings(): iterable $seen = []; foreach ($aliases as $original => $target) { - $normalized = (string) $original; + $normalized = $original; $seen[$normalized] = true; // An alias's `shared` flag reflects whether the alias name // itself is registered as a singleton — which is uncommon. @@ -200,13 +200,13 @@ public function collectBindings(): iterable yield [ 'id' => $this->displayName($normalized), 'kind' => 'alias', - 'target' => (string) $target, + 'target' => $target, 'shared' => $shares->hasKey($normalized), ]; } foreach ($shares as $name => $_) { - $normalized = (string) $name; + $normalized = $name; if (isset($seen[$normalized])) { continue; } @@ -222,7 +222,7 @@ public function collectBindings(): iterable } foreach ($delegates as $name => $_) { - $normalized = (string) $name; + $normalized = $name; if (isset($seen[$normalized])) { continue; } @@ -238,7 +238,7 @@ public function collectBindings(): iterable } foreach ($classDefinitions as $name => $_) { - $normalized = (string) $name; + $normalized = $name; if (isset($seen[$normalized])) { continue; } diff --git a/src/Altair/Introspection/Inspector/RouteInspector.php b/src/Altair/Introspection/Inspector/RouteInspector.php index 7f5cfdee..69e3f34d 100644 --- a/src/Altair/Introspection/Inspector/RouteInspector.php +++ b/src/Altair/Introspection/Inspector/RouteInspector.php @@ -33,7 +33,7 @@ public function inspectAll(): InspectionTable { $rows = []; foreach ($this->routes as $request => $action) { - [$method, $path] = $this->splitRequest((string) $request); + [$method, $path] = $this->splitRequest($request); $rows[] = [ 'method' => $method, 'path' => $path, @@ -63,7 +63,7 @@ public function inspectOne(string $path): InspectionTable { $matches = []; foreach ($this->routes as $request => $action) { - [$method, $registeredPath] = $this->splitRequest((string) $request); + [$method, $registeredPath] = $this->splitRequest($request); if ($registeredPath === $path) { $matches[] = [ 'method' => $method, diff --git a/src/Altair/Messaging/Configuration/MessengerConfiguration.php b/src/Altair/Messaging/Configuration/MessengerConfiguration.php index 4d25b038..9e90c8c8 100644 --- a/src/Altair/Messaging/Configuration/MessengerConfiguration.php +++ b/src/Altair/Messaging/Configuration/MessengerConfiguration.php @@ -40,6 +40,7 @@ use Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory; use Symfony\Component\Messenger\Transport\TransportFactory; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; /** * Wires Symfony Messenger into the Altair Container. @@ -171,7 +172,7 @@ public function apply(Container $container): void } /** - * @return list + * @return list> */ private static function collectTransportFactories(LazyBus $lazyBus): array { @@ -190,7 +191,7 @@ private static function collectTransportFactories(LazyBus $lazyBus): array foreach ($optional as $class) { if (class_exists($class)) { - /** @var TransportFactoryInterface $instance */ + /** @var TransportFactoryInterface $instance */ $instance = new $class(); $factories[] = $instance; } diff --git a/src/Altair/Messaging/Transport/TransportRegistry.php b/src/Altair/Messaging/Transport/TransportRegistry.php index 51912f64..6db33edd 100644 --- a/src/Altair/Messaging/Transport/TransportRegistry.php +++ b/src/Altair/Messaging/Transport/TransportRegistry.php @@ -26,6 +26,9 @@ final class TransportRegistry /** @var array */ private array $cache = []; + /** + * @param TransportFactoryInterface $factory + */ public function __construct( private readonly TransportSettings $settings, private readonly TransportFactoryInterface $factory, diff --git a/src/Altair/Middleware/Runner.php b/src/Altair/Middleware/Runner.php index db82e240..1a2a6a50 100644 --- a/src/Altair/Middleware/Runner.php +++ b/src/Altair/Middleware/Runner.php @@ -33,7 +33,7 @@ class Runner implements MiddlewareRunnerInterface * * Constructor. * - * @param Queue $queue The middleware queue. + * @param Queue $queue The middleware queue. * * @param callable|MiddlewareResolverInterface $resolver Converts queue entries to callables. * diff --git a/src/Altair/Persistence/Cycle/CycleRepository.php b/src/Altair/Persistence/Cycle/CycleRepository.php index cf805dc7..e22ddb48 100644 --- a/src/Altair/Persistence/Cycle/CycleRepository.php +++ b/src/Altair/Persistence/Cycle/CycleRepository.php @@ -37,6 +37,7 @@ */ class CycleRepository implements RepositoryInterface { + /** @var CycleNativeRepository */ private readonly CycleNativeRepository $repository; /** @@ -47,7 +48,9 @@ public function __construct( private readonly ORMInterface $orm, private readonly UnitOfWorkInterface $unitOfWork, ) { - $this->repository = $this->orm->getRepository($this->entityClass); + /** @var CycleNativeRepository $repository */ + $repository = $this->orm->getRepository($this->entityClass); + $this->repository = $repository; } #[Override] diff --git a/src/Altair/Sanitation/Collection/FilterCollection.php b/src/Altair/Sanitation/Collection/FilterCollection.php index 7f02262b..4df2fd1f 100644 --- a/src/Altair/Sanitation/Collection/FilterCollection.php +++ b/src/Altair/Sanitation/Collection/FilterCollection.php @@ -18,6 +18,9 @@ use Override; use Traversable; +/** + * @extends Map + */ class FilterCollection extends Map { /** diff --git a/src/Altair/Sanitation/FiltersRunner.php b/src/Altair/Sanitation/FiltersRunner.php index 26e4dcaa..93774f99 100644 --- a/src/Altair/Sanitation/FiltersRunner.php +++ b/src/Altair/Sanitation/FiltersRunner.php @@ -34,7 +34,7 @@ class FiltersRunner implements FiltersRunnerInterface * Constructor. * * @param callable|ResolverInterface $resolver Converts queue entries to callables. - * @param Queue $queue The middleware queue. + * @param Queue $queue The middleware queue. */ public function __construct(?ResolverInterface $resolver = null, protected ?Queue $queue = null) { diff --git a/src/Altair/Sanitation/Sanitizer.php b/src/Altair/Sanitation/Sanitizer.php index f131d5d0..6f55a9d1 100644 --- a/src/Altair/Sanitation/Sanitizer.php +++ b/src/Altair/Sanitation/Sanitizer.php @@ -40,7 +40,7 @@ public function sanitize(SanitizableInterface $sanitizable): SanitizableInterfac $this->payload = $this->buildPayload($sanitizable); foreach ($sanitizable->getFilters() as $key => $value) { - $keys = explode(',', (string) preg_replace('/\s+/', '', (string) $key)); + $keys = explode(',', (string) preg_replace('/\s+/', '', $key)); foreach ($keys as $attribute) { $filters = \is_array($value) ? $value : [$value]; $runner = $this->runner->withFilters($filters); diff --git a/src/Altair/Validation/Collection/RuleCollection.php b/src/Altair/Validation/Collection/RuleCollection.php index 545c457c..3a0bdf6e 100644 --- a/src/Altair/Validation/Collection/RuleCollection.php +++ b/src/Altair/Validation/Collection/RuleCollection.php @@ -18,6 +18,9 @@ use Override; use Traversable; +/** + * @extends Map + */ class RuleCollection extends Map { /** diff --git a/src/Altair/Validation/RulesRunner.php b/src/Altair/Validation/RulesRunner.php index a16e7f2d..4bbfa0a2 100644 --- a/src/Altair/Validation/RulesRunner.php +++ b/src/Altair/Validation/RulesRunner.php @@ -34,7 +34,7 @@ class RulesRunner implements RulesRunnerInterface * Constructor. * * @param callable|ResolverInterface $resolver Converts queue entries to callables. - * @param Queue $queue The middleware queue. + * @param Queue $queue The middleware queue. */ public function __construct(?ResolverInterface $resolver = null, protected ?Queue $queue = null) {