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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ See individual rule documentation for detailed configuration examples. A [full c
- [Methods Returning Bool Must Follow Naming Convention Rule](docs/rules/Methods-Returning-Bool-Must-Follow-Naming-Convention-Rule.md)
- [Method Signature Must Match Rule](docs/rules/Method-Signature-Must-Match-Rule.md)
- [Method Must Return Type Rule](docs/rules/Method-Must-Return-Type-Rule.md)
- [Property Must Match Rule](docs/rules/Property-Must-Match-Rule.md)
- [Forbidden Accessors Rule](docs/rules/Forbidden-Accessors-Rule.md)

### Clean Code Rules

Expand Down
65 changes: 65 additions & 0 deletions data/ForbiddenAccessors/EntityWithAccessors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Domain;

class UserEntity
{
private string $name;
private int $age;
private bool $active;

public function getName(): string
{
return $this->name;
}

public function setName(string $name): void
{
$this->name = $name;
}

public function getAge(): int
{
return $this->age;
}

public function setAge(int $age): void
{
$this->age = $age;
}

protected function getActive(): bool
{
return $this->active;
}

protected function setActive(bool $active): void
{
$this->active = $active;
}

private function getPrivateValue(): string
{
return 'private';
}

private function setPrivateValue(string $value): void
{
// This should not trigger an error (private)
}

public function doSomething(): void
{
// Regular method, should not trigger
}

public function get(): void
{
// Should not trigger - no uppercase letter after 'get'
}

public function set(): void
{
// Should not trigger - no uppercase letter after 'set'
}
}
18 changes: 18 additions & 0 deletions data/ForbiddenAccessors/ServiceWithAccessors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Service;

class ServiceWithAccessors
{
private string $config;

public function getConfig(): string
{
return $this->config;
}

public function setConfig(string $config): void
{
$this->config = $config;
}
}
99 changes: 99 additions & 0 deletions data/PropertyMustMatch/TestClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

class DummyRepository
{
}

// Valid controller with all required properties
class ValidController
{
private int $id;
private DummyRepository $repository;
}

// Missing required property 'id'
class MissingPropertyController
{
private DummyRepository $repository;
}

// Wrong type for 'id' property (string instead of int)
class WrongTypeController
{
private string $id;
private DummyRepository $repository;
}

// Wrong visibility for 'id' property (public instead of private)
class WrongVisibilityController
{
public int $id;
private DummyRepository $repository;
}

// Multiple errors: wrong type and wrong visibility
class MultipleErrorsController
{
public string $id;
protected DummyRepository $repository;
}

// Missing type on property
class NoTypeController
{
private $id;
private DummyRepository $repository;
}

// Nullable type property
class NullableTypeController
{
private ?int $id;
private DummyRepository $repository;
}

// Service class with optional logger property (not required)
class ValidService
{
private LoggerInterface $logger;
}

// Service class without logger (should be fine since not required)
class ServiceWithoutLogger
{
private string $name;
}

// Service class with wrong logger type
class WrongLoggerTypeService
{
private string $logger;
}

// Class that doesn't match any pattern (should be ignored)
class NotMatchingClass
{
public string $id;
public int $logger;
}

// NullableAllowed classes - used to test nullable: true flag
// Has nullable type, should pass when nullable: true
class NullableAllowedHandler
{
private ?int $id;
}

// Has non-nullable type, should also pass when nullable: true
class NonNullableAllowedHandler
{
private int $id;
}

// Has wrong type entirely, should fail even with nullable: true
class WrongTypeAllowedHandler
{
private string $id;
}
46 changes: 46 additions & 0 deletions docs/Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ Detects circular dependencies between modules in a modular architecture. This ru

See [Circular Module Dependency Rule documentation](rules/Circular-Module-Dependency-Rule.md) for detailed information.

## Forbidden Accessors Rule

Forbids public and/or protected getters and setters on classes matching specified patterns. Useful for enforcing immutability or the "Tell, Don't Ask" principle.

See [Forbidden Accessors Rule documentation](rules/Forbidden-Accessors-Rule.md) for detailed information.

## Property Must Match Rule

Ensures that classes matching specified patterns have properties with expected names, types, and visibility scopes. Can optionally enforce that matching classes must have certain properties.

See [Property Must Match Rule documentation](rules/Property-Must-Match-Rule.md) for detailed information.

## Full Configuration Example

Here is a full example for a modular monolith with clean architecture rules.
Expand Down Expand Up @@ -130,6 +142,27 @@ services:
tags:
- phpstan.rules.rule

# Property rules for entities
-
class: Phauthentic\PHPStanRules\Architecture\PropertyMustMatchRule
arguments:
propertyPatterns:
-
classPattern: '/^App\\Capability\\.*\\Domain\\Model\\.*Entity$/'
properties:
-
name: 'id'
type: 'int'
visibilityScope: 'private'
required: true
-
name: 'createdAt'
type: 'DateTimeImmutable'
visibilityScope: 'private'
required: true
tags:
- phpstan.rules.rule

# Method signature rules for repositories
-
class: Phauthentic\PHPStanRules\Architecture\MethodSignatureMustMatchRule
Expand Down Expand Up @@ -191,6 +224,19 @@ services:
tags:
- phpstan.rules.rule

# Forbid accessors on domain entities
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule
arguments:
classPatterns:
- '/^App\\Capability\\.*\\Domain\\Model\\.*Entity$/'
forbidGetters: true
forbidSetters: true
visibility:
- public
tags:
- phpstan.rules.rule

# Clean Code Rules

# Control structure nesting
Expand Down
67 changes: 67 additions & 0 deletions docs/rules/Forbidden-Accessors-Rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Forbidden Accessors Rule

Forbids public and/or protected getters (`getXxx()`) and setters (`setXxx()`) on classes matching specified patterns. This is useful for enforcing immutability or encapsulation in domain entities or value objects.

## Configuration Example

```neon
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule
arguments:
classPatterns:
- '/^App\\Domain\\.*Entity$/'
- '/^App\\Domain\\.*ValueObject$/'
forbidGetters: true
forbidSetters: true
visibility:
- public
tags:
- phpstan.rules.rule
```

## Parameters

- `classPatterns`: Array of regex patterns to match against class FQCNs.
- `forbidGetters`: Whether to forbid `getXxx()` methods (default: `true`).
- `forbidSetters`: Whether to forbid `setXxx()` methods (default: `true`).
- `visibility`: Array of visibilities to check. Valid values are `public` and `protected` (default: `['public']`).

## Use Cases

### Forbid Setters Only (Immutable Objects)

To enforce immutability while still allowing getters:

```neon
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule
arguments:
classPatterns:
- '/^App\\Domain\\.*Entity$/'
forbidGetters: false
forbidSetters: true
visibility:
- public
- protected
tags:
- phpstan.rules.rule
```

### Forbid All Accessors (Tell, Don't Ask)

To enforce the "Tell, Don't Ask" principle on domain entities:

```neon
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule
arguments:
classPatterns:
- '/^App\\Domain\\Model\\/'
forbidGetters: true
forbidSetters: true
visibility:
- public
- protected
tags:
- phpstan.rules.rule
```
Loading