├── .gitignore ├── phpstan.neon.dist ├── .editorconfig ├── src ├── UnableToChangeState.php ├── EloquentObjectProxyFactory.php ├── EloquentObjectProxy.php └── SingleFiniteStateMachineBehavior.php ├── .php-cs-fixer.dist.php ├── .github └── workflows │ ├── phpstan.yml │ └── php-cs-fixer.yml ├── LICENSE └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .php-cs-fixer.cache 3 | composer.lock 4 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src/ 8 | 9 | # Level 9 is the highest level 10 | level: max 11 | 12 | checkMissingIterableValueType: false 13 | checkGenericClassInNonGenericObjectType: false 14 | 15 | # TODO Check later 16 | treatPhpDocTypesAsCertain: false 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [*.{neon,neon.dist}] 18 | indent_style = tab 19 | 20 | [docker-compose.yml] 21 | indent_size = 4 22 | 23 | [*.{js,ts,cjs,vue,tsx}] 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /src/UnableToChangeState.php: -------------------------------------------------------------------------------- 1 | transition = $transition; 18 | 19 | return $instance; 20 | } 21 | 22 | public function transition(): ?string 23 | { 24 | return $this->transition ?? null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | save(); 17 | 18 | use Ergebnis\PhpCsFixer\Config; 19 | 20 | $config = Config\Factory::fromRuleSet(new Dflydev\PhpCsFixer\Config\RuleSet\Dflydev()); 21 | 22 | $config->getFinder()->in(__DIR__); 23 | $config->setCacheFile(__DIR__ . '/.php-cs-fixer.cache'); 24 | 25 | return $config; 26 | -------------------------------------------------------------------------------- /src/EloquentObjectProxyFactory.php: -------------------------------------------------------------------------------- 1 | defaultPropertyName = $defaultPropertyName; 18 | } 19 | 20 | public function build(object $object, array $options): ObjectProxy 21 | { 22 | return new EloquentObjectProxy($object, $options['property_path'] ?? $this->defaultPropertyName); 23 | } 24 | 25 | public function supports(object $object, array $options): bool 26 | { 27 | return $object instanceof Model; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: PHPStan 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: phpstan-${{ github.ref_name || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | 15 | phpstan: 16 | name: PHPStan PHP ${{ matrix.php }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | php: ['8.2'] 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | with: 27 | fetch-depth: 1 28 | 29 | - name: Set up PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | 34 | - name: Install Composer dependencies 35 | uses: ramsey/composer-install@v2 36 | with: 37 | composer-options: "--no-progress --prefer-dist --optimize-autoloader" 38 | 39 | - name: Larastan 40 | run: php ./vendor/bin/phpstan analyse --memory-limit=2G --error-format=github 41 | -------------------------------------------------------------------------------- /src/EloquentObjectProxy.php: -------------------------------------------------------------------------------- 1 | object = $object; 20 | $this->property = $property; 21 | } 22 | 23 | public function object(): object 24 | { 25 | return $this->object; 26 | } 27 | 28 | public function state(): ?string 29 | { 30 | return $this->object->{$this->property}; 31 | } 32 | 33 | public function apply( 34 | Transition $transition, 35 | State $fromState, 36 | State $toState 37 | ): void { 38 | $this->object->{$this->property} = $toState->name(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: PHP CS Fixer 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: php-cs-fixer-${{ github.ref_name || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | 15 | php-cs-fixer: 16 | name: PHP CS Fixer ${{ matrix.php }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | php: ['8.2'] 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | with: 27 | fetch-depth: 1 28 | 29 | - name: Set up PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | tools: cs2pr 34 | 35 | - name: Install Composer dependencies 36 | uses: ramsey/composer-install@v2 37 | with: 38 | composer-options: "--no-progress --prefer-dist --optimize-autoloader" 39 | 40 | - name: php-cs-fixer 41 | run: composer run style:check -- --format=checkstyle | cs2pr 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Dragonfly Development Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/finite-state-machine-support-for-laravel", 3 | "license": "MIT", 4 | "type": "library", 5 | "require": { 6 | "php": "^8.1", 7 | "dflydev/finite-state-machine": "^0@dev" 8 | }, 9 | "require-dev": { 10 | "dflydev/php-coding-standards": "dev-main", 11 | "ergebnis/composer-normalize": "^2.31", 12 | "ergebnis/license": "^2.1", 13 | "nunomaduro/larastan": "^2.5", 14 | "orchestra/testbench": "^8.0", 15 | "phpstan/phpstan": "^1.10" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Dflydev\\FiniteStateMachine\\SupportForLaravel\\": "src/" 20 | } 21 | }, 22 | "config": { 23 | "allow-plugins": { 24 | "ergebnis/composer-normalize": true 25 | }, 26 | "preferred-install": "dist", 27 | "sort-packages": true 28 | }, 29 | "extra": { 30 | "branch-alias": { 31 | "dev-main": "0.x-dev" 32 | } 33 | }, 34 | "scripts": { 35 | "style:check": "@php ./vendor/bin/php-cs-fixer fix --dry-run", 36 | "style:fix": "@php ./vendor/bin/php-cs-fixer fix" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SingleFiniteStateMachineBehavior.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | private static array $finiteStateMachines; 17 | 18 | abstract protected function getFiniteStateMachineDiscriminator(): string; 19 | 20 | private function getFiniteStateMachine(?FiniteStateMachineFactory $finiteStateMachineFactory = null): FiniteStateMachine 21 | { 22 | $key = self::class.':'.spl_object_id($this).':'.$this->getFiniteStateMachineDiscriminator(); 23 | 24 | if (isset(self::$finiteStateMachines[$key])) { 25 | return self::$finiteStateMachines[$key]; 26 | } 27 | 28 | $finiteStateMachineFactory = $finiteStateMachineFactory ?? app(FiniteStateMachineFactory::class); 29 | 30 | return self::$finiteStateMachines[$key] = $finiteStateMachineFactory->build($this); 31 | } 32 | 33 | private function assertCanTransition(string $transition): void 34 | { 35 | if ($this->canTransition($transition)) { 36 | return; 37 | } 38 | 39 | throw UnableToChangeState::via($transition); 40 | } 41 | 42 | private function canTransition(string $transition): bool 43 | { 44 | return $this->getFiniteStateMachine()->can($transition); 45 | } 46 | 47 | private function applyTransition(string $transition): void 48 | { 49 | try { 50 | $this->getFiniteStateMachine()->apply($transition); 51 | } catch (Throwable $throwable) { 52 | throw UnableToChangeState::via($transition, $throwable); 53 | } 54 | } 55 | } 56 | --------------------------------------------------------------------------------