diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ab3bab --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# IDE generated files # +###################### +.idea + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ad9d33b --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "korridor/laravel-computed-attributes", + "description": "Laravel package that adds computed attributes to eloquent models.", + "keywords": ["laravel", "model", "eloquent", "computed", "attribute", "caching", "performance"], + "homepage": "https://github.com/korridor/laravel-computed-attributes", + "license": "MIT", + "authors": [ + { + "name": "korridor", + "email": "26689068+korridor@users.noreply.github.com" + } + ], + "minimum-stability": "stable", + "require": { + "php": "^7.1|^7.2|^7.3", + "illuminate/support": "^5.6|^5.7|^5.8|^6", + "illuminate/database": "^5.6|^5.7|^5.8|^6", + "illuminate/console": "^5.6|^5.7|^5.8|^6" + }, + "autoload": { + "psr-4": { + "Korridor\\LaravelComputedAttributes\\": "src" + } + }, + "extra": { + "laravel": { + "providers": [ + "Korridor\\LaravelComputedAttributes\\LaravelComputedAttributesServiceProvider" + ] + } + }, + "config": { + "sort-packages": true + } +} diff --git a/license.md b/license.md new file mode 100644 index 0000000..eb3db8d --- /dev/null +++ b/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2019` `korridor` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e4fa174 --- /dev/null +++ b/readme.md @@ -0,0 +1,15 @@ +# Laravel computed attributes + +Warning: This package is still under heavy development. + +## Installation + +You can install the package via composer with following command: + +```bash +composer require korridor/laravel-computed-attributes +``` + +## License + +This package is licensed under the MIT License (MIT). Please see [license file](license.md) for more information. diff --git a/src/ComputedAttributes.php b/src/ComputedAttributes.php new file mode 100644 index 0000000..de60c93 --- /dev/null +++ b/src/ComputedAttributes.php @@ -0,0 +1,41 @@ +{$functionName}(); + + return $value; + } + + /** + * @param string $attributeName + */ + public function setComputedAttributeValue(string $attributeName) + { + $computed = $this->getComputedAttributeValue($attributeName); + $this->{$attributeName} = $computed; + } + + /** + * @return array + */ + public function getComputedAttributeConfiguration() + { + if (isset($this->computed)) { + return $this->computed; + } else { + return []; + } + } +} diff --git a/src/Console/GenerateComputedAttributes.php b/src/Console/GenerateComputedAttributes.php new file mode 100644 index 0000000..c57ddc5 --- /dev/null +++ b/src/Console/GenerateComputedAttributes.php @@ -0,0 +1,154 @@ +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'); + + return false; + } + + // 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); + } + } + } + + // 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'); + + return false; + } + $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); + + return false; + } + } + } + array_push($modelAttributesToProcess, [ + 'model' => $model, + 'modelInstance' => $modelInstance, + 'attributes' => $attributes, + ]); + } + } + + // 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).']'); + if (sizeof($attributes) > 0) { + $modelInstance->chunk($chunkSize, function ($modelResults) use ($attributes) { + /* @var Model|ComputedAttributes $modelInstance */ + foreach ($modelResults as $modelResult) { + foreach ($attributes as $attribute) { + $modelResult->setComputedAttributeValue($attribute); + } + $modelResult->save(); + } + }); + } + } + + return true; + } +} diff --git a/src/LaravelComputedAttributesServiceProvider.php b/src/LaravelComputedAttributesServiceProvider.php new file mode 100644 index 0000000..00cdb05 --- /dev/null +++ b/src/LaravelComputedAttributesServiceProvider.php @@ -0,0 +1,31 @@ +app->runningInConsole()) { + $this->commands([ + Console\GenerateComputedAttributes::class, + ]); + } + } +} diff --git a/tests/Models/Post.php b/tests/Models/Post.php new file mode 100644 index 0000000..fa4c960 --- /dev/null +++ b/tests/Models/Post.php @@ -0,0 +1,35 @@ +setComputedAttributeValue('complex_calculation'); + }); + parent::boot(); + } +}