Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/app/Http/Controllers/Operations/ListOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Backpack\CRUD\app\Http\Controllers\Operations;

use Backpack\CRUD\app\Library\CrudPanel\Hooks\Facades\LifecycleHook;
use Backpack\CRUD\app\Library\Support\DatatableCache;
use Illuminate\Support\Facades\Route;

trait ListOperation
Expand Down Expand Up @@ -78,7 +79,7 @@ public function search()

// If there's a config closure in the cache for this CRUD, run that configuration closure.
// This is done in order to allow the developer to configure the datatable component.
\Backpack\CRUD\app\View\Components\Datatable::applyCachedSetupClosure($this->crud);
DatatableCache::applyFromRequest($this->crud);

$this->crud->applyUnappliedFilters();

Expand Down
203 changes: 203 additions & 0 deletions src/app/Library/Support/DatatableCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

namespace Backpack\CRUD\app\Library\Support;

use Backpack\CRUD\app\Library\CrudPanel\CrudPanel;
use Backpack\CRUD\app\Library\Widget;
use Backpack\CRUD\CrudManager;

final class DatatableCache extends SetupCache
{
public function __construct()
{
$this->cachePrefix = 'datatable_config_';
$this->cacheDuration = 60; // 1 hour
}

/**
* Cache setup closure for a datatable component.
*
* @param string $tableId The table ID to use as cache key
* @param string $controllerClass The controller class
* @param \Closure|null $setup The setup closure
* @param string|null $name The element name
* @param CrudPanel $crud The CRUD panel instance to update with datatable_id
* @return bool Whether the operation was successful
*/
public function cacheForComponent(string $tableId, string $controllerClass, ?\Closure $setup = null, ?string $name = null, ?CrudPanel $crud = null): bool
{
if (! $setup) {
return false;
}

$cruds = CrudManager::getCrudPanels();
$parentCrud = reset($cruds);

if ($parentCrud && $parentCrud->getCurrentEntry()) {
$parentEntry = $parentCrud->getCurrentEntry();
$parentController = $parentCrud->controller;

// Store in cache
$this->store(
$tableId,
$controllerClass,
$parentController,
$parentEntry,
$name
);

// Set the datatable_id in the CRUD panel if provided
if ($crud) {
$crud->set('list.datatable_id', $tableId);
}

return true;
}

return false;
}

public static function applyAndStoreSetupClosure(
string $tableId,
string $controllerClass,
\Closure $setupClosure,
?string $name = null,
?CrudPanel $crud = null,
$parentEntry = null
): bool {
$instance = new self();
// Cache the setup closure for the datatable component
if ($instance->applySetupClosure($crud, $controllerClass, $setupClosure, $parentEntry)) {
// Apply the setup closure to the CrudPanel instance
return $instance->cacheForComponent($tableId, $controllerClass, $setupClosure, $name, $crud);
}

return false;
}

/**
* Apply cached setup to a CRUD instance using the request's datatable_id.
*
* @param CrudPanel $crud The CRUD panel instance
* @return bool Whether the operation was successful
*/
public static function applyFromRequest(CrudPanel $crud): bool
{
$instance = new self();
// Check if the request has a datatable_id parameter
$tableId = request('datatable_id');

if (! $tableId) {
\Log::debug('Missing datatable_id in request parameters');

return false;
}

return $instance->apply($tableId, $crud);
}

/**
* Apply a setup closure to a CrudPanel instance.
*
* @param CrudPanel $crud The CRUD panel instance
* @param string $controllerClass The controller class
* @param \Closure $setupClosure The setup closure
* @param mixed $entry The entry to pass to the setup closure
* @return bool Whether the operation was successful
*/
public function applySetupClosure(CrudPanel $crud, string $controllerClass, \Closure $setupClosure, $entry = null): bool
{
$originalSetup = $setupClosure;
$modifiedSetup = function ($crud, $entry) use ($originalSetup, $controllerClass) {
CrudManager::setActiveController($controllerClass);

// Run the original closure
return ($originalSetup)($crud, $entry);
};

try {
// Execute the modified closure
($modifiedSetup)($crud, $entry);

return true;
} finally {
// Clean up
CrudManager::unsetActiveController();
}
}

/**
* Prepare datatable data for storage in the cache.
*
* @param string $controllerClass The controller class
* @param string $parentController The parent controller
* @param mixed $parentEntry The parent entry
* @param string|null $elementName The element name
* @return array The data to be cached
*/
protected function prepareDataForStorage(...$args): array
{
[$controllerClass, $parentController, $parentEntry, $elementName] = $args;

return [
'controller' => $controllerClass,
'parentController' => $parentController,
'parent_entry' => $parentEntry,
'element_name' => $elementName,
'operations' => CrudManager::getInitializedOperations($parentController),
];
}

/**
* Apply data from the cache to configure a datatable.
*
* @param array $cachedData The cached data
* @param CrudPanel $crud The CRUD panel instance
* @return bool Whether the operation was successful
*/
protected function applyFromCache($cachedData, ...$args): bool
{
[$crud] = $args;

try {
// Initialize operations for the parent controller
$this->initializeOperations($cachedData['parentController'], $cachedData['operations']);
$entry = $cachedData['parent_entry'];
$elementName = $cachedData['element_name'];

$widgets = Widget::collection();
$found = false;

foreach ($widgets as $widget) {
if ($widget['type'] === 'datatable' &&
(isset($widget['name']) && $widget['name'] === $elementName) &&
(isset($widget['setup']) && $widget['setup'] instanceof \Closure)) {
$this->applySetupClosure($crud, $cachedData['controller'], $widget['setup'], $entry);
$found = true;
break;
}
}

return $found;
} catch (\Exception $e) {
\Log::error('Error applying cached datatable config: '.$e->getMessage(), [
'exception' => $e,
]);

return false;
}
}

/**
* Initialize operations for a parent controller.
*/
private function initializeOperations(string $parentController, $operations): void
{
$parentCrud = CrudManager::setupCrudPanel($parentController);

foreach ($operations as $operation) {
$parentCrud->initialized = false;
CrudManager::setupCrudPanel($parentController, $operation);
}
}
}
114 changes: 114 additions & 0 deletions src/app/Library/Support/SetupCache.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clean. And well-named. I like it. We're done here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace Backpack\CRUD\app\Library\Support;

use Illuminate\Support\Facades\Cache;

abstract class SetupCache
{
protected string $cachePrefix = 'setup_cache_';
protected int $cacheDuration = 60; // minutes

/**
* Generate a cache key for the given identifier.
*/
protected function generateCacheKey($identifier): string
{
return $this->cachePrefix.$identifier;
}

/**
* Store data in the cache.
*/
public function store($identifier, ...$args)
{
$cacheKey = $this->generateCacheKey($identifier);
$data = $this->prepareDataForStorage(...$args);

if ($data !== false && $data !== null) {
Cache::forget($cacheKey);
Cache::put($cacheKey, $data, now()->addMinutes($this->cacheDuration));

return true;
}

return false;
}

/**
* Apply cached data.
*/
public function apply($identifier, ...$args)
{
$cacheKey = $this->generateCacheKey($identifier);
$cachedData = Cache::get($cacheKey);

if (! $cachedData) {
return false;
}

return $this->applyFromCache($cachedData, ...$args);
}

/**
* Get cached data without applying it.
*/
public function get($identifier)
{
$cacheKey = $this->generateCacheKey($identifier);

return Cache::get($cacheKey);
}

/**
* Check if cache exists for the given identifier.
*/
public function has($identifier): bool
{
$cacheKey = $this->generateCacheKey($identifier);

return Cache::has($cacheKey);
}

/**
* Remove cached data.
*/
public function forget($identifier): bool
{
$cacheKey = $this->generateCacheKey($identifier);

return Cache::forget($cacheKey);
}

/**
* Set the cache prefix.
*/
public function setCachePrefix(string $prefix): self
{
$this->cachePrefix = $prefix;

return $this;
}

/**
* Set the cache duration in minutes.
*/
public function setCacheDuration(int $minutes): self
{
$this->cacheDuration = $minutes;

return $this;
}

/**
* Prepare data for storage in the cache.
* This method should be implemented by child classes.
*/
abstract protected function prepareDataForStorage(...$args);

/**
* Apply data from the cache.
* This method should be implemented by child classes.
*/
abstract protected function applyFromCache($cachedData, ...$args);
}
Loading