Skip to content

Commit

Permalink
Cache rebuild command added.
Browse files Browse the repository at this point in the history
  • Loading branch information
kirkbushell committed Nov 29, 2023
1 parent 7c7a80c commit a75c626
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 7 deletions.
23 changes: 21 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"illuminate/database": "^10.0",
"illuminate/support": "^10.0",
"ramsey/uuid": "^4.7",
"hanneskod/classtools": "^0.1.0"
"hanneskod/classtools": "^0.1.0",
"symfony/finder": "^6.3"
},
"require-dev": {
"illuminate/events": "^10.0",
Expand All @@ -37,7 +38,18 @@
}
},
"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,
Expand All @@ -47,5 +59,12 @@
"Eloquence\\EloquenceServiceProvider"
]
}
},
"autoload-dev": {
"psr-4": {
"Workbench\\App\\": "workbench/app/",
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
}
}
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>
23 changes: 18 additions & 5 deletions src/EloquenceServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@

class EloquenceServiceProvider extends ServiceProvider
{
/**
* Initialises the service provider, and here we attach our own blueprint
* resolver to the schema, so as to provide the enhanced functionality.
*/
public function boot()
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/eloquence.php' => config_path('eloquence.php'),
], 'config');

$this->initialiseDbQueryLog();
$this->initialiseCommands();
}

protected function initialiseDbQueryLog(): void
{
DBQueryLog::initialise();
}

private function initialiseCommands(): void
{
$this->commands([
Utilities\RebuildCaches::class,
]);
}
}
97 changes: 97 additions & 0 deletions src/Utilities/RebuildCaches.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Eloquence\Utilities;

use Eloquence\Behaviours\CountCache\HasCounts;
use Eloquence\Behaviours\SumCache\HasSums;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

class RebuildCaches extends Command
{
protected $signature = 'eloquence:rebuild-caches {path? : The absolute path to search for models. Defaults to your application path.}';
protected $description = 'Rebuilds the caches of all affected models within the database.';

private $caches = [
HasCounts::class => 'rebuildCountCache',
HasSums::class => 'rebuildSumCache',
];

public function handle(): void
{
$path = $this->argument('path') ?? app_path();

$this->allModelsUsingCaches($path)->each(function(string $class) {
$traits = class_uses_recursive($class);

foreach ($this->caches as $trait => $method) {
if (!in_array($trait, $traits)) continue;

$class::$method();
}
});
}

/**
* Returns only those models that are utilising eloquence cache mechanisms.
*
* @param string $path
* @return Collection
*/
private function allModelsUsingCaches(string $path): Collection
{
return collect(Finder::create()->files()->in($path)->name('*.php'))
->filter(fn(SplFileInfo $file) => $file->getFilename()[0] === Str::upper($file->getFilename()[0]))
->map(fn(SplFileInfo $file) => $this->fullyQualifiedClassName($file))
->filter(fn(string $class) => is_subclass_of($class, Model::class))
->filter(fn(string $class) => $this->usesCaches($class));
}

/**
* Determines the fully qualified class name of the provided file.
*
* @param SplFileInfo $file
* @return string
*/
private function fullyQualifiedClassName(SplFileInfo $file)
{
$tokens = \PhpToken::tokenize($file->getContents());
$namespace = null;
$class = null;

foreach ($tokens as $i => $token) {
if ($token->is(T_NAMESPACE)) {
$namespace = $tokens[$i+2]->text;
}

if ($token->is(T_CLASS)) {
$class = $tokens[$i+2]->text;
}

if ($namespace && $class) {
break;
}
}

if (!$namespace || !$class) {
$this->error(sprintf('Could not find namespace or class in %s', $file->getRealPath()));
}

return sprintf('%s\\%s', $namespace, $class);
}

/**
* Returns true if the provided class uses any of the caches provided by Eloquence.
*
* @param string $class
* @return bool
*/
private function usesCaches(string $class): bool
{
return (bool) array_intersect(class_uses_recursive($class), array_keys($this->caches));
}
}
8 changes: 8 additions & 0 deletions tests/Acceptance/AcceptanceTestCase.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Tests\Acceptance;

use Eloquence\EloquenceServiceProvider;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Orchestra\Testbench\TestCase;
Expand All @@ -15,6 +16,13 @@ public function setUp(): void
$this->init();
}

protected function getPackageProviders($app)
{
return [
EloquenceServiceProvider::class,
];
}

protected function getEnvironmentSetUp($app)
{
$app['config']->set('database.default', 'test');
Expand Down
25 changes: 25 additions & 0 deletions tests/Acceptance/RebuildCachesCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Tests\Acceptance;

class RebuildCachesCommandTest extends AcceptanceTestCase
{
function test_itCanRebuildCachesOfAllAffectedModels()
{
$result = $this->artisan('eloquence:rebuild-caches '.__DIR__.'/../../tests/Acceptance/Models');

$result->assertExitCode(0);

$this->assertDatabaseHas('users', [
'post_count' => 5,
]);

$this->assertDatabaseHas('users', [
'post_count' => 2,
]);

$this->assertDatabaseHas('orders', [
'total_amount' => 30,
]);
}
}

0 comments on commit a75c626

Please sign in to comment.