From 9fc23fbf2487300a98a3b7d22b02f0c8d5fe5e1e Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 15:47:08 +0200
Subject: [PATCH 1/9] Added validation command; Renamed command; Fixed bug in
calculate command; Added tests; Added TravisCI, codecoverage and StyleCI
---
.gitignore | 4 +
.php_cs | 14 ++
.styleci.yml | 1 +
.travis.yml | 83 ++++++++++++
composer.json | 26 +++-
config/computed-attributes.php | 6 +
docker/Dockerfile | 24 ++++
docker/docker-compose.yml | 9 ++
phpcs.xml | 9 ++
phpunit.xml | 38 ++++++
readme.md | 39 +++++-
src/Console/GenerateComputedAttributes.php | 125 ++++++------------
src/Console/ValidateComputedAttributes.php | 111 ++++++++++++++++
...ravelComputedAttributesServiceProvider.php | 14 +-
src/Parser/ModelAttributeParser.php | 106 +++++++++++++++
src/Parser/ModelAttributesEntry.php | 44 ++++++
src/Parser/ParsingException.php | 10 ++
.../GenerateComputedAttributesCommandTest.php | 88 ++++++++++++
tests/TestCase.php | 33 +++++
.../0000_00_00_000000_add_posts_table.php | 36 +++++
.../0000_00_00_000001_add_votes_table.php | 39 ++++++
tests/{ => TestEnvironment}/Models/Post.php | 26 +++-
tests/TestEnvironment/Models/Vote.php | 35 +++++
23 files changed, 827 insertions(+), 93 deletions(-)
create mode 100644 .php_cs
create mode 100644 .styleci.yml
create mode 100644 .travis.yml
create mode 100644 config/computed-attributes.php
create mode 100644 docker/Dockerfile
create mode 100644 docker/docker-compose.yml
create mode 100644 phpcs.xml
create mode 100644 phpunit.xml
create mode 100644 src/Console/ValidateComputedAttributes.php
create mode 100644 src/Parser/ModelAttributeParser.php
create mode 100644 src/Parser/ModelAttributesEntry.php
create mode 100644 src/Parser/ParsingException.php
create mode 100644 tests/Feature/GenerateComputedAttributesCommandTest.php
create mode 100644 tests/TestCase.php
create mode 100644 tests/TestEnvironment/Migrations/0000_00_00_000000_add_posts_table.php
create mode 100644 tests/TestEnvironment/Migrations/0000_00_00_000001_add_votes_table.php
rename tests/{ => TestEnvironment}/Models/Post.php (52%)
create mode 100644 tests/TestEnvironment/Models/Vote.php
diff --git a/.gitignore b/.gitignore
index 2ab3bab..04db3f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@
.Trashes
ehthumbs.db
Thumbs.db
+vendor
+composer.lock
+.php_cs.cache
+.phpunit.result.cache
diff --git a/.php_cs b/.php_cs
new file mode 100644
index 0000000..398a1a5
--- /dev/null
+++ b/.php_cs
@@ -0,0 +1,14 @@
+setRiskyAllowed(false)
+ ->setRules([
+ '@PSR2' => true,
+ ])
+ ->setUsingCache(true)
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ )
+;
diff --git a/.styleci.yml b/.styleci.yml
new file mode 100644
index 0000000..0285f17
--- /dev/null
+++ b/.styleci.yml
@@ -0,0 +1 @@
+preset: laravel
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8a64a41
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,83 @@
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+language: php
+
+matrix:
+ include:
+ # Laravel 5.6.*
+ - php: 7.1
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.2
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.3
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ # Laravel 5.7.*
+ - php: 7.1
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.2
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.3
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ # Laravel 5.8.*
+ - php: 7.1
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.1
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.2
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.2
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.3
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.3
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ # Laravel 6.*
+ - php: 7.2
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.2
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.3
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.3
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable'
+ # Laravel 7.*
+ - php: 7.2
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.2
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.3
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.3
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.4
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.4
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ # Laravel 8.*
+ - php: 7.3
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.3
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable'
+ - php: 7.4
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest'
+ - php: 7.4
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable'
+ fast_finish: true
+
+before_install:
+ - travis_retry composer self-update
+ - travis_retry composer require "laravel/framework:${LARAVEL}" "orchestra/testbench:${TESTBENCH}" --no-interaction --no-update
+
+install:
+ - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction --no-suggest
+
+before_script:
+ - composer config discard-changes true
+
+script:
+ - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
diff --git a/composer.json b/composer.json
index e2d66d7..3332240 100644
--- a/composer.json
+++ b/composer.json
@@ -12,16 +12,34 @@
],
"minimum-stability": "stable",
"require": {
- "php": "^7.1|^7.2|^7.3|^7.4",
- "illuminate/support": "^5.6|^5.7|^5.8|^6|^7",
- "illuminate/database": "^5.6|^5.7|^5.8|^6|^7",
- "illuminate/console": "^5.6|^5.7|^5.8|^6|^7"
+ "php": "^7.1",
+ "composer/composer": "^1.10",
+ "illuminate/console": "^5.6|^6|^7|^8",
+ "illuminate/database": "^5.6|^6|^7|^8",
+ "illuminate/support": "^5.6|^6|^7|^8"
+ },
+ "require-dev": {
+ "orchestra/testbench": "^3.6|^4.0|^5.0|^6.0",
+ "phpunit/phpunit": "^7.0|^8.0|^9.0",
+ "friendsofphp/php-cs-fixer": "^2.16",
+ "squizlabs/php_codesniffer": "^3.5"
},
"autoload": {
"psr-4": {
"Korridor\\LaravelComputedAttributes\\": "src"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "Korridor\\LaravelComputedAttributes\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-coverage": "vendor/bin/phpunit --coverage-html coverage",
+ "fix": "./vendor/bin/php-cs-fixer fix",
+ "lint": "./vendor/bin/phpcs --error-severity=1 --warning-severity=8 --extensions=php"
+ },
"extra": {
"laravel": {
"providers": [
diff --git a/config/computed-attributes.php b/config/computed-attributes.php
new file mode 100644
index 0000000..d5e1732
--- /dev/null
+++ b/config/computed-attributes.php
@@ -0,0 +1,6 @@
+ 'app/Models',
+ 'model_namespace' => 'App\\Models',
+];
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..7dc8b96
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,24 @@
+FROM php:7.4-cli
+
+RUN apt-get update && apt-get install -y \
+ zlib1g-dev \
+ libzip-dev
+
+RUN docker-php-ext-install zip
+
+RUN pecl install xdebug-2.8.1 \
+ && docker-php-ext-enable xdebug
+
+# Install composer and add its bin to the PATH.
+RUN curl -s http://getcomposer.org/installer | php && \
+ echo "export PATH=${PATH}:/var/www/vendor/bin" >> ~/.bashrc && \
+ mv composer.phar /usr/local/bin/composer
+
+# Add bash aliases
+RUN echo "alias ll='ls --color=auto -al'" >> ~/.bashrc
+
+
+# Source the bash
+RUN . ~/.bashrc
+
+WORKDIR /usr/src/app
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..dd54d6e
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,9 @@
+version: '3.7'
+services:
+ workspace:
+ build:
+ context: .
+ volumes:
+ - ..:/usr/src/app
+ tty: true
+ stdin_open: true
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 0000000..4f5006d
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,9 @@
+
+
+ The ASH2 coding standard.
+
+
+
+ src/
+ tests/
+
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..e312d86
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ src/
+
+
+
+
+ tests/Feature
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
index e4fa174..4a64d23 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,10 @@
# Laravel computed attributes
-Warning: This package is still under heavy development.
+[![Latest Version on Packagist](https://img.shields.io/packagist/v/korridor/laravel-computed-attributes?style=flat-square)](https://packagist.org/packages/korridor/laravel-computed-attributes)
+[![License](https://img.shields.io/packagist/l/korridor/laravel-computed-attributes?style=flat-square)](license.md)
+[![TravisCI](https://img.shields.io/travis/korridor/laravel-computed-attributes?style=flat-square)](https://travis-ci.org/korridor/laravel-computed-attributes)
+[![Codecov](https://img.shields.io/codecov/c/github/korridor/laravel-computed-attributes?style=flat-square)](https://codecov.io/gh/korridor/laravel-computed-attributes)
+[![StyleCI](https://styleci.io/repos/226346821/shield)](https://styleci.io/repos/226346821)
## Installation
@@ -10,6 +14,39 @@ You can install the package via composer with following command:
composer require korridor/laravel-computed-attributes
```
+### Requirements
+
+This package is tested for the following Laravel versions:
+
+ - 8.*
+ - 7.*
+ - 6.*
+ - 5.8.*
+ - 5.7.* (stable only)
+ - 5.6.* (stable only)
+
+## Usage examples
+
+
+
+## Contributing
+
+I am open for suggestions and contributions. Just create an issue or a pull request.
+
+### Testing
+
+```bash
+composer test
+composer test-coverage
+```
+
+### Codeformatting/Linting
+
+```bash
+composer fix
+composer lint
+```
+
## License
This package is licensed under the MIT License (MIT). Please see [license file](license.md) for more information.
diff --git a/src/Console/GenerateComputedAttributes.php b/src/Console/GenerateComputedAttributes.php
index c57ddc5..1ea9db3 100644
--- a/src/Console/GenerateComputedAttributes.php
+++ b/src/Console/GenerateComputedAttributes.php
@@ -2,11 +2,14 @@
namespace Korridor\LaravelComputedAttributes\Console;
-use Composer\Autoload\ClassMapGenerator;
use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Log;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
-use ReflectionClass;
+use Korridor\LaravelComputedAttributes\Parser\ModelAttributeParser;
+use Korridor\LaravelComputedAttributes\Parser\ParsingException;
+use Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post;
use ReflectionException;
/**
@@ -19,9 +22,9 @@ class GenerateComputedAttributes extends Command
*
* @var string
*/
- protected $signature = 'computed-attributes:generate '.
- '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} '.
- '{--chunkSize=100 : Size of the model chunk}';
+ protected $signature = 'computed-attributes:generate ' .
+ '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} ' .
+ '{--chunkSize= : Size of the model chunk}';
/**
* The console command description.
@@ -41,104 +44,55 @@ public function __construct()
/**
* Execute the console command.
*
- * @return bool
+ * @return int
* @throws ReflectionException
*/
public function handle()
{
$modelsWithAttributes = $this->argument('modelsAttributes');
- $modelPath = app_path('Models');
- $modelNamespace = 'App\\Models\\';
- $chunkSizeRaw = $this->option('chunkSize');
- if (preg_match('/^\d+$/', $chunkSizeRaw)) {
- $chunkSize = intval($chunkSizeRaw);
- if ($chunkSize < 1) {
- $this->error('Option chunkSize needs to be greater than zero');
- return false;
- }
- } else {
- $this->error('Option chunkSize needs to be an integer');
+ $this->info('Parsing arguments...');
- return false;
- }
+ // Validate and parse modelsAttributes argument
+ $modelAttributeParser = app(ModelAttributeParser::class);
+ try {
+ $modelAttributesEntries = $modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
+ } catch (ParsingException $exception) {
+ var_dump($exception->getMessage());
+ $this->error($exception->getMessage());
- // Get all models with trait
- $classmap = ClassMapGenerator::createMap($modelPath);
- $models = [];
- foreach ($classmap as $class => $filepath) {
- $reflection = new ReflectionClass($class);
- $traits = $reflection->getTraitNames();
- foreach ($traits as $trait) {
- if ('Korridor\\LaravelComputedAttributes\\ComputedAttributes' === $trait) {
- array_push($models, $class);
- }
- }
+ return 1;
}
- // Get all class/attribute combinations
- $modelAttributesToProcess = [];
- if (null === $modelsWithAttributes) {
- $this->info('Start calculating for all models with trait...');
- foreach ($models as $model) {
- /** @var Model|ComputedAttributes $modelInstance */
- $modelInstance = new $model();
- $attributes = $modelInstance->getComputedAttributeConfiguration();
- array_push($modelAttributesToProcess, [
- 'model' => $model,
- 'modelInstance' => $modelInstance,
- 'attributes' => $attributes,
- ]);
- }
- } else {
- $this->info('Start calculating for given models...');
- $modelsInAttribute = explode(';', $modelsWithAttributes);
- foreach ($modelsInAttribute as $modelInAttribute) {
- $modelInAttributeExploded = explode(':', $modelInAttribute);
- if (1 !== sizeof($modelInAttributeExploded) && 2 !== sizeof($modelInAttributeExploded)) {
- $this->error('Parsing error');
-
- return false;
- }
- $model = $modelNamespace.$modelInAttributeExploded[0];
- if (in_array($model, $models)) {
- /** @var Model|ComputedAttributes $modelInstance */
- $modelInstance = new $model();
- } else {
- $this->error('Model "'.$model.'" not found');
+ // Validate chunkSize option
+ $chunkSizeRaw = $this->option('chunkSize');
+ if ($chunkSizeRaw !== null) {
+ if (preg_match('/^\d+$/', $chunkSizeRaw)) {
+ $chunkSize = intval($chunkSizeRaw);
+ if ($chunkSize < 1) {
+ $this->error('Option chunkSize needs to be greater than zero');
- return false;
+ return 1;
}
- $attributes = $modelInstance->getComputedAttributeConfiguration();
- if (2 === sizeof($modelInAttributeExploded)) {
- $attributeWhitelistItems = explode(',', $modelInAttributeExploded[1]);
- foreach ($attributeWhitelistItems as $attributeWhitelistItem) {
- if (in_array($attributeWhitelistItem, $attributes)) {
- } else {
- $this->error('Attribute "'.$attributeWhitelistItem.'" does not exist in model '.$model);
+ } else {
+ $this->error('Option chunkSize needs to be an integer');
- return false;
- }
- }
- }
- array_push($modelAttributesToProcess, [
- 'model' => $model,
- 'modelInstance' => $modelInstance,
- 'attributes' => $attributes,
- ]);
+ return 1;
}
}
// Calculate
- foreach ($modelAttributesToProcess as $modelAttributeToProcess) {
- $this->info('Start calculating for following attributes of model "'.$modelAttributeToProcess['model'].'":');
- /** @var Model|ComputedAttributes $modelInstance */
- $modelInstance = $modelAttributeToProcess['modelInstance'];
- $attributes = $modelAttributeToProcess['attributes'];
- $this->info('['.implode(',', $attributes).']');
+ foreach ($modelAttributesEntries as $modelAttributesEntry) {
+ $model = $modelAttributesEntry->getModel();
+ $this->info('Start calculating for following attributes of model "' . $model . '":');
+
+ /** @var Builder|ComputedAttributes $modelInstance */
+ $modelInstance = new $model();
+ $attributes = $modelAttributesEntry->getAttributes();
+ $this->info('[' . implode(',', $attributes) . ']');
if (sizeof($attributes) > 0) {
$modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes) {
- /* @var Model|ComputedAttributes $modelInstance */
+ /* @var Model|ComputedAttributes $modelResult */
foreach ($modelResults as $modelResult) {
foreach ($attributes as $attribute) {
$modelResult->setComputedAttributeValue($attribute);
@@ -148,7 +102,6 @@ public function handle()
});
}
}
-
- return true;
+ return 0;
}
}
diff --git a/src/Console/ValidateComputedAttributes.php b/src/Console/ValidateComputedAttributes.php
new file mode 100644
index 0000000..bca7417
--- /dev/null
+++ b/src/Console/ValidateComputedAttributes.php
@@ -0,0 +1,111 @@
+modelAttributeParser = $modelAttributeParser;
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return int
+ * @throws ReflectionException
+ */
+ public function handle()
+ {
+ $modelsWithAttributes = $this->argument('modelsAttributes');
+
+ $this->info('Parsing arguments...');
+
+ // Validate and parse modelsAttributes argument
+ try {
+ $modelAttributesEntries = $this->modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
+ } catch (ParsingException $exception) {
+ $this->error($exception->getMessage());
+
+ return 1;
+ }
+
+ // Validate chunkSize option
+ $chunkSizeRaw = $this->option('chunkSize');
+ if ($chunkSizeRaw !== null) {
+ if (preg_match('/^\d+$/', $chunkSizeRaw)) {
+ $chunkSize = intval($chunkSizeRaw);
+ if ($chunkSize < 1) {
+ $this->error('Option chunkSize needs to be greater than zero');
+
+ return 1;
+ }
+ } else {
+ $this->error('Option chunkSize needs to be an integer');
+
+ return 1;
+ }
+ }
+
+ // Validate
+ foreach ($modelAttributesEntries as $modelAttributesEntry) {
+ /** @var Model|ComputedAttributes $modelInstance */
+ $modelInstance = ($modelAttributesEntry->getModel())();
+ $attributes = $modelAttributesEntry->getAttributes();
+
+ $this->info('Start validating for following attributes of model "'.$modelAttributesEntry->getModel().'": '.implode(',', $attributes).'');
+ if (sizeof($attributes) > 0) {
+ $modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes, $modelAttributesEntry) {
+ /* @var Model|ComputedAttributes $modelResult */
+ foreach ($modelResults as $modelResult) {
+ foreach ($attributes as $attribute) {
+ if ($modelResult->getComputedAttributeValue($attribute) !== $modelResult->{$attribute}) {
+ $this->info($modelAttributesEntry->getModel().'['.$modelResult->getKeyName().'='.$modelResult->getKey().']['.$attribute.']');
+ $this->info('Current value: '.$modelResult->{$attribute});
+ $this->info('Calculated value: '.$modelResult->getComputedAttributeValue($attribute));
+ }
+ }
+ }
+ });
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/LaravelComputedAttributesServiceProvider.php b/src/LaravelComputedAttributesServiceProvider.php
index 00cdb05..e3704c0 100644
--- a/src/LaravelComputedAttributesServiceProvider.php
+++ b/src/LaravelComputedAttributesServiceProvider.php
@@ -3,7 +3,7 @@
namespace Korridor\LaravelComputedAttributes;
use Illuminate\Support\ServiceProvider;
-use Korridor\LaravelComputedAttributes\Console\GenerateComputedAttributes;
+use Korridor\LaravelComputedAttributes\Parser\ModelAttributeParser;
/**
* Class LaravelComputedAttributesServiceProvider.
@@ -23,9 +23,21 @@ public function register()
public function boot()
{
if ($this->app->runningInConsole()) {
+ $this->publishes([
+ __DIR__ . '/../config/computed-attributes.php' => config_path('computed-attributes.php'),
+ ], 'computed-attributes-config');
$this->commands([
Console\GenerateComputedAttributes::class,
]);
}
+ $this->app->bind(ModelAttributeParser::class, function () {
+ return new ModelAttributeParser();
+ });
+ if (!$this->app->configurationIsCached()) {
+ $this->mergeConfigFrom(
+ __DIR__ . '/../config/computed-attributes.php',
+ 'computed-attributes-config'
+ );
+ }
}
}
diff --git a/src/Parser/ModelAttributeParser.php b/src/Parser/ModelAttributeParser.php
new file mode 100644
index 0000000..500e2dd
--- /dev/null
+++ b/src/Parser/ModelAttributeParser.php
@@ -0,0 +1,106 @@
+getAbsolutePathOfModelFolder());
+ $models = [];
+ foreach ($classmap as $class => $filepath) {
+ $reflection = new ReflectionClass($class);
+ $traits = $reflection->getTraitNames();
+ foreach ($traits as $trait) {
+ if ('Korridor\\LaravelComputedAttributes\\ComputedAttributes' === $trait) {
+ array_push($models, $class);
+ }
+ }
+ }
+ return $models;
+ }
+
+ /**
+ * @param string|null $modelsWithAttributes
+ * @return ModelAttributesEntry[]
+ * @throws ParsingException
+ * @throws ReflectionException
+ */
+ public function getModelAttributeEntries(?string $modelsWithAttributes = null)
+ {
+ $modelAttributesToProcess = [];
+ $models = $this->getAllModelClasses();
+ if (null === $modelsWithAttributes) {
+ foreach ($models as $model) {
+ /** @var Model|ComputedAttributes $modelInstance */
+ $modelInstance = new $model();
+ $attributes = $modelInstance->getComputedAttributeConfiguration();
+ array_push($modelAttributesToProcess, new ModelAttributesEntry($model, $attributes));
+ }
+ } else {
+ $modelsInAttribute = explode(';', $modelsWithAttributes);
+ foreach ($modelsInAttribute as $modelInAttribute) {
+ $modelInAttributeExploded = explode(':', $modelInAttribute);
+ if (1 !== sizeof($modelInAttributeExploded) && 2 !== sizeof($modelInAttributeExploded)) {
+ throw new ParsingException('Parsing error');
+ }
+ $model = $this->getModelNamespaceBase().str_replace('/', '\\', $modelInAttributeExploded[0]);
+ if (in_array($model, $models)) {
+ /** @var Model|ComputedAttributes $modelInstance */
+ $modelInstance = new $model();
+ } else {
+ throw new ParsingException('Model "'.$model.'" not found');
+ }
+ $attributes = $modelInstance->getComputedAttributeConfiguration();
+ if (2 === sizeof($modelInAttributeExploded)) {
+ $attributeWhitelistItems = explode(',', $modelInAttributeExploded[1]);
+ foreach ($attributeWhitelistItems as $attributeWhitelistItem) {
+ if (!in_array($attributeWhitelistItem, $attributes)) {
+ throw new ParsingException('Attribute "'.$attributeWhitelistItem.
+ '" does not exist in model '.$model);
+ }
+ }
+ $attributes = $attributeWhitelistItems;
+ }
+ array_push($modelAttributesToProcess, new ModelAttributesEntry($model, $attributes));
+ }
+ }
+ return $modelAttributesToProcess;
+ }
+}
diff --git a/src/Parser/ModelAttributesEntry.php b/src/Parser/ModelAttributesEntry.php
new file mode 100644
index 0000000..46014fc
--- /dev/null
+++ b/src/Parser/ModelAttributesEntry.php
@@ -0,0 +1,44 @@
+model = $model;
+ $this->attributes = $attributes;
+ }
+
+ /**
+ * @return string
+ */
+ public function getModel(): string
+ {
+ return $this->model;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+}
diff --git a/src/Parser/ParsingException.php b/src/Parser/ParsingException.php
new file mode 100644
index 0000000..4ca72e6
--- /dev/null
+++ b/src/Parser/ParsingException.php
@@ -0,0 +1,10 @@
+title = 'titleTest';
+ $post->content = 'Text';
+ $post->save();
+ $vote = new Vote();
+ $vote->rating = 4;
+ $vote->post()->associate($post);
+ $vote->save();
+ Config::set('computed-attributes.model_path', 'Models');
+ Config::set('computed-attributes.model_namespace', 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models');
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+
+ // Act
+ $this->artisan('computed-attributes:generate --chunkSize=100')
+ ->expectsOutput('Start calculating for following attributes of model ' .
+ '"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
+ ->expectsOutput('[complex_calculation,sum_of_votes]')
+ ->assertExitCode(0)
+ ->execute();
+
+ // Assert
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => 3,
+ 'sum_of_votes' => 4,
+ ]);
+ }
+
+ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInArgument()
+ {
+ // Arrange
+ $post = new Post();
+ $post->title = 'titleTest';
+ $post->content = 'Text';
+ $post->save();
+ $vote = new Vote();
+ $vote->rating = 4;
+ $vote->post()->associate($post);
+ $vote->save();
+ Config::set('computed-attributes.model_path', 'Models');
+ Config::set('computed-attributes.model_namespace', 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models');
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+
+ // Act
+ $this->artisan('computed-attributes:generate "Post:sum_of_votes" --chunkSize=100')
+ ->expectsOutput('Start calculating for following attributes of model ' .
+ '"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
+ ->expectsOutput('[sum_of_votes]')
+ ->assertExitCode(0)
+ ->execute();
+
+ // Assert
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 4,
+ ]);
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..b497418
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,33 @@
+loadMigrationsFrom(__DIR__ . '/TestEnvironment/Migrations');
+ }
+
+ protected function getEnvironmentSetUp($app)
+ {
+ $app->setBasePath(__DIR__ . '/TestEnvironment');
+ }
+
+ /**
+ * @param Application $app
+ * @return array
+ */
+ protected function getPackageProviders($app)
+ {
+ return [
+ LaravelComputedAttributesServiceProvider::class,
+ ];
+ }
+}
diff --git a/tests/TestEnvironment/Migrations/0000_00_00_000000_add_posts_table.php b/tests/TestEnvironment/Migrations/0000_00_00_000000_add_posts_table.php
new file mode 100644
index 0000000..b5af955
--- /dev/null
+++ b/tests/TestEnvironment/Migrations/0000_00_00_000000_add_posts_table.php
@@ -0,0 +1,36 @@
+increments('id');
+ $table->string('title');
+ $table->text('content');
+ $table->integer('complex_calculation')->nullable();
+ $table->integer('sum_of_votes');
+ $table->softDeletes();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('posts');
+ }
+}
diff --git a/tests/TestEnvironment/Migrations/0000_00_00_000001_add_votes_table.php b/tests/TestEnvironment/Migrations/0000_00_00_000001_add_votes_table.php
new file mode 100644
index 0000000..f7e5ffd
--- /dev/null
+++ b/tests/TestEnvironment/Migrations/0000_00_00_000001_add_votes_table.php
@@ -0,0 +1,39 @@
+increments('id');
+ $table->integer('rating');
+ $table->integer('post_id');
+ $table->foreign('post_id')
+ ->on('posts')
+ ->references('id')
+ ->onUpdate('CASCADE')
+ ->onDelete('RESTRICT');
+ $table->softDeletes();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('votes');
+ }
+}
diff --git a/tests/Models/Post.php b/tests/TestEnvironment/Models/Post.php
similarity index 52%
rename from tests/Models/Post.php
rename to tests/TestEnvironment/Models/Post.php
index fa4c960..c20cbd8 100644
--- a/tests/Models/Post.php
+++ b/tests/TestEnvironment/Models/Post.php
@@ -1,6 +1,9 @@
votes()->sum('rating');
+ }
+
+ /*
+ * Relations
+ */
+
+ /**
+ * @return HasMany|Vote
+ */
+ public function votes(): HasMany
+ {
+ return $this->hasMany(Vote::class);
+ }
+
/**
* Boot function from laravel.
*/
protected static function boot()
{
static::saving(function (Post $model) {
- $model->setComputedAttributeValue('complex_calculation');
+ $model->setComputedAttributeValue('sum_of_votes');
});
parent::boot();
}
diff --git a/tests/TestEnvironment/Models/Vote.php b/tests/TestEnvironment/Models/Vote.php
new file mode 100644
index 0000000..3492230
--- /dev/null
+++ b/tests/TestEnvironment/Models/Vote.php
@@ -0,0 +1,35 @@
+belongsTo(Post::class);
+ }
+
+ /**
+ * Boot function from laravel.
+ */
+ protected static function boot()
+ {
+ /*
+ static::saved(function (Vote $model) {
+ $model->post->setComputedAttributeValue('sum_of_votes');
+ });
+ */
+ parent::boot();
+ }
+}
From b018ea5f0bb297c064011bdfda75dfcc8eff5dad Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 15:50:58 +0200
Subject: [PATCH 2/9] Apply fixes from StyleCI (#2)
Co-authored-by: korridor
---
src/Console/GenerateComputedAttributes.php | 13 ++++++-------
src/LaravelComputedAttributesServiceProvider.php | 6 +++---
src/Parser/ModelAttributeParser.php | 5 +++--
src/Parser/ModelAttributesEntry.php | 1 -
src/Parser/ParsingException.php | 1 -
.../GenerateComputedAttributesCommandTest.php | 8 ++------
tests/TestCase.php | 4 ++--
tests/TestEnvironment/Models/Vote.php | 1 -
8 files changed, 16 insertions(+), 23 deletions(-)
diff --git a/src/Console/GenerateComputedAttributes.php b/src/Console/GenerateComputedAttributes.php
index 1ea9db3..136063d 100644
--- a/src/Console/GenerateComputedAttributes.php
+++ b/src/Console/GenerateComputedAttributes.php
@@ -5,11 +5,9 @@
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
-use Illuminate\Support\Facades\Log;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
use Korridor\LaravelComputedAttributes\Parser\ModelAttributeParser;
use Korridor\LaravelComputedAttributes\Parser\ParsingException;
-use Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post;
use ReflectionException;
/**
@@ -22,8 +20,8 @@ class GenerateComputedAttributes extends Command
*
* @var string
*/
- protected $signature = 'computed-attributes:generate ' .
- '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} ' .
+ protected $signature = 'computed-attributes:generate '.
+ '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} '.
'{--chunkSize= : Size of the model chunk}';
/**
@@ -58,7 +56,7 @@ public function handle()
try {
$modelAttributesEntries = $modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
} catch (ParsingException $exception) {
- var_dump($exception->getMessage());
+ var_dump($exception->getMessage());
$this->error($exception->getMessage());
return 1;
@@ -84,12 +82,12 @@ public function handle()
// Calculate
foreach ($modelAttributesEntries as $modelAttributesEntry) {
$model = $modelAttributesEntry->getModel();
- $this->info('Start calculating for following attributes of model "' . $model . '":');
+ $this->info('Start calculating for following attributes of model "'.$model.'":');
/** @var Builder|ComputedAttributes $modelInstance */
$modelInstance = new $model();
$attributes = $modelAttributesEntry->getAttributes();
- $this->info('[' . implode(',', $attributes) . ']');
+ $this->info('['.implode(',', $attributes).']');
if (sizeof($attributes) > 0) {
$modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes) {
/* @var Model|ComputedAttributes $modelResult */
@@ -102,6 +100,7 @@ public function handle()
});
}
}
+
return 0;
}
}
diff --git a/src/LaravelComputedAttributesServiceProvider.php b/src/LaravelComputedAttributesServiceProvider.php
index e3704c0..b7d7a93 100644
--- a/src/LaravelComputedAttributesServiceProvider.php
+++ b/src/LaravelComputedAttributesServiceProvider.php
@@ -24,7 +24,7 @@ public function boot()
{
if ($this->app->runningInConsole()) {
$this->publishes([
- __DIR__ . '/../config/computed-attributes.php' => config_path('computed-attributes.php'),
+ __DIR__.'/../config/computed-attributes.php' => config_path('computed-attributes.php'),
], 'computed-attributes-config');
$this->commands([
Console\GenerateComputedAttributes::class,
@@ -33,9 +33,9 @@ public function boot()
$this->app->bind(ModelAttributeParser::class, function () {
return new ModelAttributeParser();
});
- if (!$this->app->configurationIsCached()) {
+ if (! $this->app->configurationIsCached()) {
$this->mergeConfigFrom(
- __DIR__ . '/../config/computed-attributes.php',
+ __DIR__.'/../config/computed-attributes.php',
'computed-attributes-config'
);
}
diff --git a/src/Parser/ModelAttributeParser.php b/src/Parser/ModelAttributeParser.php
index 500e2dd..d07f8c8 100644
--- a/src/Parser/ModelAttributeParser.php
+++ b/src/Parser/ModelAttributeParser.php
@@ -10,7 +10,6 @@
class ModelAttributeParser
{
-
/**
* ModelAttributeParser constructor.
*/
@@ -53,6 +52,7 @@ public function getAllModelClasses()
}
}
}
+
return $models;
}
@@ -91,7 +91,7 @@ public function getModelAttributeEntries(?string $modelsWithAttributes = null)
if (2 === sizeof($modelInAttributeExploded)) {
$attributeWhitelistItems = explode(',', $modelInAttributeExploded[1]);
foreach ($attributeWhitelistItems as $attributeWhitelistItem) {
- if (!in_array($attributeWhitelistItem, $attributes)) {
+ if (! in_array($attributeWhitelistItem, $attributes)) {
throw new ParsingException('Attribute "'.$attributeWhitelistItem.
'" does not exist in model '.$model);
}
@@ -101,6 +101,7 @@ public function getModelAttributeEntries(?string $modelsWithAttributes = null)
array_push($modelAttributesToProcess, new ModelAttributesEntry($model, $attributes));
}
}
+
return $modelAttributesToProcess;
}
}
diff --git a/src/Parser/ModelAttributesEntry.php b/src/Parser/ModelAttributesEntry.php
index 46014fc..72eaccc 100644
--- a/src/Parser/ModelAttributesEntry.php
+++ b/src/Parser/ModelAttributesEntry.php
@@ -1,6 +1,5 @@
artisan('computed-attributes:generate --chunkSize=100')
- ->expectsOutput('Start calculating for following attributes of model ' .
+ ->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[complex_calculation,sum_of_votes]')
->assertExitCode(0)
@@ -72,7 +68,7 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
// Act
$this->artisan('computed-attributes:generate "Post:sum_of_votes" --chunkSize=100')
- ->expectsOutput('Start calculating for following attributes of model ' .
+ ->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[sum_of_votes]')
->assertExitCode(0)
diff --git a/tests/TestCase.php b/tests/TestCase.php
index b497418..49669ac 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -12,12 +12,12 @@ public function setUp(): void
{
parent::setUp();
- $this->loadMigrationsFrom(__DIR__ . '/TestEnvironment/Migrations');
+ $this->loadMigrationsFrom(__DIR__.'/TestEnvironment/Migrations');
}
protected function getEnvironmentSetUp($app)
{
- $app->setBasePath(__DIR__ . '/TestEnvironment');
+ $app->setBasePath(__DIR__.'/TestEnvironment');
}
/**
diff --git a/tests/TestEnvironment/Models/Vote.php b/tests/TestEnvironment/Models/Vote.php
index 3492230..d9001d2 100644
--- a/tests/TestEnvironment/Models/Vote.php
+++ b/tests/TestEnvironment/Models/Vote.php
@@ -7,7 +7,6 @@
class Vote extends Model
{
-
/*
* Relations
*/
From e9e8c5af1c175db53febe39487cb6fc7253bca12 Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 15:53:49 +0200
Subject: [PATCH 3/9] Removed var_dump
---
src/Console/GenerateComputedAttributes.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Console/GenerateComputedAttributes.php b/src/Console/GenerateComputedAttributes.php
index 136063d..6145798 100644
--- a/src/Console/GenerateComputedAttributes.php
+++ b/src/Console/GenerateComputedAttributes.php
@@ -56,7 +56,6 @@ public function handle()
try {
$modelAttributesEntries = $modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
} catch (ParsingException $exception) {
- var_dump($exception->getMessage());
$this->error($exception->getMessage());
return 1;
From 73bb3ba1a1f0b06b144869294d306a133cf8cefe Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 15:57:13 +0200
Subject: [PATCH 4/9] Fixed bug
---
src/ComputedAttributes.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ComputedAttributes.php b/src/ComputedAttributes.php
index de60c93..42f930d 100644
--- a/src/ComputedAttributes.php
+++ b/src/ComputedAttributes.php
@@ -2,7 +2,7 @@
namespace Korridor\LaravelComputedAttributes;
-use Str;
+use Illuminate\Support\Str;
trait ComputedAttributes
{
From a4f80f46a6eb1b211cbeadfa752de2734c838b21 Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 16:16:06 +0200
Subject: [PATCH 5/9] Added legacy phpunit config
---
.travis.yml | 54 ++++++++++++++++++++++++-------------------------
phpunit.xml.old | 36 +++++++++++++++++++++++++++++++++
2 files changed, 63 insertions(+), 27 deletions(-)
create mode 100644 phpunit.xml.old
diff --git a/.travis.yml b/.travis.yml
index 8a64a41..0f1b162 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,62 +8,62 @@ matrix:
include:
# Laravel 5.6.*
- php: 7.1
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
# Laravel 5.7.*
- php: 7.1
- env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
# Laravel 5.8.*
- php: 7.1
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.1
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
# Laravel 6.*
- php: 7.2
- env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.3
- env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml'
# Laravel 7.*
- php: 7.2
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.2
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- php: 7.3
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.3
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.4
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.4
- env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='7.*' TESTBENCH='5.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml'
# Laravel 8.*
- php: 7.3
- env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.3
- env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.4
- env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest'
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-lowest' PHP_UNIT_CONFIG='phpunit.xml'
- php: 7.4
- env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable'
+ env: LARAVEL='8.*' TESTBENCH='6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml'
fast_finish: true
before_install:
@@ -77,7 +77,7 @@ before_script:
- composer config discard-changes true
script:
- - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
+ - vendor/bin/phpunit -c ${PHP_UNIT_CONFIG} --coverage-text --coverage-clover=coverage.xml
after_success:
- bash <(curl -s https://codecov.io/bash)
diff --git a/phpunit.xml.old b/phpunit.xml.old
new file mode 100644
index 0000000..e73bb3e
--- /dev/null
+++ b/phpunit.xml.old
@@ -0,0 +1,36 @@
+
+
+
+
+ tests/Feature
+
+
+
+
+ src/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From bec770f86fb805b70dfb1585d690713db4c8ee5e Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 18:25:17 +0200
Subject: [PATCH 6/9] Added tests
---
.travis.yml | 7 -
composer.json | 6 +-
readme.md | 8 +-
src/ComputedAttributes.php | 44 +++++-
src/Console/GenerateComputedAttributes.php | 55 ++++----
src/Console/ValidateComputedAttributes.php | 88 ++++++------
...ravelComputedAttributesServiceProvider.php | 1 +
.../GenerateComputedAttributesCommandTest.php | 48 ++++++-
.../ValidateComputedAttributesCommandTest.php | 133 ++++++++++++++++++
tests/TestEnvironment/Models/Post.php | 58 +++++++-
tests/TestEnvironment/Models/Vote.php | 13 +-
11 files changed, 364 insertions(+), 97 deletions(-)
create mode 100644 tests/Feature/ValidateComputedAttributesCommandTest.php
diff --git a/.travis.yml b/.travis.yml
index 0f1b162..a836213 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,13 +6,6 @@ language: php
matrix:
include:
- # Laravel 5.6.*
- - php: 7.1
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- - php: 7.2
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
- - php: 7.3
- env: LARAVEL='5.6.*' TESTBENCH='3.6.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
# Laravel 5.7.*
- php: 7.1
env: LARAVEL='5.7.*' TESTBENCH='3.7.*' COMPOSER_FLAGS='--prefer-stable' PHP_UNIT_CONFIG='phpunit.xml.old'
diff --git a/composer.json b/composer.json
index 3332240..bf7c0e8 100644
--- a/composer.json
+++ b/composer.json
@@ -14,9 +14,9 @@
"require": {
"php": "^7.1",
"composer/composer": "^1.10",
- "illuminate/console": "^5.6|^6|^7|^8",
- "illuminate/database": "^5.6|^6|^7|^8",
- "illuminate/support": "^5.6|^6|^7|^8"
+ "illuminate/console": "^5.7|^6|^7|^8",
+ "illuminate/database": "^5.7|^6|^7|^8",
+ "illuminate/support": "^5.7|^6|^7|^8"
},
"require-dev": {
"orchestra/testbench": "^3.6|^4.0|^5.0|^6.0",
diff --git a/readme.md b/readme.md
index 4a64d23..c4965e8 100644
--- a/readme.md
+++ b/readme.md
@@ -6,6 +6,11 @@
[![Codecov](https://img.shields.io/codecov/c/github/korridor/laravel-computed-attributes?style=flat-square)](https://codecov.io/gh/korridor/laravel-computed-attributes)
[![StyleCI](https://styleci.io/repos/226346821/shield)](https://styleci.io/repos/226346821)
+Laravel package that adds computed attributes to eloquent models.
+A computed attribute is an accessor where the value is saved in the database.
+The value can be regenerated or validated at any time.
+This can increase performance (no calculation at every get/fetch) and it can simplify querying the database (f.e. complex filter system).
+``
## Installation
You can install the package via composer with following command:
@@ -23,11 +28,10 @@ This package is tested for the following Laravel versions:
- 6.*
- 5.8.*
- 5.7.* (stable only)
- - 5.6.* (stable only)
## Usage examples
-
+See folder `tests/TestEnvironment.
## Contributing
diff --git a/src/ComputedAttributes.php b/src/ComputedAttributes.php
index 42f930d..9989a65 100644
--- a/src/ComputedAttributes.php
+++ b/src/ComputedAttributes.php
@@ -2,35 +2,71 @@
namespace Korridor\LaravelComputedAttributes;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
+/**
+ * @method static Builder|Model computedAttributesValidate(array $attributes)
+ * @method static Builder|Model computedAttributesGenerate(array $attributes)
+ */
trait ComputedAttributes
{
/**
+ * Compute the given attribute and return the result.
+ *
* @param string $attributeName
* @return mixed
*/
public function getComputedAttributeValue(string $attributeName)
{
$functionName = 'get'.Str::studly($attributeName).'Computed';
- $value = $this->{$functionName}();
- return $value;
+ return $this->{$functionName}();
}
/**
+ * Compute the given attribute and assign the result in the model.
+ *
* @param string $attributeName
*/
- public function setComputedAttributeValue(string $attributeName)
+ public function setComputedAttributeValue(string $attributeName): void
{
$computed = $this->getComputedAttributeValue($attributeName);
$this->{$attributeName} = $computed;
}
/**
+ * This scope will be applied during the computed property generation with artisan computed-attributes:generate.
+ *
+ * @param Builder $builder
+ * @param array $attributes Attributes that will be generated.
+ * @return Builder
+ */
+ public function scopeComputedAttributesGenerate(Builder $builder, array $attributes): Builder
+ {
+ return $builder;
+ }
+
+ /**
+ * This scope will be applied during the computed property validation with artisan computed-attributes:validate.
+ *
+ * @param Builder $builder
+ * @param array $attributes Attributes that will be validated.
+ * @return Builder
+ */
+ public function scopeComputedAttributesValidate(Builder $builder, array $attributes): Builder
+ {
+ return $builder;
+ }
+
+ /**
+ * Return the configuration array for this model.
+ * If the configuration array does not exist the function will return an empty array.
+ *
* @return array
*/
- public function getComputedAttributeConfiguration()
+ public function getComputedAttributeConfiguration(): array
{
if (isset($this->computed)) {
return $this->computed;
diff --git a/src/Console/GenerateComputedAttributes.php b/src/Console/GenerateComputedAttributes.php
index 6145798..1496311 100644
--- a/src/Console/GenerateComputedAttributes.php
+++ b/src/Console/GenerateComputedAttributes.php
@@ -21,23 +21,17 @@ class GenerateComputedAttributes extends Command
* @var string
*/
protected $signature = 'computed-attributes:generate '.
- '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} '.
- '{--chunkSize= : Size of the model chunk}';
+ '{modelsAttributes? : List of models and optionally their attributes, '.
+ 'if not given all models that use the ComputedAttributes trait '.
+ '(example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace\OtherModel")} '.
+ '{--chunkSize=500 : Size of the model chunk}';
/**
* The console command description.
*
* @var string
*/
- protected $description = '';
-
- /**
- * Create a new command instance.
- */
- public function __construct()
- {
- parent::__construct();
- }
+ protected $description = '(Re-)generates and saves the given computed attributes.';
/**
* Execute the console command.
@@ -63,40 +57,39 @@ public function handle()
// Validate chunkSize option
$chunkSizeRaw = $this->option('chunkSize');
- if ($chunkSizeRaw !== null) {
- if (preg_match('/^\d+$/', $chunkSizeRaw)) {
- $chunkSize = intval($chunkSizeRaw);
- if ($chunkSize < 1) {
- $this->error('Option chunkSize needs to be greater than zero');
-
- return 1;
- }
- } else {
- $this->error('Option chunkSize needs to be an integer');
+ if (preg_match('/^\d+$/', $chunkSizeRaw)) {
+ $chunkSize = intval($chunkSizeRaw);
+ if ($chunkSize < 1) {
+ $this->error('Option chunkSize needs to be greater than zero');
return 1;
}
+ } else {
+ $this->error('Option chunkSize needs to be an integer greater than zero');
+
+ return 1;
}
// Calculate
foreach ($modelAttributesEntries as $modelAttributesEntry) {
$model = $modelAttributesEntry->getModel();
- $this->info('Start calculating for following attributes of model "'.$model.'":');
-
/** @var Builder|ComputedAttributes $modelInstance */
$modelInstance = new $model();
$attributes = $modelAttributesEntry->getAttributes();
+
+ $this->info('Start calculating for following attributes of model "'.$model.'":');
$this->info('['.implode(',', $attributes).']');
if (sizeof($attributes) > 0) {
- $modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes) {
- /* @var Model|ComputedAttributes $modelResult */
- foreach ($modelResults as $modelResult) {
- foreach ($attributes as $attribute) {
- $modelResult->setComputedAttributeValue($attribute);
+ $modelInstance->computedAttributesGenerate($attributes)
+ ->chunk($chunkSize, function ($modelResults) use ($attributes) {
+ /* @var Model|ComputedAttributes $modelResult */
+ foreach ($modelResults as $modelResult) {
+ foreach ($attributes as $attribute) {
+ $modelResult->setComputedAttributeValue($attribute);
+ }
+ $modelResult->save();
}
- $modelResult->save();
- }
- });
+ });
}
}
diff --git a/src/Console/ValidateComputedAttributes.php b/src/Console/ValidateComputedAttributes.php
index bca7417..b4598fb 100644
--- a/src/Console/ValidateComputedAttributes.php
+++ b/src/Console/ValidateComputedAttributes.php
@@ -3,6 +3,7 @@
namespace Korridor\LaravelComputedAttributes\Console;
use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
use Korridor\LaravelComputedAttributes\Parser\ModelAttributeParser;
@@ -20,30 +21,17 @@ class ValidateComputedAttributes extends Command
* @var string
*/
protected $signature = 'computed-attributes:validate '.
- '{modelsAttributes? : List of models and optionally their attributes (example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace/OtherModel")} '.
- '{--chunkSize= : Size of the model chunk}';
+ '{modelsAttributes? : List of models and optionally their attributes, '.
+ 'if not given all models that use the ComputedAttributes trait '.
+ '(example: "FullModel;PartModel:attribute_1,attribute_2" or "OtherNamespace/OtherModel")} '.
+ '{--chunkSize=500 : Size of the model chunk}';
/**
* The console command description.
*
* @var string
*/
- protected $description = '';
-
- /**
- * @var ModelAttributeParser
- */
- private $modelAttributeParser;
-
- /**
- * Create a new command instance.
- * @param ModelAttributeParser $modelAttributeParser
- */
- public function __construct(ModelAttributeParser $modelAttributeParser)
- {
- $this->modelAttributeParser = $modelAttributeParser;
- parent::__construct();
- }
+ protected $description = 'Validates the current values of the given computed attributes.';
/**
* Execute the console command.
@@ -58,8 +46,9 @@ public function handle()
$this->info('Parsing arguments...');
// Validate and parse modelsAttributes argument
+ $modelAttributeParser = app(ModelAttributeParser::class);
try {
- $modelAttributesEntries = $this->modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
+ $modelAttributesEntries = $modelAttributeParser->getModelAttributeEntries($modelsWithAttributes);
} catch (ParsingException $exception) {
$this->error($exception->getMessage());
@@ -68,44 +57,59 @@ public function handle()
// Validate chunkSize option
$chunkSizeRaw = $this->option('chunkSize');
- if ($chunkSizeRaw !== null) {
- if (preg_match('/^\d+$/', $chunkSizeRaw)) {
- $chunkSize = intval($chunkSizeRaw);
- if ($chunkSize < 1) {
- $this->error('Option chunkSize needs to be greater than zero');
-
- return 1;
- }
- } else {
- $this->error('Option chunkSize needs to be an integer');
+ if (preg_match('/^\d+$/', $chunkSizeRaw)) {
+ $chunkSize = intval($chunkSizeRaw);
+ if ($chunkSize < 1) {
+ $this->error('Option chunkSize needs to be greater than zero');
return 1;
}
+ } else {
+ $this->error('Option chunkSize needs to be an integer greater than zero');
+
+ return 1;
}
// Validate
foreach ($modelAttributesEntries as $modelAttributesEntry) {
- /** @var Model|ComputedAttributes $modelInstance */
- $modelInstance = ($modelAttributesEntry->getModel())();
+ $model = $modelAttributesEntry->getModel();
+ /** @var Builder|ComputedAttributes $modelInstance */
+ $modelInstance = new $model();
$attributes = $modelAttributesEntry->getAttributes();
- $this->info('Start validating for following attributes of model "'.$modelAttributesEntry->getModel().'": '.implode(',', $attributes).'');
+ $this->info('Start validating following attributes of model "'.$model.'":');
+ $this->info('['.implode(',', $attributes).']');
if (sizeof($attributes) > 0) {
- $modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes, $modelAttributesEntry) {
- /* @var Model|ComputedAttributes $modelResult */
- foreach ($modelResults as $modelResult) {
- foreach ($attributes as $attribute) {
- if ($modelResult->getComputedAttributeValue($attribute) !== $modelResult->{$attribute}) {
- $this->info($modelAttributesEntry->getModel().'['.$modelResult->getKeyName().'='.$modelResult->getKey().']['.$attribute.']');
- $this->info('Current value: '.$modelResult->{$attribute});
- $this->info('Calculated value: '.$modelResult->getComputedAttributeValue($attribute));
+ $modelInstance->computedAttributesValidate($attributes)
+ ->chunk($chunkSize, function ($modelResults) use ($attributes, $modelAttributesEntry) {
+ /* @var Model|ComputedAttributes $modelResult */
+ foreach ($modelResults as $modelResult) {
+ foreach ($attributes as $attribute) {
+ if ($modelResult->getComputedAttributeValue($attribute) !== $modelResult->{$attribute}) {
+ $this->info($modelAttributesEntry->getModel().
+ '['.$modelResult->getKeyName().'='.$modelResult->getKey().']['.$attribute.']');
+ $this->info('Current value: '.$this->varToString($modelResult->{$attribute}));
+ $this->info('Calculated value: '.
+ $this->varToString($modelResult->getComputedAttributeValue($attribute)));
+ }
}
}
- }
- });
+ });
}
}
return true;
}
+
+ /**
+ * @param $var
+ * @return false|string
+ */
+ private function varToString($var)
+ {
+ if ($var === null) {
+ return 'null';
+ }
+ return gettype($var).'('.$var.')';
+ }
}
diff --git a/src/LaravelComputedAttributesServiceProvider.php b/src/LaravelComputedAttributesServiceProvider.php
index b7d7a93..6e5e138 100644
--- a/src/LaravelComputedAttributesServiceProvider.php
+++ b/src/LaravelComputedAttributesServiceProvider.php
@@ -28,6 +28,7 @@ public function boot()
], 'computed-attributes-config');
$this->commands([
Console\GenerateComputedAttributes::class,
+ Console\ValidateComputedAttributes::class,
]);
}
$this->app->bind(ModelAttributeParser::class, function () {
diff --git a/tests/Feature/GenerateComputedAttributesCommandTest.php b/tests/Feature/GenerateComputedAttributesCommandTest.php
index 9cc8b83..80de23d 100644
--- a/tests/Feature/GenerateComputedAttributesCommandTest.php
+++ b/tests/Feature/GenerateComputedAttributesCommandTest.php
@@ -24,7 +24,10 @@ public function testCommandComputesAttributesForAllModelsWithTraitAndAllThereAtt
$vote->post()->associate($post);
$vote->save();
Config::set('computed-attributes.model_path', 'Models');
- Config::set('computed-attributes.model_namespace', 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models');
+ Config::set(
+ 'computed-attributes.model_namespace',
+ 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models'
+ );
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'complex_calculation' => null,
@@ -32,7 +35,9 @@ public function testCommandComputesAttributesForAllModelsWithTraitAndAllThereAtt
]);
// Act
- $this->artisan('computed-attributes:generate --chunkSize=100')
+ $this->artisan('computed-attributes:generate', [
+ 'modelsAttributes' => null,
+ ])
->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[complex_calculation,sum_of_votes]')
@@ -59,7 +64,10 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
$vote->post()->associate($post);
$vote->save();
Config::set('computed-attributes.model_path', 'Models');
- Config::set('computed-attributes.model_namespace', 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models');
+ Config::set(
+ 'computed-attributes.model_namespace',
+ 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models'
+ );
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'complex_calculation' => null,
@@ -67,7 +75,9 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
]);
// Act
- $this->artisan('computed-attributes:generate "Post:sum_of_votes" --chunkSize=100')
+ $this->artisan('computed-attributes:generate', [
+ 'modelsAttributes' => 'Post:sum_of_votes',
+ ])
->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[sum_of_votes]')
@@ -81,4 +91,34 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
'sum_of_votes' => 4,
]);
}
+
+ public function testNonNumericChunkSizeIsReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:generate', [
+ '--chunkSize' => 'text'
+ ])
+ ->expectsOutput('Option chunkSize needs to be an integer greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
+
+ public function testNegativeChunkSizeReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:generate', [
+ '--chunkSize' => '-10'
+ ])
+ ->expectsOutput('Option chunkSize needs to be an integer greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
+
+ public function testZeroAsChunkSizeReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:generate', [
+ '--chunkSize' => '0'
+ ])
+ ->expectsOutput('Option chunkSize needs to be greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
}
diff --git a/tests/Feature/ValidateComputedAttributesCommandTest.php b/tests/Feature/ValidateComputedAttributesCommandTest.php
new file mode 100644
index 0000000..7da75d2
--- /dev/null
+++ b/tests/Feature/ValidateComputedAttributesCommandTest.php
@@ -0,0 +1,133 @@
+title = 'titleTest';
+ $post->content = 'Text';
+ $post->save();
+ $vote = new Vote();
+ $vote->rating = 4;
+ $vote->post()->associate($post);
+ $vote->save();
+ Config::set('computed-attributes.model_path', 'Models');
+ Config::set(
+ 'computed-attributes.model_namespace',
+ 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models'
+ );
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+
+ // Act
+ $this->artisan('computed-attributes:validate', [
+ 'modelsAttributes' => null,
+ ])
+ ->expectsOutput('Start validating following attributes of model '.
+ '"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
+ ->expectsOutput('[complex_calculation,sum_of_votes]')
+ ->expectsOutput('Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post[id=1][complex_calculation]')
+ ->expectsOutput('Current value: null')
+ ->expectsOutput('Calculated value: integer(3)')
+ ->expectsOutput('Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post[id=1][sum_of_votes]')
+ ->expectsOutput('Current value: integer(0)')
+ ->expectsOutput('Calculated value: integer(4)')
+ ->assertExitCode(0)
+ ->execute();
+
+ // Assert
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+ }
+
+ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInArgument()
+ {
+ // Arrange
+ $post = new Post();
+ $post->title = 'titleTest';
+ $post->content = 'Text';
+ $post->save();
+ $vote = new Vote();
+ $vote->rating = 4;
+ $vote->post()->associate($post);
+ $vote->save();
+ Config::set('computed-attributes.model_path', 'Models');
+ Config::set(
+ 'computed-attributes.model_namespace',
+ 'Korridor\\LaravelComputedAttributes\\Tests\\TestEnvironment\\Models'
+ );
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+
+ // Act
+ $this->artisan('computed-attributes:validate', [
+ 'modelsAttributes' => 'Post:sum_of_votes',
+ ])
+ ->expectsOutput('Start validating following attributes of model '.
+ '"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
+ ->expectsOutput('[sum_of_votes]')
+ ->expectsOutput('Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post[id=1][sum_of_votes]')
+ ->expectsOutput('Current value: integer(0)')
+ ->expectsOutput('Calculated value: integer(4)')
+ ->assertExitCode(0)
+ ->execute();
+
+ // Assert
+ $this->assertDatabaseHas('posts', [
+ 'id' => $post->id,
+ 'complex_calculation' => null,
+ 'sum_of_votes' => 0,
+ ]);
+ }
+
+ public function testNonNumericChunkSizeIsReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:validate', [
+ '--chunkSize' => 'text'
+ ])
+ ->expectsOutput('Option chunkSize needs to be an integer greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
+
+ public function testNegativeChunkSizeReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:validate', [
+ '--chunkSize' => '-10'
+ ])
+ ->expectsOutput('Option chunkSize needs to be an integer greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
+
+ public function testZeroAsChunkSizeReturnsErrorMessage()
+ {
+ $this->artisan('computed-attributes:validate', [
+ '--chunkSize' => '0'
+ ])
+ ->expectsOutput('Option chunkSize needs to be greater than zero')
+ ->assertExitCode(1)
+ ->execute();
+ }
+}
diff --git a/tests/TestEnvironment/Models/Post.php b/tests/TestEnvironment/Models/Post.php
index c20cbd8..480a61d 100644
--- a/tests/TestEnvironment/Models/Post.php
+++ b/tests/TestEnvironment/Models/Post.php
@@ -2,6 +2,7 @@
namespace Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
@@ -11,6 +12,9 @@ class Post extends Model
use ComputedAttributes;
/**
+ * The attributes that are computed. (f.e. for performance reasons)
+ * These attributes can be regenerated at any time.
+ *
* @var array
*/
protected $computed = [
@@ -18,10 +22,24 @@ class Post extends Model
'sum_of_votes',
];
+ /**
+ * The attributes that should be cast to native types.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'complex_calculation' => 'int',
+ 'sum_of_votes' => 'int',
+ ];
+
+ /*
+ * Computed attributes.
+ */
+
/**
* @return int
*/
- public function getComplexCalculationComputed()
+ public function getComplexCalculationComputed(): int
{
return 1 + 2;
}
@@ -29,9 +47,43 @@ public function getComplexCalculationComputed()
/**
* @return int
*/
- public function getSumOfVotesComputed()
+ public function getSumOfVotesComputed(): int
+ {
+ return $this->votes->sum('rating');
+ }
+
+ /*
+ * Scopes.
+ */
+
+ /**
+ * This scope will be applied during the computed property generation with artisan computed-attributes:generate.
+ *
+ * @param Builder $builder
+ * @param array $attributes Attributes that will be generated.
+ * @return Builder
+ */
+ public function scopeComputedAttributesGenerate(Builder $builder, array $attributes): Builder
+ {
+ if (in_array('sum_of_votes', $attributes)) {
+ return $builder->with('votes');
+ }
+ return $builder;
+ }
+
+ /**
+ * This scope will be applied during the computed property validation with artisan computed-attributes:validate.
+ *
+ * @param Builder $builder
+ * @param array $attributes Attributes that will be validated.
+ * @return Builder
+ */
+ public function scopeComputedAttributesValidate(Builder $builder, array $attributes): Builder
{
- return $this->votes()->sum('rating');
+ if (in_array('sum_of_votes', $attributes)) {
+ return $builder->with('votes');
+ }
+ return $builder;
}
/*
diff --git a/tests/TestEnvironment/Models/Vote.php b/tests/TestEnvironment/Models/Vote.php
index d9001d2..8a24306 100644
--- a/tests/TestEnvironment/Models/Vote.php
+++ b/tests/TestEnvironment/Models/Vote.php
@@ -7,6 +7,16 @@
class Vote extends Model
{
+
+ /**
+ * The attributes that should be cast to native types.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'rating' => 'int',
+ ];
+
/*
* Relations
*/
@@ -25,10 +35,11 @@ public function post(): BelongsTo
protected static function boot()
{
/*
+ Note: This listener is only commented out to test the commands on incorrect data.
static::saved(function (Vote $model) {
$model->post->setComputedAttributeValue('sum_of_votes');
});
- */
+ */
parent::boot();
}
}
From d4ccd1580e40a39abea973d1f0810c918bd219e4 Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 18:28:02 +0200
Subject: [PATCH 7/9] Apply fixes from StyleCI (#3)
Co-authored-by: korridor
---
src/Console/ValidateComputedAttributes.php | 1 +
.../GenerateComputedAttributesCommandTest.php | 20 +++++++++----------
.../ValidateComputedAttributesCommandTest.php | 20 +++++++++----------
tests/TestEnvironment/Models/Post.php | 2 ++
tests/TestEnvironment/Models/Vote.php | 1 -
5 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/src/Console/ValidateComputedAttributes.php b/src/Console/ValidateComputedAttributes.php
index b4598fb..fcbcf1b 100644
--- a/src/Console/ValidateComputedAttributes.php
+++ b/src/Console/ValidateComputedAttributes.php
@@ -110,6 +110,7 @@ private function varToString($var)
if ($var === null) {
return 'null';
}
+
return gettype($var).'('.$var.')';
}
}
diff --git a/tests/Feature/GenerateComputedAttributesCommandTest.php b/tests/Feature/GenerateComputedAttributesCommandTest.php
index 80de23d..6714a9c 100644
--- a/tests/Feature/GenerateComputedAttributesCommandTest.php
+++ b/tests/Feature/GenerateComputedAttributesCommandTest.php
@@ -36,8 +36,8 @@ public function testCommandComputesAttributesForAllModelsWithTraitAndAllThereAtt
// Act
$this->artisan('computed-attributes:generate', [
- 'modelsAttributes' => null,
- ])
+ 'modelsAttributes' => null,
+ ])
->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[complex_calculation,sum_of_votes]')
@@ -76,8 +76,8 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
// Act
$this->artisan('computed-attributes:generate', [
- 'modelsAttributes' => 'Post:sum_of_votes',
- ])
+ 'modelsAttributes' => 'Post:sum_of_votes',
+ ])
->expectsOutput('Start calculating for following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[sum_of_votes]')
@@ -95,8 +95,8 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
public function testNonNumericChunkSizeIsReturnsErrorMessage()
{
$this->artisan('computed-attributes:generate', [
- '--chunkSize' => 'text'
- ])
+ '--chunkSize' => 'text',
+ ])
->expectsOutput('Option chunkSize needs to be an integer greater than zero')
->assertExitCode(1)
->execute();
@@ -105,8 +105,8 @@ public function testNonNumericChunkSizeIsReturnsErrorMessage()
public function testNegativeChunkSizeReturnsErrorMessage()
{
$this->artisan('computed-attributes:generate', [
- '--chunkSize' => '-10'
- ])
+ '--chunkSize' => '-10',
+ ])
->expectsOutput('Option chunkSize needs to be an integer greater than zero')
->assertExitCode(1)
->execute();
@@ -115,8 +115,8 @@ public function testNegativeChunkSizeReturnsErrorMessage()
public function testZeroAsChunkSizeReturnsErrorMessage()
{
$this->artisan('computed-attributes:generate', [
- '--chunkSize' => '0'
- ])
+ '--chunkSize' => '0',
+ ])
->expectsOutput('Option chunkSize needs to be greater than zero')
->assertExitCode(1)
->execute();
diff --git a/tests/Feature/ValidateComputedAttributesCommandTest.php b/tests/Feature/ValidateComputedAttributesCommandTest.php
index 7da75d2..632d7cb 100644
--- a/tests/Feature/ValidateComputedAttributesCommandTest.php
+++ b/tests/Feature/ValidateComputedAttributesCommandTest.php
@@ -36,8 +36,8 @@ public function testCommandComputesAttributesForAllModelsWithTraitAndAllThereAtt
// Act
$this->artisan('computed-attributes:validate', [
- 'modelsAttributes' => null,
- ])
+ 'modelsAttributes' => null,
+ ])
->expectsOutput('Start validating following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[complex_calculation,sum_of_votes]')
@@ -82,8 +82,8 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
// Act
$this->artisan('computed-attributes:validate', [
- 'modelsAttributes' => 'Post:sum_of_votes',
- ])
+ 'modelsAttributes' => 'Post:sum_of_votes',
+ ])
->expectsOutput('Start validating following attributes of model '.
'"Korridor\LaravelComputedAttributes\Tests\TestEnvironment\Models\Post":')
->expectsOutput('[sum_of_votes]')
@@ -104,8 +104,8 @@ public function testCommandCanOnlyCalculateOneAttributeOfOneModelIfSpecifiedInAr
public function testNonNumericChunkSizeIsReturnsErrorMessage()
{
$this->artisan('computed-attributes:validate', [
- '--chunkSize' => 'text'
- ])
+ '--chunkSize' => 'text',
+ ])
->expectsOutput('Option chunkSize needs to be an integer greater than zero')
->assertExitCode(1)
->execute();
@@ -114,8 +114,8 @@ public function testNonNumericChunkSizeIsReturnsErrorMessage()
public function testNegativeChunkSizeReturnsErrorMessage()
{
$this->artisan('computed-attributes:validate', [
- '--chunkSize' => '-10'
- ])
+ '--chunkSize' => '-10',
+ ])
->expectsOutput('Option chunkSize needs to be an integer greater than zero')
->assertExitCode(1)
->execute();
@@ -124,8 +124,8 @@ public function testNegativeChunkSizeReturnsErrorMessage()
public function testZeroAsChunkSizeReturnsErrorMessage()
{
$this->artisan('computed-attributes:validate', [
- '--chunkSize' => '0'
- ])
+ '--chunkSize' => '0',
+ ])
->expectsOutput('Option chunkSize needs to be greater than zero')
->assertExitCode(1)
->execute();
diff --git a/tests/TestEnvironment/Models/Post.php b/tests/TestEnvironment/Models/Post.php
index 480a61d..ad67d3d 100644
--- a/tests/TestEnvironment/Models/Post.php
+++ b/tests/TestEnvironment/Models/Post.php
@@ -68,6 +68,7 @@ public function scopeComputedAttributesGenerate(Builder $builder, array $attribu
if (in_array('sum_of_votes', $attributes)) {
return $builder->with('votes');
}
+
return $builder;
}
@@ -83,6 +84,7 @@ public function scopeComputedAttributesValidate(Builder $builder, array $attribu
if (in_array('sum_of_votes', $attributes)) {
return $builder->with('votes');
}
+
return $builder;
}
diff --git a/tests/TestEnvironment/Models/Vote.php b/tests/TestEnvironment/Models/Vote.php
index 8a24306..fd9988a 100644
--- a/tests/TestEnvironment/Models/Vote.php
+++ b/tests/TestEnvironment/Models/Vote.php
@@ -7,7 +7,6 @@
class Vote extends Model
{
-
/**
* The attributes that should be cast to native types.
*
From fdfa6c6d4494537b37e6028cfc94dae486a81cd2 Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Fri, 25 Sep 2020 23:33:42 +0200
Subject: [PATCH 8/9] Fixed bug in ValidateComputedAttributes command
---
src/Console/ValidateComputedAttributes.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Console/ValidateComputedAttributes.php b/src/Console/ValidateComputedAttributes.php
index fcbcf1b..b1d922c 100644
--- a/src/Console/ValidateComputedAttributes.php
+++ b/src/Console/ValidateComputedAttributes.php
@@ -98,7 +98,7 @@ public function handle()
}
}
- return true;
+ return 0;
}
/**
From 29d314cf5562ae66e337ed3bb6505972592d8e6a Mon Sep 17 00:00:00 2001
From: korridor <26689068+korridor@users.noreply.github.com>
Date: Sat, 26 Sep 2020 00:29:23 +0200
Subject: [PATCH 9/9] Fixed bugs
---
readme.md | 2 +-
src/LaravelComputedAttributesServiceProvider.php | 4 ++--
src/Parser/ModelAttributeParser.php | 5 +++--
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/readme.md b/readme.md
index c4965e8..2629383 100644
--- a/readme.md
+++ b/readme.md
@@ -31,7 +31,7 @@ This package is tested for the following Laravel versions:
## Usage examples
-See folder `tests/TestEnvironment.
+See folder `tests/TestEnvironment`.
## Contributing
diff --git a/src/LaravelComputedAttributesServiceProvider.php b/src/LaravelComputedAttributesServiceProvider.php
index 6e5e138..5d56e70 100644
--- a/src/LaravelComputedAttributesServiceProvider.php
+++ b/src/LaravelComputedAttributesServiceProvider.php
@@ -25,7 +25,7 @@ public function boot()
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/computed-attributes.php' => config_path('computed-attributes.php'),
- ], 'computed-attributes-config');
+ ], 'computed-attributes');
$this->commands([
Console\GenerateComputedAttributes::class,
Console\ValidateComputedAttributes::class,
@@ -37,7 +37,7 @@ public function boot()
if (! $this->app->configurationIsCached()) {
$this->mergeConfigFrom(
__DIR__.'/../config/computed-attributes.php',
- 'computed-attributes-config'
+ 'computed-attributes'
);
}
}
diff --git a/src/Parser/ModelAttributeParser.php b/src/Parser/ModelAttributeParser.php
index d07f8c8..a0f38e5 100644
--- a/src/Parser/ModelAttributeParser.php
+++ b/src/Parser/ModelAttributeParser.php
@@ -4,6 +4,7 @@
use Composer\Autoload\ClassMapGenerator;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Config;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
use ReflectionClass;
use ReflectionException;
@@ -22,7 +23,7 @@ public function __construct()
*/
public function getAbsolutePathOfModelFolder(): string
{
- return base_path(config('computed-attributes.model_path'));
+ return base_path(Config::get('computed-attributes.model_path'));
}
/**
@@ -30,7 +31,7 @@ public function getAbsolutePathOfModelFolder(): string
*/
public function getModelNamespaceBase(): string
{
- return config('computed-attributes.model_namespace').'\\';
+ return Config::get('computed-attributes.model_namespace').'\\';
}
/**