diff --git a/composer.json b/composer.json
index e1f32a0..5a2095f 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
@@ -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,
@@ -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/"
+ }
}
}
diff --git a/phpunit.xml b/phpunit.xml
index a80d0e1..92e20c7 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -17,4 +17,7 @@
./tests/Acceptance
+
+
+
diff --git a/src/EloquenceServiceProvider.php b/src/EloquenceServiceProvider.php
index 76540e6..2dd3a0a 100644
--- a/src/EloquenceServiceProvider.php
+++ b/src/EloquenceServiceProvider.php
@@ -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,
+ ]);
+ }
}
diff --git a/src/Utilities/RebuildCaches.php b/src/Utilities/RebuildCaches.php
new file mode 100644
index 0000000..3437304
--- /dev/null
+++ b/src/Utilities/RebuildCaches.php
@@ -0,0 +1,97 @@
+ '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));
+ }
+}
diff --git a/tests/Acceptance/AcceptanceTestCase.php b/tests/Acceptance/AcceptanceTestCase.php
index 12aaf4a..038f533 100644
--- a/tests/Acceptance/AcceptanceTestCase.php
+++ b/tests/Acceptance/AcceptanceTestCase.php
@@ -1,6 +1,7 @@
init();
}
+ protected function getPackageProviders($app)
+ {
+ return [
+ EloquenceServiceProvider::class,
+ ];
+ }
+
protected function getEnvironmentSetUp($app)
{
$app['config']->set('database.default', 'test');
diff --git a/tests/Acceptance/RebuildCachesCommandTest.php b/tests/Acceptance/RebuildCachesCommandTest.php
new file mode 100644
index 0000000..bdbdcad
--- /dev/null
+++ b/tests/Acceptance/RebuildCachesCommandTest.php
@@ -0,0 +1,25 @@
+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,
+ ]);
+ }
+}
\ No newline at end of file