keyed by DiviFeature value, in DiviFeature::cases() order */ private array $features = []; /** @param array $features keyed by DiviFeature value */ public function __construct(array $features) { foreach (DiviFeature::cases() as $feature) { $status = $features[$feature->value] ?? null; $this->features[$feature->value] = $status instanceof FeatureStatus && $status->feature === $feature ? $status : new FeatureStatus($feature, ReleaseState::Incompatible, [self::MISSING]); } } public static function allReleased(string $source = ReleaseStatus::SOURCE_CODE): self { return self::uniform(ReleaseState::Released, [], $source); } public static function allIncompatible(string $problem): self { return self::uniform(ReleaseState::Incompatible, [$problem]); } /** * Every feature in $state – the matrix of a status built without one (ReleaseStatus' constructor). * * @param list $problems */ public static function uniform(ReleaseState $state, array $problems = [], ?string $source = null): self { $released = $state === ReleaseState::Released; $features = []; foreach (DiviFeature::cases() as $feature) { $features[$feature->value] = new FeatureStatus( $feature, $state, $released ? [] : $problems, $released ? ($source ?? ReleaseStatus::SOURCE_CODE) : null ); } return new self($features); } public function of(DiviFeature $feature): FeatureStatus { return $this->features[$feature->value]; } /** @param list $features */ public function usable(array $features): bool { foreach ($features as $feature) { if (! $this->of($feature)->usable()) { return false; } } return true; } /** * The statuses of those of $features that are not usable – each once, in the order of $features. The one answer * to "what locks this call" for the gate (DiviFeatureGate), the adapters (ReleaseGate::assertFeatures()) and the * refusal (ReleaseStatus::refusalFor()). * * @param list $features * @return list */ public function locked(array $features): array { $locked = []; foreach ($features as $feature) { $status = $this->of($feature); if (! $status->usable()) { $locked[$feature->value] = $status; } } return array_values($locked); } /** @return list */ public function unusable(): array { return array_values(array_filter( $this->features, static fn (FeatureStatus $status): bool => ! $status->usable() )); } /** Incompatible before untested before released. */ public function state(): ReleaseState { $states = array_map(static fn (FeatureStatus $status): ReleaseState => $status->state, $this->features); return match (true) { in_array(ReleaseState::Incompatible, $states, true) => ReleaseState::Incompatible, in_array(ReleaseState::Untested, $states, true) => ReleaseState::Untested, default => ReleaseState::Released, }; } /** The common source of a fully released matrix; null when not all released or the sources differ. */ public function source(): ?string { $sources = array_unique(array_map( static fn (FeatureStatus $status): string => (string) $status->source, $this->features )); return $this->state() === ReleaseState::Released && count($sources) === 1 ? (string) reset($sources) : null; } /** @return list Every problem label once, in feature order. */ public function problems(): array { $problems = []; foreach ($this->features as $status) { $problems = array_merge($problems, $status->problems); } return array_values(array_unique($problems)); } /** @return list, source: ?string}> */ public function toArray(): array { return array_values(array_map(static fn (FeatureStatus $status): array => $status->toArray(), $this->features)); } }