Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/vendor/
/vendor-bin/*/vendor/
/vendor-bin/*/.phpunit.cache/
/build/
/composer.lock
/phpunit.xml
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"tests/",
"tests/Fake"
],
"Ray\\MediaQuery\\PHPStan\\": "vendor-bin/media-query-phpstan/src/",
"Tutorial\\Blog\\": "docs/tutorial/src/Blog/"
}
},
Expand Down
7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
includes:
- vendor-bin/media-query-phpstan/extension.neon

parameters:
level: max
paths:
Expand All @@ -8,3 +11,7 @@ parameters:
- tests/Fake/*
- tests/tmp/*
- src/MediaQueryModule.php
rayMediaQuery:
sqlDirectories:
- %currentWorkingDirectory%/docs/tutorial/src/sql
factoryMethod: factory
101 changes: 101 additions & 0 deletions vendor-bin/media-query-phpstan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# ray/media-query-phpstan

PHPStan rules that statically verify the `#[DbQuery]` contract of
Ray.MediaQuery interfaces — the cross-artifact invariants that PHPStan core
cannot see on its own (SQL files, `#[Pager]` coherence, factory existence).

This currently lives in-repo under `vendor-bin/media-query-phpstan/`, installed as
its own isolated dependency set via `bamarni/composer-bin-plugin` (the same
convention the repo uses for `vendor-bin/tools/`). It is structured as a
self-contained Composer package so it can later be extracted to a standalone
`ray/media-query-phpstan` distribution unchanged.

## What it checks (MVP)

The MVP is intentionally scoped to checks with near-zero false positives:

| Identifier | Meaning |
| --- | --- |
| `rayMediaQuery.sqlFileNotFound` | `#[DbQuery('id')]` has no `{sqlDir}/id.sql` in any configured directory. |
| `rayMediaQuery.emptySqlFile` | The resolved `id.sql` exists but is empty. |
| `rayMediaQuery.pagerReturnMismatch` | `#[Pager]` is present but the return type is not a `PagesInterface`. |
| `rayMediaQuery.missingPagerAttribute` | The return type is a `PagesInterface` but `#[Pager]` is missing. |
| `rayMediaQuery.invalidFactory` | `factory:` names a class/method that does not exist. |

Not implemented on purpose:

- **Invalid `type:` value** — PHPStan core already reports this via the
`'row'|'row_list'` constructor type of `DbQuery` (`argument.type`).
- **Placeholder ↔ parameter matching** — deferred until the SQL lexer lands; it
needs `#[Input]` flattening and `#[SqlTemplate]` handling to avoid false
positives.

## Usage

Add the extension to your `phpstan.neon` and point it at your SQL directories:

```neon
includes:
- vendor-bin/media-query-phpstan/extension.neon

parameters:
rayMediaQuery:
sqlDirectories:
- %currentWorkingDirectory%/path/to/sql
factoryMethod: factory # default: 'factory'
strict: false # reserved for later phases
strictPlaceholderCheck: false # reserved for later phases
```

When `sqlDirectories` is empty the SQL existence/empty checks are skipped; the
Pager and factory checks still run. SQL ids are a flat, globally-unique
namespace, so each id is searched across every configured directory.

## Caveat: SQL changes and PHPStan's result cache

PHPStan's result cache is keyed on PHP files and their PHP dependencies. A change
to a `.sql` file alone does **not** invalidate the cache, so a stale
`sqlFileNotFound` / `emptySqlFile` result can persist locally. CI runs are
unaffected because they start from a clean cache. Locally, after changing SQL
files, run:

```bash
composer clean # clears PHPStan + Psalm caches
```

## Developing / testing the rules

This bin carries its own `phpstan` + `phpunit` so `PHPStan\Testing\RuleTestCase`
has both available in one vendor (the repo's `vendor-bin/tools` has phpstan but no
phpunit, and the root has phpunit but no phpstan — neither can run rule tests
alone). Install it with the bamarni forward command, then run the tests; the
package `bootstrap.php` makes both the rule code and the parent project types
available, so the root binaries can drive the extension's configs:

```bash
composer bin media-query-phpstan install
./vendor/bin/phpunit -c vendor-bin/media-query-phpstan
```

Self-analyse the rule code at level max:

```bash
./vendor/bin/phpstan analyse -c vendor-bin/media-query-phpstan/phpstan.neon.dist
```

## Layout

```text
vendor-bin/media-query-phpstan/
extension.neon # parameters schema + service registration
phpstan.neon.dist # self-analysis of the rule code
src/
Rules/DbQueryContractRule.php
Support/AttributeFinder.php
Support/DbQueryAttribute.php
Support/SqlFileResolver.php
tests/
Rules/DbQueryContractRuleTest.php
Fixture/Data/*.php # interfaces/classes analysed by the rule tests
Fixture/sql/*.sql # SQL fixtures
```
18 changes: 18 additions & 0 deletions vendor-bin/media-query-phpstan/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

/*
* Test bootstrap for the self-contained PHPStan extension package.
*
* Loads this package's own vendor (phpstan + phpunit + this package), then the
* parent ray/media-query autoloader so fixtures can reference the real
* annotation / result / Pages types instead of stubs.
*/

require __DIR__ . '/vendor/autoload.php';

$parentAutoload = dirname(__DIR__, 2) . '/vendor/autoload.php';
if (is_file($parentAutoload)) {
require $parentAutoload;
}
33 changes: 33 additions & 0 deletions vendor-bin/media-query-phpstan/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "ray/media-query-phpstan",
"description": "PHPStan rules that statically verify Ray.MediaQuery #[DbQuery] contracts",
"license": "MIT",
"type": "phpstan-extension",
"require": {
"php": "^8.2",
"phpstan/phpstan": "^2.1"
},
"require-dev": {
"phpunit/phpunit": "^11.5"
},
"autoload": {
"psr-4": {
"Ray\\MediaQuery\\PHPStan\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Ray\\MediaQuery\\PHPStan\\Tests\\": "tests/"
}
},
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"config": {
"sort-packages": true
}
}
Loading
Loading