Skip to content

Commit

Permalink
Updated the cache count system to use the model objects instead of th…
Browse files Browse the repository at this point in the history
…e DB facade.
  • Loading branch information
kirkbushell committed Aug 26, 2023
1 parent c24a0f3 commit f49a0b0
Show file tree
Hide file tree
Showing 22 changed files with 219 additions and 223 deletions.
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data from our Eloquent models persist through to our APIs in a camel-case manner
if you are writing front-end applications, which are also using camelCase. This allows for a
better standard across our application. To use:

use \Eloquence\Behaviours\CamelCasing;
use \Eloquence\Behaviours\CamelCased;

Put the above line in your models and that's it.

Expand All @@ -49,15 +49,15 @@ snake_case of field names is the defacto standard within the Laravel community :

## Behaviours

Eloquence comes with a system for setting up behaviours, which are really just small libraries that you can use with your Eloquent models.
The first of these is the count cache.
Eloquence comes with a system for setting up behaviours, which are really just small libraries that you can use with your
Eloquent models. The first of these is the count cache.

### Count cache

Count caching is where you cache the result of a count of a related table's records. A simple example of this is where you have a user who
has many posts. In this example, you may want to count the number of posts a user has regularly - and perhaps even order by this. In SQL,
ordering by a counted field is slow and unable to be indexed. You can get around this by caching the count of the posts the user
has created on the user's record.
Count caching is where you cache the result of a count of a related table's records. A simple example of this is where you
have a user who has many posts. In this example, you may want to count the number of posts a user has regularly - and perhaps
even order by this. In SQL, ordering by a counted field is slow and unable to be indexed. You can get around this by caching
the count of the posts the user has created on the user's record.

To get this working, you need to do two steps:

Expand All @@ -66,13 +66,14 @@ To get this working, you need to do two steps:

#### Configure the count cache

To setup the count cache configuration, we need to have the model use Countable trait, like so:
To setup the count cache configuration, we need to have the model use the Countable interface, and setup the basic
functionality with the HasCounts trait, like so:

```php
class Post extends Eloquent {
use Countable;
class Post extends Eloquent implements Countable {
use HasCounts;

public function countCaches() {
public function countedBy() {
return [User::class];
}
}
Expand All @@ -91,10 +92,10 @@ The example above uses the following standard conventions:
These are, however, configurable:

```php
class Post extends Eloquent {
use Countable;
class Post extends Eloquent implements Countable {
use HasCounts;

public function countCaches() {
public function countedBy() {
return [
'num_posts' => ['User', 'users_id', 'id']
];
Expand Down Expand Up @@ -129,6 +130,10 @@ and "key" parameters will be calculated using the standard conventions mentioned

With this configuration now setup - you're ready to go!

Note: Because the various behaviours often execute multiple queries at once for updating relevant
models, it's a good idea to wrap your save operations in database transaction calls, in case one of them fails.
This will help to prevent your database getting out of sync if there's ever a problem with a single datbabase query.


### Sum cache

Expand Down
37 changes: 37 additions & 0 deletions src/Behaviours/CacheConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?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 $countField) {}

/**
* 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 -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.
*
* @param Model $model
* @return Model
*/
public function emptyRelatedModel(Model $model): Model
{
return $this->relation($model)->getQuery()->getModel();
}

public function foreignKeyName(Model $model): string
{
return $this->relation($model)->getForeignKeyName();
}
}
78 changes: 11 additions & 67 deletions src/Behaviours/Cacheable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,29 @@
namespace Eloquence\Behaviours;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
* 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.
*
* @param array $config
* @param string $operation Whether to increase or decrease a value. Valid values: +/-
* @param int|float|double $amount
* @param string $foreignKey
*/
public function updateCacheRecord(array $config, $operation, $amount, $foreignKey)
public function updateCacheRecord(Model $model, CacheConfig $config, string $operation): void
{
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);
$this->updateCacheValue($model, $config, 1);
return;
}

/*
* Decrement for - operator
*/
return $sql->decrement($config['field'], $amount);
$this->updateCacheValue($model, $config, -1);
}

/**
Expand Down Expand Up @@ -77,37 +65,10 @@ public function rebuildCacheRecord(array $config, Model $model, $command, $aggre
]);
}

/**
* Creates the key based on model properties and rules.
*
* @param string $model
* @param string $field
*
* @return string
*/
protected function field($model, $field)
{
$class = strtolower(class_basename($model));
$field = $class . '_' . $field;

return $field;
}

/**
* Process configuration parameters to check key names, fix snake casing, etc..
*
* @param array $config
* @return array
*/
protected function processConfig(array $config)
public function updateCacheValue(Model $model, CacheConfig $config, 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'])),
];
$model->{$config->countField} = $model->{$config->countField} + $amount;
$model->save();
}

/**
Expand All @@ -124,21 +85,4 @@ protected function key($field)

return $field;
}

/**
* 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
*/
protected function getModelTable($model)
{
if (!is_object($model)) {
$model = new $model;
}

return DB::getTablePrefix().$model->getTable();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use Illuminate\Support\Str;

trait CamelCasing
trait CamelCased
{
/**
* Alter eloquent model behaviour so that model attributes can be accessed via camelCase, but more importantly,
Expand Down Expand Up @@ -98,7 +98,7 @@ public function getCasts()
}

/**
* Converts a given array of attribute keys to the casing required by CamelCaseModel.
* Converts a given array of attribute keys to the casing required by CamelCased.
*
* @param mixed $attributes
* @return array
Expand All @@ -116,7 +116,7 @@ public function toCamelCase($attributes)
}

/**
* Converts a given array of attribute keys to the casing required by CamelCaseModel.
* Converts a given array of attribute keys to the casing required by CamelCased.
*
* @param $attributes
* @return array
Expand Down
Loading

0 comments on commit f49a0b0

Please sign in to comment.