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
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ When `validateFiles` is enabled:
> [!IMPORTANT]
> **Limitation:** This only validates **existing cached entries**. When you add **new attributes** to your codebase, you still need to manually refresh the cache:
> ```bash
> bin/cake attribute discover
> bin/cake attributes cache
> # or in code:
> $registry->clearCache();
> ```
Expand Down Expand Up @@ -304,7 +304,7 @@ Both approaches return the same singleton instance, ensuring consistent caching
The `AttributeRegistry` service provides several methods for finding attributes:

> [!NOTE]
> All query methods (`findByAttribute`, `findByClass`, `findByTargetType`) internally call `discover()`. The discovery result is cached after the first call, so subsequent queries within the same request are fast. When adding new attributes to your codebase, clear the cache using `$registry->clearCache()` or run `bin/cake attribute discover` to refresh the registry.
> All query methods (`findByAttribute`, `findByClass`, `findByTargetType`) internally call `discover()`. The discovery result is cached after the first call, so subsequent queries within the same request are fast. When adding new attributes to your codebase, clear the cache using `$registry->clearCache()` or run `bin/cake attributes cache` to refresh the registry.

#### Discover All Attributes

Expand Down Expand Up @@ -545,12 +545,12 @@ foreach ($routes as $routeInfo) {

The plugin provides three console commands for managing attributes:

### Discover Attributes
### Cache Attributes

Scan and cache all attributes:
Manage the attribute cache (clear and rebuild):

```bash
bin/cake attribute discover
bin/cake attributes cache
```

Output:
Expand All @@ -560,22 +560,42 @@ Discovering attributes...
Discovered 42 attributes in 0.234s
```

Options:
- `--no-clear` - Skip clearing the cache before discovering
- `--clear-only` - Only clear cache without discovering attributes
- `--validate` - Validate cache integrity after building

Example with validation:

```bash
bin/cake attributes cache --validate
```

Output:
```
Clearing attribute cache...
Discovering attributes...
Discovered 42 attributes in 0.234s
Validating cache integrity...
Cache validation passed: 42 attributes, 15 files
```

### List Attributes

List discovered attributes with optional filtering:

```bash
# List all attributes
bin/cake attribute list
bin/cake attributes list

# Filter by attribute name
bin/cake attribute list --attribute Route
bin/cake attributes list --attribute Route

# Filter by class name
bin/cake attribute list --class UserController
bin/cake attributes list --class UserController

# Filter by target type
bin/cake attribute list --type method
bin/cake attributes list --type method
```

Output:
Expand All @@ -599,11 +619,11 @@ View detailed information about specific attributes:

```bash
# Inspect by attribute name
bin/cake attribute inspect Route
bin/cake attributes inspect Route

# Inspect attributes on a specific class
bin/cake attribute inspect --class UserController
bin/cake attribute inspect -c UserController
bin/cake attributes inspect --class UserController
bin/cake attributes inspect -c UserController
```

Output:
Expand Down
16 changes: 8 additions & 8 deletions src/AttributeRegistryPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace AttributeRegistry;

use AttributeRegistry\Command\AttributeDiscoverCommand;
use AttributeRegistry\Command\AttributeInspectCommand;
use AttributeRegistry\Command\AttributeListCommand;
use AttributeRegistry\Command\AttributesCacheCommand;
use AttributeRegistry\Command\AttributesInspectCommand;
use AttributeRegistry\Command\AttributesListCommand;
use AttributeRegistry\Utility\ConfigMerger;
use Cake\Command\CacheClearallCommand;
use Cake\Console\CommandCollection;
Expand Down Expand Up @@ -97,9 +97,9 @@ function (RouteBuilder $builder): void {
*/
public function console(CommandCollection $commands): CommandCollection
{
$commands->add('attribute discover', AttributeDiscoverCommand::class);
$commands->add('attribute list', AttributeListCommand::class);
$commands->add('attribute inspect', AttributeInspectCommand::class);
$commands->add('attributes cache', AttributesCacheCommand::class);
$commands->add('attributes list', AttributesListCommand::class);
$commands->add('attributes inspect', AttributesInspectCommand::class);

return $commands;
}
Expand All @@ -124,8 +124,8 @@ public function events(EventManagerInterface $eventManager): EventManagerInterfa
// Clear AttributeRegistry cache when cache:clear_all is run
if ($command instanceof CacheClearallCommand) {
$command->executeCommand(
AttributeDiscoverCommand::class,
['--no-discover'],
AttributesCacheCommand::class,
['--clear-only'],
);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
namespace AttributeRegistry\Command;

use AttributeRegistry\AttributeRegistry;
use AttributeRegistry\Service\AttributeCacheValidator;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;

/**
* Command to discover and cache all attributes.
* Command to manage attribute cache.
*/
class AttributeDiscoverCommand extends Command
class AttributesCacheCommand extends Command
{
/**
* @param \AttributeRegistry\AttributeRegistry $registry Attribute registry
Expand All @@ -28,15 +29,15 @@ public function __construct(
*/
public static function defaultName(): string
{
return 'attribute discover';
return 'attributes cache';
}

/**
* Get the command description.
*/
public static function getDescription(): string
{
return 'Discover and cache all PHP attributes in the application.';
return 'Manage attribute cache (clear and rebuild).';
}

/**
Expand All @@ -45,16 +46,21 @@ public static function getDescription(): string
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser->setDescription(static::getDescription());
$parser->addOption('no-clear-cache', [
$parser->addOption('no-clear', [
'boolean' => true,
'default' => false,
'help' => 'Skip clearing the attribute cache before discovering',
'help' => 'Skip clearing the cache before discovering',
]);
$parser->addOption('no-discover', [
$parser->addOption('clear-only', [
'boolean' => true,
'default' => false,
'help' => 'Only clear cache without discovering attributes',
]);
$parser->addOption('validate', [
'boolean' => true,
'default' => false,
'help' => 'Validate cache integrity after building',
]);

return $parser;
}
Expand All @@ -68,14 +74,14 @@ public function execute(Arguments $args, ConsoleIo $io): int
$io->warning('Cache is disabled. Attributes will be re-discovered on every request.');
}

// Clear cache by default unless --no-clear-cache is set
if (!$args->getOption('no-clear-cache')) {
// Clear cache by default unless --no-clear is set
if (!$args->getOption('no-clear')) {
$io->out('<info>Clearing attribute cache...</info>');
$this->registry->clearCache();
}

// Only discover if --no-discover is not set
if (!$args->getOption('no-discover')) {
// Only discover if --clear-only is not set
if (!$args->getOption('clear-only')) {
$io->out('<info>Discovering attributes...</info>');

$startTime = microtime(true);
Expand All @@ -87,6 +93,28 @@ public function execute(Arguments $args, ConsoleIo $io): int
count($attributes),
$elapsed,
));

// Validate cache if --validate option is set
if ($args->getOption('validate')) {
$io->out('<info>Validating cache integrity...</info>');
$validator = new AttributeCacheValidator($this->registry);
$result = $validator->validate();

if ($result->valid) {
$io->success(sprintf(
'Cache validation passed: %d attributes, %d files',
$result->totalAttributes,
$result->totalFiles,
));
} else {
$io->error('Cache validation failed:');
foreach ($result->errors as $error) {
$io->err(' - ' . $error);
}

return static::CODE_ERROR;
}
}
}

return static::CODE_SUCCESS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* Command to inspect details of a specific attribute.
*/
class AttributeInspectCommand extends Command
class AttributesInspectCommand extends Command
{
/**
* @param \AttributeRegistry\AttributeRegistry $registry Attribute registry
Expand All @@ -31,7 +31,7 @@ public function __construct(
*/
public static function defaultName(): string
{
return 'attribute inspect';
return 'attributes inspect';
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/**
* Command to list discovered attributes.
*/
class AttributeListCommand extends Command
class AttributesListCommand extends Command
{
/**
* @param \AttributeRegistry\AttributeRegistry $registry Attribute registry
Expand All @@ -29,7 +29,7 @@ public function __construct(
*/
public static function defaultName(): string
{
return 'attribute list';
return 'attributes list';
}

/**
Expand Down
84 changes: 84 additions & 0 deletions src/Service/AttributeCacheValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);

namespace AttributeRegistry\Service;

use AttributeRegistry\AttributeRegistry;
use AttributeRegistry\ValueObject\AttributeCacheValidationResult;

/**
* Validates attribute cache integrity.
*/
class AttributeCacheValidator
{
/**
* Constructor.
*
* @param \AttributeRegistry\AttributeRegistry $registry Attribute registry instance
*/
public function __construct(private AttributeRegistry $registry)
{
}

/**
* Validate the attribute cache.
*
* Checks that all cached attributes reference existing files
* and that file hashes match (if present).
*
* @return \AttributeRegistry\ValueObject\AttributeCacheValidationResult
*/
public function validate(): AttributeCacheValidationResult
{
$discovered = $this->registry->discover();
$attributes = [];

// Collect all attributes
foreach ($discovered as $attribute) {
$attributes[] = $attribute;
}

// If no attributes, return success with 0 attributes
if ($attributes === []) {
return AttributeCacheValidationResult::success(0, 0);
}

$errors = [];
$filesChecked = [];

foreach ($attributes as $attribute) {
$filePath = $attribute->filePath;
$fileHash = $attribute->fileHash;

// Track unique files
$filesChecked[$filePath] = true;

// Check file existence
if (!file_exists($filePath)) {
$errors[] = 'File not found: ' . $filePath;
continue;
}

// Check hash if present (backward compatibility - skip if empty)
if (!empty($fileHash)) {
$actualHash = hash_file('xxh3', $filePath);
if ($actualHash !== $fileHash) {
$errors[] = 'Hash mismatch for file: ' . $filePath;
}
}
}

if ($errors !== []) {
return AttributeCacheValidationResult::failure(
$errors,
count($attributes),
count($filesChecked),
);
}

return AttributeCacheValidationResult::success(
count($attributes),
count($filesChecked),
);
}
}
Loading
Loading