From 0c6ebfbcd5c21c486e0335732f3e43b1c4565c23 Mon Sep 17 00:00:00 2001 From: gpanos Date: Mon, 23 Aug 2021 19:21:21 +0200 Subject: [PATCH] wip --- .github/workflows/code-style.yml | 29 +++++++++ .github/workflows/tests.yml | 42 ++++++++++++ .gitignore | 5 ++ .php_cs.dist | 41 ++++++++++++ README.md | 34 ++++++++++ composer.json | 54 ++++++++++++++++ phpunit.xml | 25 ++++++++ src/Observe.php | 13 ++++ src/ObserveAttributeServiceProvider.php | 22 +++++++ src/ObserverRegistrar.php | 82 ++++++++++++++++++++++++ tests/ObserverTest.php | 27 ++++++++ tests/Stubs/EloquentTestObserverStub.php | 16 +++++ tests/Stubs/Models/EloquentModelStub.php | 12 ++++ tests/TestCase.php | 31 +++++++++ 14 files changed, 433 insertions(+) create mode 100644 .github/workflows/code-style.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .php_cs.dist create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 src/Observe.php create mode 100644 src/ObserveAttributeServiceProvider.php create mode 100644 src/ObserverRegistrar.php create mode 100644 tests/ObserverTest.php create mode 100644 tests/Stubs/EloquentTestObserverStub.php create mode 100644 tests/Stubs/Models/EloquentModelStub.php create mode 100644 tests/TestCase.php diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 0000000..9e608e6 --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,29 @@ +name: code style + +on: [push] + +jobs: + php-cs-fixer: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Run PHP CS Fixer + uses: docker://oskarstark/php-cs-fixer-ga:2.19.0 + with: + args: --config=.php_cs.dist --allow-risky=yes + + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v2.3.0 + with: + commit_message: Fix styling + branch: ${{ steps.extract_branch.outputs.branch }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..d423887 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,42 @@ +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [8.0] + laravel: [^8.0] + + name: P${{ matrix.php }} - L${{ matrix.laravel }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip + coverage: none + + - name: Install dependencies + run: composer require "illuminate/contracts=${{ matrix.laravel }}" --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a691947 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +composer.lock +/vendor +.phpunit.result.cache +.php_cs.cache +.DS_Store diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..69fb76e --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,41 @@ +notPath('vendor') + ->in(__DIR__) + ->name('*.php'); + +return PhpCsFixer\Config::create() + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'not_operator_with_successor_space' => true, + 'no_extra_blank_lines' => [ + 'curly_brace_block', + 'extra', + 'parenthesis_brace_block', + 'throw', + 'use', + ], + 'no_unused_imports' => true, + 'ordered_imports' => ['sortAlgorithm' => 'alpha'], + 'ternary_operator_spaces' => true, + 'single_blank_line_before_namespace' => true, + + // PSR-12 + 'blank_line_after_opening_tag' => true, + 'braces' => ['allow_single_line_closure' => true], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'function_typehint_space' => true, + 'new_with_braces' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_whitespace_in_blank_line' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'single_trait_insert_per_statement' => true, + ]) + ->setFinder($finder); diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6839d8 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# PHP 8 attribute to register Laravel model observers. + +Instead of defining [observers](https://laravel.com/docs/8.x/eloquent#observers) inside service providers this package offers an alternative way to register model observers for your Laravel applications. + +## Installation + +Under development. For now make sure to configure the repository in your composer.json by running: + +```bash +composer config repositories.laravel-observe-attribute vcs https://github.com/gpanos/laravel-observe-attribute +``` + +Then install the package by running: + +```bash +composer require gpanos/laravel-observe-attribute +``` + +## Usage + +To register an observer add the `Observe` attribute to your model and pass it your observer class. + +```php + + + + + ./tests/ + + + + + src/ + + + + + + diff --git a/src/Observe.php b/src/Observe.php new file mode 100644 index 0000000..901c582 --- /dev/null +++ b/src/Observe.php @@ -0,0 +1,13 @@ +useRootNamespace(app()->getNamespace()) + ->registerDirectory($this->getDirectory()); + } + + protected function getDirectory(): string + { + $testClassDirectory = __DIR__ . '/../tests/Stubs'; + + return app()->runningUnitTests() ? __DIR__ . '/../tests/Stubs' : app_path('Models'); + } +} diff --git a/src/ObserverRegistrar.php b/src/ObserverRegistrar.php new file mode 100644 index 0000000..e25ecc0 --- /dev/null +++ b/src/ObserverRegistrar.php @@ -0,0 +1,82 @@ +basePath = app()->path(); + } + + public function useBasePath(string $basePath): self + { + $this->basePath = $basePath; + + return $this; + } + + public function useRootNamespace(string $rootNamespace): self + { + $this->rootNamespace = $rootNamespace; + + return $this; + } + + public function registerDirectory(string $directory): void + { + $files = (new Finder())->files()->name('*.php')->in($directory); + + collect($files)->each(fn (SplFileInfo $file) => $this->registerFile($file)); + } + + public function registerFile(string | SplFileInfo $path): void + { + if (is_string($path)) { + $path = new SplFileInfo($path); + } + + $fullyQualifiedClassName = $this->fullQualifiedClassNameFromFile($path); + + $this->processAttributes($fullyQualifiedClassName); + } + + protected function fullQualifiedClassNameFromFile(SplFileInfo $file): string + { + $class = trim(Str::replaceFirst($this->basePath, '', $file->getRealPath()), DIRECTORY_SEPARATOR); + + $class = str_replace( + [DIRECTORY_SEPARATOR, 'App\\'], + ['\\', app()->getNamespace()], + ucfirst(Str::replaceLast('.php', '', $class)) + ); + + return $this->rootNamespace . $class; + } + + protected function processAttributes(string $className): void + { + if (! class_exists($className)) { + return; + } + + $class = new ReflectionClass($className); + + $attributes = $class->getAttributes(Observe::class); + + foreach ($attributes as $attribute) { + $observer = $attribute->newInstance()->observer; + + $className::observe($observer); + } + } +} diff --git a/tests/ObserverTest.php b/tests/ObserverTest.php new file mode 100644 index 0000000..faa8cb2 --- /dev/null +++ b/tests/ObserverTest.php @@ -0,0 +1,27 @@ +shouldReceive('listen')->once()->with('eloquent.creating: ' . EloquentModelStub::class, EloquentTestObserverStub::class . '@creating'); + $events->shouldReceive('listen')->once()->with('eloquent.saved: ' . EloquentModelStub::class, EloquentTestObserverStub::class . '@saved'); + $events->shouldReceive('forget'); + $events->shouldReceive('dispatch'); + + $this + ->observerRegistrar + ->registerDirectory($this->getTestPath('Stubs/Models')); + + EloquentModelStub::flushEventListeners(); + } +} diff --git a/tests/Stubs/EloquentTestObserverStub.php b/tests/Stubs/EloquentTestObserverStub.php new file mode 100644 index 0000000..f3fbc59 --- /dev/null +++ b/tests/Stubs/EloquentTestObserverStub.php @@ -0,0 +1,16 @@ +observerRegistrar = (new ObserverRegistrar()) + ->useBasePath($this->getTestPath()) + ->useRootNamespace('Gpanos\ObserveAttribute\Tests\\'); + } + + public function getTestPath(string $directory = null): string + { + return __DIR__ . ($directory ? '/' . $directory : ''); + } + + protected function getPackageProviders($app) + { + return [ObserveAttributeServiceProvider::class]; + } +}