-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #112 from kirkbushell/release/11.0
Eloquence 11.0
- Loading branch information
Showing
60 changed files
with
1,185 additions
and
967 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ composer.lock | |
.phpunit.result.cache | ||
.DS_Store | ||
.idea* | ||
.php-cs-fixer.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
return [ | ||
'logging' => [ | ||
'enabled' => env('ELOQUENCE_LOGGING_ENABLED', false), | ||
'driver' => env('ELOQUENCE_LOGGING_DRIVER', env('LOG_CHANNEL', 'stack')), | ||
] | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
namespace Eloquence\Behaviours; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\Relations\Relation; | ||
|
||
class CacheConfig | ||
{ | ||
public function __construct(readonly string $relationName, readonly string $aggregateField, readonly string $sourceField = 'id') | ||
{ | ||
} | ||
|
||
/** | ||
* Returns the actual Relation object - such as BelongsTo. This method makes a call to the relationship | ||
* method specified on the model object, and is used to infer data about the relationship. | ||
*/ | ||
public function relation(Model $model): Relation | ||
{ | ||
return $model->{$this->relationName}(); | ||
} | ||
|
||
/** | ||
* Returns the current related model. | ||
*/ | ||
public function relatedModel(Model $model): Model | ||
{ | ||
return $model->{$this->relationName}; | ||
} | ||
|
||
/** | ||
* Returns -a- related model object - this object is actually empty, and is found on the query builder, used to | ||
* infer certain information abut the relationship that cannot be found on CacheConfig::relation. | ||
*/ | ||
public function emptyRelatedModel(Model $model): Model | ||
{ | ||
return $this->relation($model)->getModel(); | ||
} | ||
|
||
/** | ||
* Returns the related model class name. | ||
*/ | ||
public function relatedModelClass($model): string | ||
{ | ||
return get_class($this->emptyRelatedModel($model)); | ||
} | ||
|
||
public function foreignKeyName(Model $model): string | ||
{ | ||
return $this->relation($model)->getForeignKeyName(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,144 +1,109 @@ | ||
<?php | ||
|
||
namespace Eloquence\Behaviours; | ||
|
||
use Closure; | ||
use Eloquence\Behaviours\SumCache\SummedBy; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Support\Facades\DB; | ||
use Illuminate\Support\Str; | ||
use ReflectionClass; | ||
use ReflectionMethod; | ||
use Tests\Acceptance\Models\User; | ||
|
||
/** | ||
* The cacheable trait is concerned with the related models. | ||
*/ | ||
trait Cacheable | ||
{ | ||
|
||
/** | ||
* Updates a table's record based on the query information provided in the $config variable. | ||
* Allows cacheable to work with implementors and their unique relationship methods. | ||
* | ||
* @param array $config | ||
* @param string $operation Whether to increase or decrease a value. Valid values: +/- | ||
* @param int|float|double $amount | ||
* @param string $foreignKey | ||
* @return array | ||
*/ | ||
public function updateCacheRecord(array $config, $operation, $amount, $foreignKey) | ||
{ | ||
if (is_null($foreignKey)) { | ||
return; | ||
} | ||
|
||
$config = $this->processConfig($config); | ||
|
||
$sql = DB::table($config['table'])->where($config['key'], $foreignKey); | ||
|
||
/* | ||
* Increment for + operator | ||
*/ | ||
if ($operation == '+') { | ||
return $sql->increment($config['field'], $amount); | ||
} | ||
|
||
/* | ||
* Decrement for - operator | ||
*/ | ||
return $sql->decrement($config['field'], $amount); | ||
} | ||
abstract private function configuration(): array; | ||
|
||
/** | ||
* Rebuilds the cache for the records in question. | ||
* | ||
* @param array $config | ||
* @param Model $model | ||
* @param $command | ||
* @param null $aggregateField | ||
* @return mixed | ||
* Helper method for easier use of the implementing classes. | ||
*/ | ||
public function rebuildCacheRecord(array $config, Model $model, $command, $aggregateField = null) | ||
public static function for(Model $model): self | ||
{ | ||
$config = $this->processConfig($config); | ||
$table = $this->getModelTable($model); | ||
|
||
if (is_null($aggregateField)) { | ||
$aggregateField = $config['foreignKey']; | ||
} else { | ||
$aggregateField = Str::snake($aggregateField); | ||
} | ||
|
||
$sql = DB::table($table)->select($config['foreignKey'])->groupBy($config['foreignKey']); | ||
|
||
if (strtolower($command) == 'count') { | ||
$aggregate = $sql->count($aggregateField); | ||
} else if (strtolower($command) == 'sum') { | ||
$aggregate = $sql->sum($aggregateField); | ||
} else if (strtolower($command) == 'avg') { | ||
$aggregate = $sql->avg($aggregateField); | ||
} else { | ||
$aggregate = null; | ||
} | ||
return new self($model); | ||
} | ||
|
||
return DB::table($config['table']) | ||
->update([ | ||
$config['field'] => $aggregate | ||
]); | ||
public function reflect(string $attributeClass, \Closure $fn) | ||
{ | ||
$reflect = new ReflectionClass($this->model); | ||
|
||
// This behemoth cycles through all valid methods, and then gets only the attributes we care about, | ||
// formatting it in a way that is usable by our various aggregate service classes. | ||
return collect($reflect->getMethods()) | ||
->filter(fn (ReflectionMethod $method) => count($method->getAttributes($attributeClass)) > 0) | ||
->flatten() | ||
->map(function (ReflectionMethod $method) use ($attributeClass) { | ||
return collect($method->getAttributes($attributeClass))->map(fn (\ReflectionAttribute $attribute) => [ | ||
'name' => $method->name, | ||
'attribute' => $attribute->newInstance(), | ||
])->toArray(); | ||
}) | ||
->flatten(1) | ||
->mapWithKeys($fn) | ||
->toArray(); | ||
} | ||
|
||
/** | ||
* Creates the key based on model properties and rules. | ||
* | ||
* @param string $model | ||
* @param string $field | ||
* | ||
* @return string | ||
* Applies the provided function using the relevant configuration to all configured relations. Configuration | ||
* would be one of countedBy, summedBy, averagedBy.etc. | ||
*/ | ||
protected function field($model, $field) | ||
protected function apply(Closure $function): void | ||
{ | ||
$class = strtolower(class_basename($model)); | ||
$field = $class . '_' . $field; | ||
|
||
return $field; | ||
foreach ($this->configuration() as $key => $value) { | ||
$function($this->config($key, $value)); | ||
} | ||
} | ||
|
||
/** | ||
* Process configuration parameters to check key names, fix snake casing, etc.. | ||
* Updates a table's record based on the query information provided in the $config variable. | ||
* | ||
* @param array $config | ||
* @return array | ||
* @param string $operation Whether to increase or decrease a value. Valid values: +/- | ||
*/ | ||
protected function processConfig(array $config) | ||
protected function updateCacheRecord(Model $model, CacheConfig $config, string $operation, int $amount): void | ||
{ | ||
return [ | ||
'model' => $config['model'], | ||
'table' => $this->getModelTable($config['model']), | ||
'field' => Str::snake($config['field']), | ||
'key' => Str::snake($this->key($config['key'])), | ||
'foreignKey' => Str::snake($this->key($config['foreignKey'])), | ||
]; | ||
$this->updateCacheValue($model, $config, $amount); | ||
} | ||
|
||
/** | ||
* Returns the true key for a given field. | ||
* It's a bit hard to read what's going on in this method, so let's elaborate. | ||
* | ||
* @param string $field | ||
* @return mixed | ||
* 1. Get the foreign key of the model that needs to be queried. | ||
* 2. Get the aggregate value for all records with that foreign key. | ||
* 3. Update the related model wth the relevant aggregate value. | ||
*/ | ||
protected function key($field) | ||
public function rebuildCacheRecord(CacheConfig $config, Model $model, $command): void | ||
{ | ||
if (method_exists($this->model, 'getTrueKey')) { | ||
return $this->model->getTrueKey($field); | ||
} | ||
|
||
return $field; | ||
$foreignKey = $config->foreignKeyName($model); | ||
$related = $config->emptyRelatedModel($model); | ||
|
||
$updateSql = sprintf( | ||
'UPDATE %s SET %s = COALESCE((SELECT %s(%s) FROM %s WHERE %s = %s.%s), 0)', | ||
$related->getTable(), | ||
$config->aggregateField, | ||
$command, | ||
$config->sourceField, | ||
$model->getTable(), | ||
$foreignKey, | ||
$related->getTable(), | ||
$related->getKeyName() | ||
); | ||
|
||
DB::update($updateSql); | ||
} | ||
|
||
/** | ||
* Returns the table for a given model. Model can be an Eloquent model object, or a full namespaced | ||
* class string. | ||
* | ||
* @param string|Model $model | ||
* @return mixed | ||
* Update the cache value for the model. | ||
*/ | ||
protected function getModelTable($model) | ||
protected function updateCacheValue(Model $model, CacheConfig $config, int $amount): void | ||
{ | ||
if (!is_object($model)) { | ||
$model = new $model; | ||
} | ||
|
||
return DB::getTablePrefix().$model->getTable(); | ||
$model->{$config->aggregateField} = $model->{$config->aggregateField} + $amount; | ||
$model->save(); | ||
} | ||
|
||
} |
Oops, something went wrong.