Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 6
level: 7
phpVersion: 80300
paths:
- src
Expand Down Expand Up @@ -42,3 +42,22 @@ parameters:
- identifier: argument.templateType
paths:
- src/Altair/Persistence/Cycle/CycleRepository.php

# CollectionTrait is shared by array-backed collections (Map, Vector, Deque) and
# delegate-backed adapters (Set->Map, Stack->Vector, Queue->Deque). The adapters
# narrow $internal to a delegate object and override the entry points, but PHP's
# trait rules force them to keep the trait's `$internal = []` default, and PHPStan
# analyses the trait's array-backed storage per using-class. These are artefacts of
# that intentional shared-trait design, not reachable bugs.
- identifier: property.defaultValue
paths:
- src/Altair/Structure/Traits/CollectionTrait.php
- src/Altair/Structure/Set.php
- src/Altair/Structure/Stack.php
- src/Altair/Structure/Queue.php
- identifier: offsetAssign.dimType
paths:
- src/Altair/Structure/Traits/CollectionTrait.php
-
message: '#^Call to an undefined method [^:]+::adjustCapacity\(\)\.$#'
path: src/Altair/Structure/Traits/CollectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public function render(array $paths): string
}

/**
* @param class-string $fqcn
* @return list<class-string>
*/
private function matchedAttributes(string $fqcn): array
Expand Down
1 change: 1 addition & 0 deletions src/Altair/AgentSpec/Reflection/AttributeScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public function scan(PackageDescriptor $package): array
}

/**
* @param class-string $fqcn
* @return list<AttributeConvention>
*/
private function collect(string $fqcn): array
Expand Down
2 changes: 1 addition & 1 deletion src/Altair/AgentSpec/Reflection/ClassNameExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function extract(string $filePath): array
return [];
}

$tokens = PhpToken::tokenize($code);
$tokens = array_values(PhpToken::tokenize($code));
$namespace = '';
$classes = [];
$tokenCount = \count($tokens);
Expand Down
3 changes: 3 additions & 0 deletions src/Altair/AgentSpec/Reflection/ConcreteClassScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ private function isSkipped(string $file, string $sourceRoot): bool
return \in_array($segment, self::SKIP_DIRECTORIES, true);
}

/**
* @param class-string $fqcn
*/
private function describeClass(string $fqcn, string $file, PackageDescriptor $package): ClassEntry
{
$reflection = new ReflectionClass($fqcn);
Expand Down
3 changes: 3 additions & 0 deletions src/Altair/AgentSpec/Reflection/ContractScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public function scan(PackageDescriptor $package): array
return $entries;
}

/**
* @param class-string $fqcn
*/
private function describeInterface(string $fqcn): ContractEntry
{
$reflection = new ReflectionClass($fqcn);
Expand Down
5 changes: 5 additions & 0 deletions src/Altair/AgentSpec/Reflection/PackageScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Altair\AgentSpec\Model\PackageDescriptor;
use FilesystemIterator;
use Override;
use SplFileInfo;

/**
* Discovers sub-packages by looking for composer.json files inside the
Expand All @@ -32,6 +33,10 @@ public function scan(string $sourceRoot, string $monorepoRoot, ?string $testsRoo

$descriptors = [];
foreach (new FilesystemIterator($sourceRoot, FilesystemIterator::SKIP_DOTS) as $entry) {
if (!$entry instanceof SplFileInfo) {
continue;
}

if (!$entry->isDir()) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Altair/Cache/CacheItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function expiresAfter(int|DateInterval|null $time): static
}

if ($time instanceof DateInterval) {
$this->expirationTime = (int) DateTime::createFromFormat('Y-m-d H:i:s', date('Y-m-d H:i:s'))
$this->expirationTime = (int) (new DateTime())
->add($time)
->format('U');

Expand Down
4 changes: 2 additions & 2 deletions src/Altair/Cache/CacheItemPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public function commit(): bool
}
} else { // retry
foreach (array_keys($values) as $id) {
$retry[$lifespan][] = $id;
$retry[$lifespan][] = (string) $id;
}
}
}
Expand Down Expand Up @@ -324,7 +324,7 @@ protected function ensureCommitDeferred(): void
*
* @return Generator<string, CacheItemInterface>
*/
protected function createCacheItemsGenerator(array $items, array $keys): ?Generator
protected function createCacheItemsGenerator(array $items, array $keys): Generator
{
try {
foreach ($items as $id => $value) {
Expand Down
3 changes: 2 additions & 1 deletion src/Altair/Cache/Storage/MemcachedCacheItemStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class MemcachedCacheItemStorage implements CacheItemStorageInterface
*/
public function __construct(Memcached $memcached)
{
if (!(\extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>='))) {
$version = phpversion('memcached');
if (!\extension_loaded('memcached') || $version === false || !version_compare($version, '2.2.0', '>=')) {
throw new CacheException('Memcached >= 2.2.0 is required.');
}

Expand Down
2 changes: 1 addition & 1 deletion src/Altair/Cli/Discovery/AttributeCommandDiscoverer.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private function extractClasses(string $filePath): array
return [];
}

$tokens = PhpToken::tokenize($code);
$tokens = array_values(PhpToken::tokenize($code));

$namespace = '';
$classes = [];
Expand Down
43 changes: 30 additions & 13 deletions src/Altair/Common/Support/Arr.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,24 @@ public static function getValue(mixed $array, $key, mixed $default = null)
$array = static::getValue($array, $keyPart);
}

if ($lastKey === null) {
return $array;
}

$key = $lastKey;
}

if ($key instanceof Closure) {
return $key($array, $default);
}

if (\is_array($array) && (isset($array[$key]) || \array_key_exists($key, $array))) {
return $array[$key];
}

if (($pos = strrpos((string) $key, '.')) !== false) {
$array = static::getValue($array, substr((string) $key, 0, $pos), $default);
$key = substr((string) $key, $pos + 1);
if (($pos = strrpos($key, '.')) !== false) {
$array = static::getValue($array, substr($key, 0, $pos), $default);
$key = substr($key, $pos + 1);
}

if (\is_object($array)) {
Expand Down Expand Up @@ -497,21 +505,30 @@ public static function multisort(array &$array, $key, $direction = SORT_ASC, $so
throw new InvalidArgumentException('The length of $sortFlag parameter must be the same as that of $keys.');
}

$args = [];
// The first sort column is passed as the leading positional argument so the
// call statically satisfies array_multisort()'s "first argument is an array"
// contract; the remaining direction/flag/column triples are spread after it.
$firstColumn = null;
$rest = [];
foreach ($keys as $i => $k) {
$flag = $sortFlag[$i];
$args[] = static::getColumn($array, $k);
$args[] = $direction[$i];
$args[] = $flag;
$column = static::getColumn($array, $k);
if ($firstColumn === null) {
$firstColumn = $column;
} else {
$rest[] = $column;
}

$rest[] = $direction[$i];
$rest[] = $sortFlag[$i];
}

// This fix is used for cases when main sorting specified by columns has equal values
// Without it it will lead to Fatal Error: Nesting level too deep - recursive dependency?
$args[] = range(1, \count($array));
$args[] = SORT_ASC;
$args[] = SORT_NUMERIC;
$args[] = &$array;
array_multisort(...$args);
$rest[] = range(1, \count($array));
$rest[] = SORT_ASC;
$rest[] = SORT_NUMERIC;
$rest[] = &$array;
array_multisort($firstColumn, ...$rest);
}

/**
Expand Down
14 changes: 9 additions & 5 deletions src/Altair/Common/Support/Inflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ public function __construct(protected Transliterator $transliterator, protected
public function slug(string $value, ?string $replacement = null, $lowercase = true): string
{
$replacement ??= '-';
$value = $this->transliterator->transliterate($value);
$value = preg_replace('/[^a-zA-Z0-9=\s—–-]+/u', '', $value);
$value = preg_replace('/[=\s—–-]+/u', $replacement, (string) $value);
$value = trim((string) $value, $replacement);
$transliterated = $this->transliterator->transliterate($value);
if ($transliterated !== false) {
$value = $transliterated;
}

$value = (string) preg_replace('/[^a-zA-Z0-9=\s—–-]+/u', '', $value);
$value = (string) preg_replace('/[=\s—–-]+/u', $replacement, $value);
$value = trim($value, $replacement);

return $lowercase ? strtolower($value) : $value;
}
Expand All @@ -49,7 +53,7 @@ public function slug(string $value, ?string $replacement = null, $lowercase = tr
* For example, 'post-tag' is converted to 'PostTag'.
*
* @param string $id the id to be converted
* @param string $separator the character used to separate the words in the id
* @param non-empty-string $separator the character used to separate the words in the id
*/
public function idToCamel(string $id, string $separator = '-'): string
{
Expand Down
7 changes: 6 additions & 1 deletion src/Altair/Common/Support/Str.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public function truncate(string $value, int $length, string $suffix = '...', str
public function truncateWords(string $value, int $count, string $suffix = '...'): string
{
$words = preg_split('/(\s+)/u', trim($value), -1, PREG_SPLIT_DELIM_CAPTURE);
if ($words === false) {
return $value;
}

return \count($words) / 2 > $count
? implode('', \array_slice($words, 0, ($count * 2) - 1)) . $suffix
Expand Down Expand Up @@ -123,7 +126,9 @@ public function endsWith(
*/
public function countWords(string $value): int
{
return \count(preg_split('/\s+/u', $value, -1, PREG_SPLIT_NO_EMPTY));
$words = preg_split('/\s+/u', $value, -1, PREG_SPLIT_NO_EMPTY);

return $words === false ? 0 : \count($words);
}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/Altair/Container/Builder/ExecutableBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ protected function buildExecutableStructureFromClassMethodCallable(string $class

if ($relativeStaticMethodStartPos === 0) {
$childReflection = $this->container->getReflector()->getClass($class);
$class = $childReflection->getParentClass()->name;
$parentReflection = $childReflection->getParentClass();
if ($parentReflection === false) {
throw new InjectionException(\sprintf('Class "%s" has no parent class.', $class));
}

$class = $parentReflection->name;
$method = substr($method, 8);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Altair/Container/Executable.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ protected function setMethodCallable(ReflectionMethod $reflection, mixed $object
}

/**
* @param array<int, mixed> $args
* @param array<int|string, mixed> $args
*/
protected function invokeClosure(ReflectionFunction $reflection, array $args): mixed
{
Expand Down
4 changes: 4 additions & 0 deletions src/Altair/Container/Reflection/StandardReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class StandardReflection implements ReflectionInterface
#[Override]
public function getClass(string $class): ReflectionClass
{
if (!class_exists($class) && !interface_exists($class)) {
throw new ReflectionException(\sprintf('Class "%s" does not exist.', $class));
}

return new ReflectionClass($class);
}

Expand Down
7 changes: 6 additions & 1 deletion src/Altair/Cookie/Support/CookieStr.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ class CookieStr
*/
public function split(string $value): array
{
return array_filter(preg_split('@\s*[;]\s*@', $value));
$parts = preg_split('@\s*[;]\s*@', $value);
if ($parts === false) {
return [];
}

return array_values(array_filter($parts));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function withMap(MessageCommandMap $map): InMemoryCommandLocatorServiceIn
/**
* Adds a new message to command mapping.
*
*
* @param class-string<CommandInterface> $commandName
*/
public function add(string $messageName, string $commandName): InMemoryCommandLocatorServiceInterface;
}
2 changes: 2 additions & 0 deletions src/Altair/Courier/Service/InMemoryCommandLocatorService.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public function withMap(MessageCommandMap $map): InMemoryCommandLocatorServiceIn

/**
* @inheritDoc
*
* @param class-string<CommandInterface> $commandName
*/
#[Override]
public function add(string $messageName, string $commandName): InMemoryCommandLocatorServiceInterface
Expand Down
27 changes: 25 additions & 2 deletions src/Altair/Courier/Strategy/CommandRunnerMiddlewareStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public function __construct(?array $middlewares = null, protected ?MiddlewareRes
public function withMiddlewares(array $middlewares): CommandRunnerStrategyInterface
{
foreach ($middlewares as $middleware) {
if ($middleware instanceof CommandMiddlewareInterface) {
continue;
}

if (!is_subclass_of($middleware, CommandMiddlewareInterface::class)) {
throw new InvalidCommandMiddlewareException(
\sprintf(
Expand Down Expand Up @@ -93,11 +97,30 @@ protected function call(int $index): Closure
$middleware = $this->middlewares[$index];

if ($this->resolver instanceof MiddlewareResolverInterface) {
$middleware = \call_user_func($this->resolver, $middleware);
$resolved = \call_user_func($this->resolver, $middleware);
if (!$resolved instanceof CommandMiddlewareInterface) {
throw new InvalidCommandMiddlewareException(
\sprintf(
'Resolved command middleware must implement %s',
CommandMiddlewareInterface::class
)
);
}

$middleware = $resolved;
$this->middlewares[$index] = $middleware;
}

return function ($message) use ($middleware, $index): void {
if (!$middleware instanceof CommandMiddlewareInterface) {
throw new InvalidCommandMiddlewareException(
\sprintf(
'Command middleware must be resolved to an instance of %s before execution',
CommandMiddlewareInterface::class
)
);
}

return function (CommandMessageInterface $message) use ($middleware, $index): void {
$middleware->handle($message, $this->call($index + 1));
};
}
Expand Down
7 changes: 5 additions & 2 deletions src/Altair/Events/Cli/ShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ public function __invoke(

echo $this->renderer->eventDetailHuman($event);
if ($snapshot !== null) {
echo "snapshot:\n";
echo " " . str_replace("\n", "\n ", json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)), "\n";
$encoded = json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($encoded !== false) {
echo "snapshot:\n";
echo " " . str_replace("\n", "\n ", $encoded), "\n";
}
}

return 0;
Expand Down
Loading
Loading