Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
korridor committed Dec 6, 2019
0 parents commit 64ff01e
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# IDE generated files #
######################
.idea

# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
35 changes: 35 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -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
}
}
25 changes: 25 additions & 0 deletions license.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -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.
41 changes: 41 additions & 0 deletions src/ComputedAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Korridor\LaravelComputedAttributes;

use Str;

trait ComputedAttributes
{
/**
* @param string $attributeName
* @return mixed
*/
public function getComputedAttributeValue(string $attributeName)
{
$functionName = 'get'.Str::studly($attributeName).'Computed';
$value = $this->{$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 [];
}
}
}
154 changes: 154 additions & 0 deletions src/Console/GenerateComputedAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

namespace Korridor\LaravelComputedAttributes\Console;

use Composer\Autoload\ClassMapGenerator;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Korridor\LaravelComputedAttributes\ComputedAttributes;
use ReflectionClass;
use ReflectionException;

/**
* Class GenerateComputedAttributes.
*/
class GenerateComputedAttributes extends Command
{
/**
* The name and signature of the console 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}';

/**
* The console command description.
*
* @var string
*/
protected $description = '';

/**
* Create a new command instance.
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return bool
* @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');

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;
}
}
31 changes: 31 additions & 0 deletions src/LaravelComputedAttributesServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Korridor\LaravelComputedAttributes;

use Illuminate\Support\ServiceProvider;
use Korridor\LaravelComputedAttributes\Console\GenerateComputedAttributes;

/**
* Class LaravelComputedAttributesServiceProvider.
*/
class LaravelComputedAttributesServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register()
{
}

/**
* Bootstrap services.
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
Console\GenerateComputedAttributes::class,
]);
}
}
}
35 changes: 35 additions & 0 deletions tests/Models/Post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use Illuminate\Database\Eloquent\Model;
use Korridor\LaravelComputedAttributes\ComputedAttributes;

class Post extends Model
{
use ComputedAttributes;

/**
* @var array
*/
protected $computed = [
'complex_calculation',
];

/**
* @return int
*/
public function getComplexCalculationComputed()
{
return 1 + 2;
}

/**
* Boot function from laravel.
*/
protected static function boot()
{
static::saving(function (Post $model) {
$model->setComputedAttributeValue('complex_calculation');
});
parent::boot();
}
}

0 comments on commit 64ff01e

Please sign in to comment.