PHP code example of structuraphp / structura

1. Go to this page and download the library: Download structuraphp/structura library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

structuraphp / structura example snippets


return static function (StructuraConfig $config): void {
    $config->archiRootNamespace(
        '<MY_NAMESPACE>\Tests\Architecture', // namespace
        'tests/Architecture', // test directory
    );
};

use StructuraPhp\Structura\Asserts\ToExtendNothing;
use StructuraPhp\Structura\Attributes\TestDox;
use StructuraPhp\Structura\Expr;
use StructuraPhp\Structura\Testing\TestBuilder;
use Symfony\Component\Finder\Finder;

final class TestDto extends TestBuilder
{
    #[TestDox('Asserts rules Dto architecture')]
    public function testAssertArchitectureRules(): void
    {
        $this
            ->allClasses()
            ->fromDir(
                'app/Dto',
                fn(Finder $finder) => $finder->depth('== 0')
            )
            ->that($this->conditionThat(...))
            ->except($this->exception(...))
            ->should($this->should(...));
    }

    private function that(Expr $expr): void
    {
        // The rules will only apply to classes (ignore traits, enums, interfaces, etc.)
        $expr->toBeClasses();
    }

    private function exception(Except $except): void
    {
        // These classes will be ignored in the tests
        $except
            ->byClassname(
                className: [
                    FooDto::class,
                    BarDto::class,
                ],
                expression: ToExtendNothing::class
            );
    }

    private function should(Expr $expr): void
    {
        $expr
            ->toBeFinal()
            ->toBeReadonly()
            ->toHaveSuffix('Dto')
            ->toExtendsNothing()
            ->toHaveMethod('fromArray')
            ->toImplement(\JsonSerializable::class);
    }
}

->fromDir(
    'src/Dto',
    static fn(Finder $finder): Finder => $finder->depth('== 0')
)

->fromRaw('
            
    use ArrayAccess;
    use Depend\Bap;
    use Depend\Bar;
            
    class Foo implements \Stringable {
        public function __construct(ArrayAccess $arrayAccess) {}

    public function __toString(): string {
        return $this->arrayAccess['foo'] ?? throw new \Exception();
    }
}')

->that(static fn(Expr $expr): Expr => $expr->toBeClasses())

->except(static fn(Except $except): Except => $except
    ->byClassname(
        className: [
            FooDto::class,
            BarDto::class,
        ],
        expression: ToExtendNothing::class
    )
)

->should(static fn(Expr $expr): Expr => $expr
    ->toBeFinal()
    ->toBeReadonly()
    ->toHaveSuffix('Dto')
    ->toExtendsNothing()
    ->toHaveMethod('fromArray')
    ->toImplement(\JsonSerializable::class)
)

$this
  ->allClasses()
  ->fromRaw(' abstract class Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeAbstract(),
  );

$this
  ->allClasses()
  ->fromRaw(' new class {};')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeAnonymousClasses(),
  );

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeClasses(),
  );

$this
  ->allClasses()
  ->fromRaw(' enum Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeEnums(),
  );

use StructuraPhp\Structura\Enums\ScalarType;

$this
  ->allClasses()
  ->fromRaw(' enum Foo: string {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeBackedEnums(ScalarType::String),
  );

$this
  ->allClasses()
  ->fromRaw(' final class Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeFinal(),
  );

$this
  ->allClasses()
  ->fromRaw(' interface Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeInterfaces(),
  );

$this
  ->allClasses()
  ->fromRaw(' class Foo { public function __invoke() {} }')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeInvokable(),
  );

$this
  ->allClasses()
  ->fromRaw(' readonly class Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeReadonly(),
  );

$this
  ->allClasses()
  ->fromRaw(' trait Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeTraits(),
  );

$this
  ->allClasses()
  ->fromRaw(' #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] class Foo {}')
  ->should(
    static fn (Expr $assert): Expr => $assert->toBeAttribute(\Attribute::TARGET_CLASS_CONSTANT),
  );



[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] // OK
class Foo {

}

#[Custom] // KO
class Bar {

}

(new ReflectionClass(Bar::class))->getAttributes()[0]->newInstance();
// Fatal error: Uncaught Error: Attribute class "Custom" not found

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOn(
        names: [ArrayAccess::class, /* ... */],
        patterns: ['App\Dto.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnAttribut(
        names: [\Attribute::class, /* ... */],
        patterns: ['Attributes\Custom.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnImplementation(
        names: [\ArrayAccess::class, /* ... */],
        patterns: ['Contracts\Dto.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnInheritance(
        names: [Controller::class, /* ... */],
        patterns: ['Controllers\Admin.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnUseTrait(
        names: [\HasFactor::class, /* ... */],
        patterns: ['Concerns\Models.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->toNotDependsOn(
        names: [ArrayAccess::class, /* ... */],
        patterns: ['App\Dto.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnFunction(
        names: ['strtolower', /* ... */],
        patterns: ['array_.+', /* ... */],
    )
  );

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr
    ->dependsOnlyOnFunction(
        names: ['goto', /* ... */],
        patterns: ['.+exec', /* ... */],
    )
  );

$this
  ->allClasses()
  ->fromRaw(' class Foo extends \Exception {}')
  ->should(fn(Expr $expr) => $expr->toExtend(Exception::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(fn(Expr $expr) => $expr->toExtendsNothing());

$this
  ->allClasses()
  ->fromRaw(' class Foo implements \ArrayAccess, \JsonSerializable {}')
  ->should(fn(Expr $expr) => $expr->toImplement(ArrayAccess::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(fn(Expr $expr) => $expr->toImplementNothing());

$this
  ->allClasses()
  ->fromRaw(' class Foo implements \ArrayAccess {}')
  ->should(fn(Expr $expr) => $expr->toOnlyImplement(ArrayAccess::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo { use Bar, Baz; }')
  ->should(fn(Expr $expr) => $expr->toUseTrait(Bar::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(fn(Expr $expr) => $expr->toNotUseTrait());

$this
  ->allClasses()
  ->fromRaw(' class Foo { use Bar; }')
  ->should(fn(Expr $expr) => $expr->toOnlyUseTrait(Bar::class));

$this
  ->allClasses()
  ->fromRaw(' #[\Deprecated] class Foo {}')
  ->should(fn(Expr $expr) => $expr->toHaveAttribute(Deprecated::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(fn(Expr $expr) => $expr->toHaveNoAttribute());

$this
  ->allClasses()
  ->fromRaw(' #[\Deprecated] class Foo {}')
  ->should(fn(Expr $expr) => $expr->toHaveOnlyAttribute(Deprecated::class));

$this
  ->allClasses()
  ->fromRaw(' class Foo { public function bar() {} }')
  ->should(fn(Expr $expr) => $expr->toHaveMethod('bar'));

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr->toHaveConstructor());

$this
  ->allClasses()
  ->should(fn(Expr $expr) => $expr->toHaveDestructor());

$this
  ->allClasses()
  ->fromRaw(' class ExempleFoo {}')
  ->should(fn(Expr $expr) => $expr->toHavePrefix('Exemple'));

$this
  ->allClasses()
  ->fromRaw(' class FooExemple {}')
  ->should(fn(Expr $expr) => $expr->toHaveSuffix('Exemple'));

$this
  ->allClasses()
  ->fromRaw(' declare(strict_types=1); class Foo {}')
  ->should(fn(Expr $expr) => $expr->toUseStrictTypes());

$this
  ->allClasses()
  ->fromRaw(' declare(encoding='ISO-8859-1'); class Foo {}')
  ->should(fn(Expr $expr) => $expr->toUseDeclare('encoding', 'ISO-8859-1'));

$this
  ->allClasses()
  ->fromDir('tests')
  ->that(
    fn(Expr $expr) => $expr
      ->toBeInOneOfTheNamespaces('Tests\Unit.+')
  )
  ->should(fn(Expr $expr) => $expr /* our rules */);

$this
  ->allClasses()
  ->fromDir('tests')
  ->that(
    fn(Expr $expr) => $expr
      ->notToBeInOneOfTheNamespaces('Tests\Unit.+')
  )
  ->should(fn(Expr $expr) => $expr /* our rules */);

// if Foo interface extends ArrayAccess and (JsonSerializable and Countable)
$this
  ->allClasses()
  ->fromRaw(' interface Foo extends ArrayAccess, JsonSerializable, Countable {}')
  ->should(fn(Expr $expr) => $expr
    ->toBeInterfaces()
    ->toExtend(ArrayAccess::class)
    ->and(fn(Expr $expr) => $expr
      ->toExtend(JsonSerializable::class)
      ->toExtend(Countable::class)
    )
  );


// if Foo class implements ArrayAccess and (JsonSerializable or Countable)
$this
  ->allClasses()
  ->fromRaw(' class Foo implements ArrayAccess, JsonSerializable {}')
  ->should(fn(Expr $expr) => $expr
    ->toBeClasses()
    ->toImplement(ArrayAccess::class)
    ->or(fn(Expr $expr) => $expr
      ->toImplement(JsonSerializable::class)
      ->toImplement(Countable::class)
    )
  );



final readonly class CustomRule implements ExprInterface
{
    public function __construct(
        private string $message = '',
    ) {}

    public function __toString(): string
    {
        return 'exemple'; // Name of the rule
    }

    public function assert(ClassDescription $class): bool
    {
        return true; // Must return false if the test fails
    }

    public function getViolation(ClassDescription $class): ViolationValueObject
    {
        return new ViolationValueObject(
            'error message', // Console output
            $this::class,
            $class->lines,
            $class->getFileBasename(),
            $this->message,
        );
    }
}

$this
  ->allClasses()
  ->fromRaw(' class Foo {}')
  ->should(fn(Expr $expr) => $expr
    ->addExpr(new CustomRule('foo'))
  );
shell
php structura init
shell
php bin/structura make
shell
php bin/structura analyze