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
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
package-lock.json export-ignore
package.json export-ignore
phpcs.xml.dist export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
/tests/ export-ignore
15 changes: 15 additions & 0 deletions .github/workflows/phpcs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: PHP CodeSniffer

on:
pull_request:
# Allow manually triggering the workflow.
workflow_dispatch:

# Cancel all previous workflow runs for the same branch that have not yet completed.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
phpcs:
uses: wp-media/workflows/.github/workflows/phpcs.yml@main
15 changes: 15 additions & 0 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: PHPStan

on:
pull_request:
# Allow manually triggering the workflow.
workflow_dispatch:

# Cancel all previous workflow runs for the same branch that have not yet completed.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
phpstan:
uses: wp-media/workflows/.github/workflows/phpstan.yml@main
32 changes: 32 additions & 0 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PHPUnit

on:
pull_request:
# Allow manually triggering the workflow.
workflow_dispatch:

# Cancel all previous workflow runs for the same branch that have not yet completed.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
phpunit:
name: PHPUnit tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: none # XDebug can be enabled here 'coverage: xdebug'
tools: composer:v2

- name: Install dependencies
run: composer install --no-progress --no-suggest

- name: Run PHPUnit tests
run: composer test-unit
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.phpunit.result.cache
/composer.lock
/node_modules/
/vendor/
277 changes: 276 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,276 @@
# phpstan-wp
# PHPStan WP

A PHPStan extension providing custom rules for WordPress projects, specifically designed for WP Media projects

## Overview

This extension adds static analysis rules to ensure proper implementation of WordPress hooks through subscriber classes. It validates that your event subscribers follow best practices for WordPress actions and filters.

## Custom Rules

### SubscriberCallbackRule

Validates `get_subscribed_events()` implementations in classes that implement subscriber interfaces. This rule ensures:

1. **Required Annotations**: All hook registrations must have `@action` or `@filter` PHPDoc annotations
2. **Filter Return Types**: Filter callbacks must return non-void values
3. **Action Return Types**: Action callbacks must return void
4. **Parameter Count Validation**: When `accepted_args` is specified, the callback method must have the correct number of parameters

#### Supported Subscriber Interfaces

- `WP_Rocket\Event_Management\Subscriber_Interface`
- `Imagify\EventManagement\SubscriberInterface`
- `WPMedia\EventManager\SubscriberInterface`
- `WPMedia\BackWPup\EventManagement\SubscriberInterface`

## Installation

Install via Composer:

```bash
composer require --dev wp-media/phpstan-wp
```

If you have [phpstan/extension-installer](https://github.com/phpstan/extension-installer) installed, the extension will be automatically registered.

### Manual Configuration

If you don't use the extension installer, add this to your `phpstan.neon` or `phpstan.neon.dist`:

```neon
includes:
- vendor/wp-media/phpstan-wp/extension.neon
```

## Usage

### Valid Subscriber Example

```php
<?php

use WPMedia\EventManager\SubscriberInterface;

class MySubscriber implements SubscriberInterface
{
public function get_subscribed_events(): array
{
return [
// @filter - Filters must return a value
'the_content' => 'filterContent',

// @action - Actions must return void
'init' => 'onInit',

// @filter - With priority
'the_title' => ['filterTitle', 10],

// @action - With priority and accepted_args
'save_post' => ['onSavePost', 10, 2],
];
}

/**
* Filter callback - returns modified content
*/
public function filterContent(string $content): string
{
return $content . ' - filtered';
}

/**
* Action callback - returns void
*/
public function onInit(): void
{
// Perform initialization
}

/**
* Filter callback with priority
*/
public function filterTitle(string $title): string
{
return strtoupper($title);
}

/**
* Action callback with 2 parameters (matching accepted_args)
*/
public function onSavePost(int $postId, \WP_Post $post): void
{
// Handle post save
}
}
```

### Common Errors and How to Fix Them

#### Missing Annotation

❌ **Invalid** - Missing `@filter` or `@action` annotation:

```php
public function get_subscribed_events(): array
{
return [
'the_content' => 'filterContent', // ERROR: Missing annotation
];
}
```

✅ **Valid** - Add the annotation before the array element:

```php
public function get_subscribed_events(): array
{
return [
// @filter
'the_content' => 'filterContent',
];
}
```

#### Filter with Void Return Type

❌ **Invalid** - Filter callbacks must return a value:

```php
public function get_subscribed_events(): array
{
return [
// @filter
'the_content' => 'filterContent',
];
}

public function filterContent(string $content): void // ERROR: Returns void
{
echo $content;
}
```

✅ **Valid** - Filter callbacks must return the filtered value:

```php
public function get_subscribed_events(): array
{
return [
// @filter
'the_content' => 'filterContent',
];
}

public function filterContent(string $content): string
{
return $content . ' - filtered';
}
```

#### Action with Non-Void Return Type

❌ **Invalid** - Action callbacks must return void:

```php
public function get_subscribed_events(): array
{
return [
// @action
'init' => 'onInit',
];
}

public function onInit(): int // ERROR: Returns non-void
{
return 42;
}
```

✅ **Valid** - Action callbacks must return void:

```php
public function get_subscribed_events(): array
{
return [
// @action
'init' => 'onInit',
];
}

public function onInit(): void
{
// Perform action
}
```

#### Parameter Count Mismatch

❌ **Invalid** - Parameter count doesn't match `accepted_args`:

```php
public function get_subscribed_events(): array
{
return [
// @action - accepted_args=3 but method has only 1 parameter
'save_post' => ['onSavePost', 10, 3], // ERROR
];
}

public function onSavePost(int $postId): void
{
// Missing 2 parameters
}
```

✅ **Valid** - Parameter count matches accepted_args:

```php
public function get_subscribed_events(): array
{
return [
// @action
'save_post' => ['onSavePost', 10, 3],
];
}

public function onSavePost(int $postId, \WP_Post $post, bool $update): void
{
// Now has 3 parameters matching accepted_args
}
```

## Development

### Running Tests

```bash
composer test-unit
```

### Code Style

Check code style:

```bash
composer phpcs
```

Fix code style issues:

```bash
composer phpcbf
```

## Requirements

- PHP 7.4 or higher
- PHPStan 2.0 or higher

## License

GPL-3.0-or-later

## Contributing

Contributions are welcome! Please ensure your code follows the WordPress Coding Standards and includes appropriate tests.
Loading