Skip to content

Conversation

@koriym
Copy link
Member

@koriym koriym commented Oct 27, 2025

Summary

This PR modernizes BEAR.AppMeta with domain type aliases following BEAR.Package conventions, upgrades to PHP 8.1 with readonly properties, and adds full PHP 8.5 CI support.

Major Changes

Type System Improvements

  • ✅ Introduce Types.php with domain type aliases (AppName, Context, AppDir, TmpDir, LogDir, UriPath, FilePath, Scheme)
  • ✅ Use @psalm-import-type pattern across all classes
  • ✅ Remove duplicate @psalm-* annotations in favor of standard PHPDoc
  • ✅ Achieve 100% Psalm type inference

PHP 8.1 Upgrade

  • ✅ Upgrade minimum PHP version from 8.0 (EOL) to 8.1
  • ✅ Add readonly properties to ResMeta value object
  • ✅ Update PHPUnit from ^9.5.10 to ^9.6 for readonly keyword support
  • ✅ Update phpcs.xml and rector.php for PHP 8.1

Dependency Management

  • ✅ Lower bear/resource to ^1.0 for maximum compatibility (was ^1.26)
  • ✅ Lower koriym/psr4list to ^1.0.2 for maximum compatibility (was ^1.4)
  • ✅ Remove phpmetrics (old php-parser couldn't parse readonly)
  • ✅ Remove post-install-cmd and post-update-cmd for CI compatibility
  • ✅ Set forward-command: true in bamarni-bin config

PHP 8.5 Support

  • ✅ Add PHP 8.5 to CI test matrix (ubuntu-latest and windows-latest)
  • ✅ All tests pass on PHP 8.1, 8.2, 8.3, 8.4, and 8.5
  • ✅ Fix Windows path separator issues in tests

Code Quality

  • ✅ Update coding standards dependencies (doctrine/coding-standard ^14.0, squizlabs/php_codesniffer ^4.0)
  • ✅ Add descriptive assert messages
  • ✅ Normalize file paths in tests for cross-platform compatibility

Testing

  • ✅ PHPStan: No errors
  • ✅ Psalm: No errors (100% type inference)
  • ✅ PHPCS: No violations
  • ✅ PHPUnit: All tests passing (10 tests, 21 assertions)
  • ✅ PHP 8.1-8.5: All tests pass on both Ubuntu and Windows

Backward Compatibility

  • ✅ Wider dependency ranges (^1.0 instead of ^1.26) improve compatibility with older projects
  • ✅ PHP 8.1 minimum requirement (breaking change from PHP 8.0)

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 27, 2025

Reviewer's Guide

This PR adds a centralized domain type alias file, migrates metadata classes to use these aliases with proper PHPDoc and Psalm imports, enforces runtime type assertions for full Psalm inference, applies minor readability tweaks, marks exceptions as final, and updates the CI matrix to support PHP 8.5.

Class diagram for new domain type aliases in Types.php

classDiagram
    class Types {
        <<final>>
        +AppName: non-empty-string
        +Context: non-empty-string
        +AppDir: non-empty-string
        +TmpDir: non-empty-string
        +LogDir: non-empty-string
        +UriPath: non-empty-string
        +FilePath: non-empty-string
        +Scheme: non-empty-string
    }
Loading

Updated class diagram for AppMeta metadata classes

classDiagram
    class AbstractAppMeta {
        +AppName name
        +AppDir appDir
        +TmpDir tmpDir
        +LogDir logDir
        +getResourceListGenerator(): Generator
        +getGenerator(scheme: Scheme): Generator<ResMeta>
        -camel2kebab(str: non-empty-string): void
    }
    class Meta {
        <<final>>
        +Meta(name: AppName, context: Context, appDir: string)
        -getAppDir(name: AppName): AppDir
    }
    AbstractAppMeta <|-- Meta
    class ResMeta {
        <<final>>
        +ResMeta(uriPath: UriPath, class: class-string<ResourceObject>, filePath: FilePath)
        +UriPath uriPath
        +class-string<ResourceObject> class
        +FilePath filePath
    }
Loading

Class diagram for updated exception classes

classDiagram
    class AppNameException {
        <<final>>
        AppNameException extends LogicException
    }
    class NotWritableException {
        <<final>>
        NotWritableException extends LogicException
    }
Loading

File-Level Changes

Change Details Files
Introduce centralized domain type aliases and remove duplicate Psalm annotations
  • Add new Types.php with @psalm-type definitions for core domain strings
  • Inject @psalm-import-type annotations into AbstractAppMeta, Meta, and ResMeta
  • Eliminate redundant @psalm-* inline annotations in favor of imported aliases
src/Types.php
src/AbstractAppMeta.php
src/Meta.php
src/ResMeta.php
Strengthen PHPDoc typing across metadata classes
  • Update @var, @param, and @return tags to use alias types (AppName, AppDir, UriPath, FilePath, Scheme)
  • Adjust method signatures and return declarations to reflect precise domain aliases
  • Correct property promotion annotations in ResMeta constructor
src/AbstractAppMeta.php
src/Meta.php
src/ResMeta.php
Add runtime assertions and remove unnecessary casts for 100% Psalm inference
  • Insert assert() checks after file and directory resolution to validate non-empty-string invariants
  • Remove redundant (string) casts in URI construction and value conversions
src/AbstractAppMeta.php
src/Meta.php
Improve code readability and align formatting
  • Split long expressions into multiple lines for clarity in AbstractAppMeta
  • Refine camel2kebab method comments and ensure proper phpcs ignore tag
src/AbstractAppMeta.php
src/ResMeta.php
Finalize exception classes and update CI for PHP 8.5
  • Mark AppNameException and NotWritableException classes as final
  • Update GitHub Actions matrix to include PHP 8.5 and shift 8.4 to old_stable
src/Exception/AppNameException.php
src/Exception/NotWritableException.php
.github/workflows/continuous-integration.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov
Copy link

codecov bot commented Oct 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7a2ab7f) to head (693b3a7).
⚠️ Report is 2 commits behind head on 1.x.

Additional details and impacted files
@@             Coverage Diff             @@
##                 1.x       #38   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
  Complexity        19        19           
===========================================
  Files              3         3           
  Lines             40        45    +5     
===========================================
+ Hits              40        45    +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Relying on PHP’s assert() for enforcing non-empty-string invariants can be disabled at runtime; consider using explicit runtime checks or lightweight value objects to guarantee these domain types.
  • In the Meta constructor docblock the $appDir parameter is still marked as string rather than using the AppDir alias—import and apply @param AppDir there for consistency.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Relying on PHP’s assert() for enforcing non-empty-string invariants can be disabled at runtime; consider using explicit runtime checks or lightweight value objects to guarantee these domain types.
- In the Meta constructor docblock the `$appDir` parameter is still marked as `string` rather than using the `AppDir` alias—import and apply `@param AppDir` there for consistency.

## Individual Comments

### Comment 1
<location> `src/Meta.php:59-64` </location>
<code_context>
         }

-        return dirname((string) (new ReflectionClass($module))->getFileName(), 3);
+        $fileName = (new ReflectionClass($module))->getFileName();
+        assert($fileName !== false);
+
+        /** @var AppDir $dir */
</code_context>

<issue_to_address>
**suggestion:** ReflectionClass::getFileName() can return false for internal classes.

If $module is an internal class, this will fail. Consider throwing a more descriptive exception for easier debugging.

```suggestion
        $fileName = (new ReflectionClass($module))->getFileName();
        if ($fileName === false) {
            throw new \RuntimeException(
                sprintf(
                    'Cannot determine file name for module "%s". The class may be internal and does not have a file name.',
                    $module
                )
            );
        }

        /** @var AppDir $dir */
        $dir = dirname($fileName, 3);
        assert($dir !== '.');
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR introduces semantic type definitions via Psalm and strengthens type safety across the codebase. A new Types.php file defines semantic type aliases. Core classes now import and use these types in docblocks and implement runtime assertions. Exception classes are made final, and CI workflow supports PHP 8.4.

Changes

Cohort / File(s) Summary
CI Configuration
.github/workflows/continuous-integration.yml
Updated supported PHP versions: expanded old_stable to include "8.4" and updated current_stable from 8.4 to 8.5
Semantic Type System
src/Types.php
New file introducing Psalm type aliases (AppName, Context, AppDir, TmpDir, LogDir, UriPath, FilePath, Scheme) as non-empty-string within final Types class
Core Type Refinements
src/AbstractAppMeta.php
Added runtime assertions and stricter type hints; replaced generic strings with semantic types (AppName, AppDir, FilePath, Scheme, etc.); introduced local variables for resource discovery; updated return types and docblocks for getResourceListGenerator and getGenerator; enhanced camel2kebab with runtime validation
Application Metadata
src/Meta.php
Added Psalm type imports; enhanced getAppDir logic with stored fileName and assertions; updated constructor and docblock documentation to reflect AppName and Context semantic types
Resource Metadata
src/ResMeta.php
Added Psalm type imports for UriPath and FilePath; expanded constructor docblock to explicitly declare parameter types
Exception Finality
src/Exception/AppNameException.php, src/Exception/NotWritableException.php
Made both exception classes final, preventing further subclassing

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • AbstractAppMeta.php requires particular attention due to multiple runtime assertions, type hint changes in getGenerator and getResourceListGenerator, and logic modifications affecting observable behavior (e.g., path slicing with non-empty-string types)
  • The new Types.php file introduces the foundation for all type refinements and should be validated for correctness of semantic type definitions
  • Meta.php adds runtime assertions in getAppDir that should be verified to ensure proper error handling and no unintended side effects

Possibly related PRs

  • bearsunday/BEAR.AppMeta#36: Overlaps with same files and methods (AbstractAppMeta and Meta path handling), indicating related changes at code level

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Introduce domain type aliases and PHP 8.5 support" directly addresses the two primary objectives of the changeset. The domain type aliases component is evidenced by the new Types.php file and the widespread adoption of these aliases throughout src/AbstractAppMeta.php, src/Meta.php, and src/ResMeta.php through @psalm-import-type declarations. The PHP 8.5 support is reflected in the CI workflow updates where PHP 8.5 becomes the current stable version and PHP 8.4 moves to old_stable. The title is concise, clear, and effectively communicates the main changes without unnecessary detail.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed The PR description is clearly related to the changeset and provides meaningful information about the changes. The description specifically mentions introducing domain type aliases in Types.php (confirmed in raw summary), using @psalm-import-type pattern across classes (confirmed in AbstractAppMeta.php, Meta.php, and ResMeta.php), making exception classes final (confirmed for AppNameException and NotWritableException), adding runtime assertions (confirmed in multiple files), and updating CI for PHP 8.5 support (confirmed in continuous-integration.yml). The description aligns well with the actual technical changes shown in the raw summary and is not vague or generic, providing specific details about type system improvements, dependency management, and code quality enhancements.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

koriym and others added 2 commits October 27, 2025 09:50
This commit modernizes the codebase with better type safety following BEAR.Package conventions.

Type System Improvements:
- Add Types.php with domain type aliases (AppName, Context, AppDir, TmpDir, LogDir, UriPath, FilePath, Scheme)
- Remove duplicate @psalm-* annotations in favor of standard PHPDoc annotations
- Use @psalm-import-type to import type aliases across classes
- Add proper type assertions to achieve 100% Psalm type inference

Code Quality:
- Mark exception classes as final (AppNameException, NotWritableException)
- Split long lines for better readability (AbstractAppMeta:64)
- Remove unnecessary string casts (AbstractAppMeta:90)
- Use promoted properties annotations correctly in ResMeta

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add PHP 8.5 to CI test matrix
- Move PHP 8.4 to old_stable versions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@koriym
Copy link
Member Author

koriym commented Oct 27, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/Meta.php (1)

60-67: Good assert message; resolves prior concern.

The explicit message on getFileName() false improves debuggability during development.

🧹 Nitpick comments (6)
.github/workflows/continuous-integration.yml (1)

12-13: Quote 8.5 to avoid YAML float coercion.

Pass it as a string for consistency with old_stable and many reusable workflow inputs.

-      current_stable: 8.5
+      current_stable: "8.5"

If the reusable workflow expects a string, this prevents type surprises.

src/Types.php (1)

7-23: Harden the types container and narrow Scheme.

  • Prevent accidental instantiation of Types with a private constructor.
  • Scheme is only 'app' | 'page' | '*'; reflect that for stronger checks.
 /**
  * Domain Types
  * @psalm-type AppName = non-empty-string
  * @psalm-type Context = non-empty-string
@@
- * @psalm-type Scheme = non-empty-string
+ * @psalm-type Scheme = "app" | "page" | "*"
  */
 final class Types
 {
+    private function __construct()
+    {
+    }
 }
src/ResMeta.php (1)

15-23: Make promoted properties readonly.

They are value metadata and not mutated; readonly communicates intent and prevents accidental writes.

     public function __construct(
-        public string $uriPath,
-        public string $class,
-        public string $filePath,
+        public readonly string $uriPath,
+        /** @var class-string<ResourceObject> */
+        public readonly string $class,
+        public readonly string $filePath,
     ) {
     }
src/Meta.php (1)

37-45: Consider removing error suppression on mkdir or capturing context.

The pattern works but hides diagnostics. Optionally capture warnings or retry on race without @ to aid debugging.

-        if (! file_exists($this->tmpDir) && ! @mkdir($this->tmpDir, 0777, true) && ! is_dir($this->tmpDir)) {
+        if (! file_exists($this->tmpDir) && ! mkdir($this->tmpDir, 0777, true) && ! is_dir($this->tmpDir)) {
             throw new NotWritableException($this->tmpDir);
         }

If noisy in CI, keep @ but log last error when throwing.

src/AbstractAppMeta.php (2)

76-87: Guard against empty $path before indexing.

Rare, but defensive check avoids undefined offset if a class unexpectedly lacks the expected segments.

         foreach ($this->getResourceListGenerator() as [$class, $file]) {
             /** @var array<non-empty-string> $paths */
             $paths = explode('\\', $class);
             $path = array_slice($paths, 3);
             array_walk($path, [$this, 'camel2kebab']);
+            if ($path === []) {
+                continue; // defensive: unexpected class structure
+            }
             if ($scheme === '*') {
                 /** @var non-empty-string $schemeValue */
                 $schemeValue = $path[0];

38-49: Optional: consider typed or readonly properties in next major.

Typing (and possibly readonly) for $name, $appDir, $tmpDir, $logDir would encode invariants at runtime, but could be BC-impacting in 1.x. Consider for 2.x.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2ab7f and 6356b0b.

📒 Files selected for processing (7)
  • .github/workflows/continuous-integration.yml (1 hunks)
  • src/AbstractAppMeta.php (3 hunks)
  • src/Exception/AppNameException.php (1 hunks)
  • src/Exception/NotWritableException.php (1 hunks)
  • src/Meta.php (2 hunks)
  • src/ResMeta.php (1 hunks)
  • src/Types.php (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (3)
src/Exception/NotWritableException.php (1)

9-11: Finalizing exception class looks good.

Clearer API surface; prevents unintended inheritance.

src/Exception/AppNameException.php (1)

9-11: LGTM: finalized exception.

Consistent with the exception hierarchy tightening.

src/AbstractAppMeta.php (1)

110-113: camel2kebab: LGTM.

Asserting non-empty result is a nice safety net.

koriym and others added 4 commits October 27, 2025 10:04
- Upgrade squizlabs/php_codesniffer from ^3.7 to ^4.0
- Upgrade doctrine/coding-standard from 12.0.0 to 14.0.0
- Upgrade slevomat/coding-standard from 8.22.1 to 8.24.0

This removes deprecated sniff warnings:
- SlevomatCodingStandard.TypeHints.UnionTypeHintFormat (replaced by DNFTypeHintFormat)
- Squiz.WhiteSpace.LanguageConstructSpacing (replaced by Generic.WhiteSpace.LanguageConstructSpacing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add explicit configuration for bamarni-bin plugin to suppress deprecation warnings:
- bin-links: true (maintain current behavior)
- forward-command: false (maintain current behavior)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Update minimum PHP version from 8.0 to 8.1 (8.0 is EOL)
- Update phpcs.xml to use PHP 8.1 compatibility
- Update rector.php to use UP_TO_PHP_81 level set
- Add ReadOnlyPropertyRector for PHP 8.1 features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Use PHP 8.1 readonly properties for ResMeta's promoted constructor properties to ensure immutability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
koriym and others added 13 commits October 27, 2025 11:03
Update bear/resource from ^1.0 to ^1.26 to fix PHP 8.1+ deprecation warnings
related to ArrayAccess return type declarations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove phpmetrics as it depends on an old version of nikic/php-parser
that doesn't support PHP 8.1 readonly keyword syntax.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
PHPUnit 9.5 uses an old nikic/php-parser that doesn't support the readonly
keyword. Update to ^9.6 which includes php-parser with PHP 8.1 support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
vendor-bin tools are not needed for CI tests, which run with --no-plugins.
Removing these scripts prevents Psalm PHP 8.5 compatibility issues in CI.
Normalize file paths from getResourceListGenerator() to ensure
cross-platform compatibility. Remove unused var_dump import.
Partial revert of cd4ea86 to restore original psr4list version requirement
while keeping PHP 8.1 and other necessary updates.
- Add private constructor to prevent instantiation
- Narrow Scheme type from non-empty-string to literal union 'app'|'page'|'*'
The private constructor prevents instantiation and is not meant to be tested.
@koriym koriym merged commit ca76c27 into 1.x Oct 27, 2025
37 of 38 checks passed
@koriym koriym deleted the type-aliases branch October 27, 2025 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants