Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eloquence 11.0 #112

Merged
merged 27 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d7c5ffb
Removed the UUID code.
kirkbushell Aug 26, 2023
15b8852
Removed now redundant logic check in the getAttribute method.
kirkbushell Aug 26, 2023
af24d0c
Made Eloquence auto-discoverable by Laravel.
kirkbushell Aug 26, 2023
c24a0f3
Updated the readme for package auto-discovery.
kirkbushell Aug 26, 2023
f49a0b0
Updated the cache count system to use the model objects instead of th…
kirkbushell Aug 26, 2023
5045704
Moved config method to cacheable, started setting up summable refactor.
kirkbushell Aug 26, 2023
bec7a64
Sum cache now uses new system.
kirkbushell Aug 26, 2023
4f3f330
Got model restoration working on sumcache.
kirkbushell Aug 26, 2023
6f273db
Updated slug test wording.
kirkbushell Aug 26, 2023
18362db
Added additional tests for the sum cache.
kirkbushell Aug 27, 2023
4a4de05
Fixing a few bugs, cleaning up tests by adding factories, making it m…
kirkbushell Aug 27, 2023
4268ae6
Setting up the query log utility feature, making it super easy for de…
kirkbushell Aug 27, 2023
ea43099
Got PHP attributes working as a cleaner approach.
kirkbushell Aug 27, 2023
52201f2
Refactored sum cache test to use factories.
kirkbushell Aug 27, 2023
5096d9e
More styling updates, removed redundant code, updated readme.
kirkbushell Aug 27, 2023
aa3884e
Little more updates to the readme for ultra-clear explanation of use.
kirkbushell Aug 27, 2023
7c7a80c
Added sum and count cache rebuild feature.
kirkbushell Nov 29, 2023
a75c626
Cache rebuild command added.
kirkbushell Nov 29, 2023
d19f31e
Some fixes on the rebuild cache command.
kirkbushell Nov 29, 2023
f8e478f
Fixed a bug in the rebuild caches test, addressed feedback and sugges…
kirkbushell Jan 31, 2024
f43eba5
Removed ramsey/uuid and removed unused code.
kirkbushell Jan 31, 2024
8674e5f
Renamed traits, fixed tests, removed redundant commentary.
kirkbushell Jan 31, 2024
7a0954a
Removed empty test.
kirkbushell Jan 31, 2024
200e303
Updated the readme.
kirkbushell Jan 31, 2024
5cab32b
Removed unneccessary code.
kirkbushell Jan 31, 2024
e2f92a8
Updated documentation with details for the upgrade guide.
kirkbushell Jan 31, 2024
8ae46f4
Ran a linter and fixed any PSR issues.
kirkbushell Jan 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 115 additions & 161 deletions README.md

Large diffs are not rendered by default.

42 changes: 36 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
"name": "kirkbushell/eloquence",
"description": "A set of extensions adding additional functionality and consistency to Laravel's awesome Eloquent library.",
"keywords": [
"laravel",
"eloquent",
"aggregates",
"cache",
"camelcase",
"camel",
"case",
"count",
"eloquent",
"laravel",
"snake_case",
"snake"
"snake",
"sum"
],
"authors": [
{
Expand All @@ -21,7 +25,8 @@
"hashids/hashids": "^4.1",
"illuminate/database": "^10.0",
"illuminate/support": "^10.0",
"ramsey/uuid": "^4.7"
"hanneskod/classtools": "^0.1.0",
"symfony/finder": "^6.3"
},
"require-dev": {
"illuminate/events": "^10.0",
Expand All @@ -36,8 +41,33 @@
}
},
"scripts": {
"test": "phpunit --colors=always"
"test": "phpunit --colors=always",
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"build": "@php vendor/bin/testbench workbench:build --ansi",
"serve": [
"@build",
"@php vendor/bin/testbench serve"
]
},
"minimum-stability": "dev",
"prefer-stable": true
"prefer-stable": true,
"extra": {
"laravel": {
"providers": [
"Eloquence\\EloquenceServiceProvider"
]
}
},
"autoload-dev": {
"psr-4": {
"Workbench\\App\\": "workbench/app/",
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
}
}
8 changes: 8 additions & 0 deletions config/eloquence.php
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')),
]
];
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@
<directory suffix="Test.php">./tests/Acceptance</directory>
</testsuite>
</testsuites>
<php>
<env name="API_KEY" value="fakeApiKey" force="true" />
</php>
</phpunit>
50 changes: 50 additions & 0 deletions src/Behaviours/CacheConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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();
}
}
171 changes: 67 additions & 104 deletions src/Behaviours/Cacheable.php
Original file line number Diff line number Diff line change
@@ -1,144 +1,107 @@
<?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();
}

}
Loading
Loading