-
Notifications
You must be signed in to change notification settings - Fork 920
DataFormModal component #5863
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
base: next
Are you sure you want to change the base?
DataFormModal component #5863
Changes from 21 commits
7322594
4a8fda0
642c120
52b042a
18ec4c8
9a6d8fe
8dee927
3575976
68e4ac4
cb89781
676f1c8
945970f
c86a86e
6e8f327
bbe5763
79b31f7
60cfbd5
5833621
110c704
f294e9a
524debf
3dcef89
0697c4c
1352a4c
5938c35
f72c6d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's another round of testing. I discovered:
Here's a 5-min tour of my experience: |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,23 +66,246 @@ public function setupCrudPanel(string $controller, ?string $operation = null): C | |
|
||
// Use provided operation or default to 'list' | ||
$operation = $operation ?? 'list'; | ||
$crud->setOperation($operation); | ||
|
||
$shouldIsolate = $this->shouldIsolateOperation($controller::class, $operation); | ||
|
||
$primaryControllerRequest = $this->cruds[array_key_first($this->cruds)]->getRequest(); | ||
if (! $crud->isInitialized() || ! $this->isOperationInitialized($controller::class, $operation)) { | ||
|
||
if ($crud->isInitialized() && $crud->getOperation() !== $operation && ! $shouldIsolate) { | ||
self::setActiveController($controller::class); | ||
$crud->initialized = false; | ||
|
||
$crud->setOperation($operation); | ||
$this->setupSpecificOperation($controller, $operation, $crud); | ||
|
||
// Mark this operation as initialized | ||
$this->storeInitializedOperation($controller::class, $operation); | ||
|
||
self::unsetActiveController(); | ||
|
||
return $this->cruds[$controller::class]; | ||
} | ||
|
||
// Check if we need to initialize this specific operation | ||
if (! $crud->isInitialized() || ! $this->isOperationInitialized($controller::class, $operation)) { | ||
self::setActiveController($controller::class); | ||
$controller->initializeCrudPanel($primaryControllerRequest, $crud); | ||
|
||
// If the panel isn't initialized at all, do full initialization | ||
if (! $crud->isInitialized()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something here smells a bit - maybe we can return early or smth |
||
// Set the operation for full initialization | ||
$crud->setOperation($operation); | ||
$crud->initialized = false; | ||
$controller->initializeCrudPanel($primaryControllerRequest, $crud); | ||
} else { | ||
// Panel is initialized, just setup this specific operation | ||
// Use operation isolation for non-primary operations | ||
if ($shouldIsolate) { | ||
$this->setupIsolatedOperation($controller, $operation, $crud); | ||
} else { | ||
// Set the operation for standard setup | ||
$crud->setOperation($operation); | ||
$this->setupSpecificOperation($controller, $operation, $crud); | ||
} | ||
} | ||
|
||
// Mark this operation as initialized | ||
$this->storeInitializedOperation($controller::class, $operation); | ||
|
||
self::unsetActiveController(); | ||
$crud = $this->cruds[$controller::class]; | ||
|
||
return $this->cruds[$controller::class]; | ||
} | ||
|
||
// If we reach here, the panel and operation are both initialized | ||
// and the operation matches what was requested, so just return it | ||
return $this->cruds[$controller::class]; | ||
} | ||
|
||
/** | ||
* Determine if an operation should be isolated to prevent state interference. | ||
* | ||
* @param string $controller | ||
* @param string $operation | ||
* @return bool | ||
*/ | ||
private function shouldIsolateOperation(string $controller, string $operation): bool | ||
{ | ||
$currentCrud = $this->cruds[$controller] ?? null; | ||
if (! $currentCrud) { | ||
return false; | ||
} | ||
|
||
$currentOperation = $currentCrud->getOperation(); | ||
|
||
// If operations don't differ, no need to isolate | ||
if (! $currentOperation || $currentOperation === $operation) { | ||
return false; | ||
} | ||
|
||
// Check backtrace for components implementing IsolatesOperationSetup | ||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 10); | ||
|
||
foreach ($backtrace as $trace) { | ||
if (isset($trace['object'])) { | ||
$object = $trace['object']; | ||
|
||
// If we find a component that implements the interface, use its declared behavior | ||
if ($object instanceof \Backpack\CRUD\app\View\Components\Contracts\IsolatesOperationSetup) { | ||
return $object->shouldIsolateOperationSetup(); | ||
} | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Setup an operation in isolation without affecting the main CRUD panel state. | ||
* This creates a temporary context for operation setup without state interference. | ||
* | ||
* @param object $controller The controller instance | ||
* @param string $operation The operation to setup | ||
* @param CrudPanel $crud The CRUD panel instance | ||
*/ | ||
private function setupIsolatedOperation($controller, string $operation, CrudPanel $crud): void | ||
{ | ||
// Store the complete current state | ||
$originalOperation = $crud->getOperation(); | ||
$originalSettings = $crud->settings(); | ||
$originalColumns = $crud->columns(); // Use the direct method, not operation setting | ||
$originalRoute = $crud->route ?? null; | ||
$originalEntityName = $crud->entity_name ?? null; | ||
$originalEntityNamePlural = $crud->entity_name_plural ?? null; | ||
|
||
// Store operation-specific settings generically | ||
$originalOperationSettings = $this->extractOperationSettings($crud, $originalOperation); | ||
|
||
// Temporarily setup the requested operation | ||
$crud->setOperation($operation); | ||
|
||
// Use the controller's own method to setup the operation properly | ||
$reflection = new \ReflectionClass($controller); | ||
$method = $reflection->getMethod('setupConfigurationForCurrentOperation'); | ||
$method->setAccessible(true); | ||
$method->invoke($controller, $operation); | ||
|
||
// Completely restore the original state | ||
$crud->setOperation($originalOperation); | ||
|
||
// CRITICAL: Properly restore columns by clearing and re-adding them | ||
// This is essential to preserve list operation columns | ||
$crud->removeAllColumns(); | ||
foreach ($originalColumns as $column) { | ||
$crud->addColumn($column); | ||
} | ||
|
||
// Restore all original settings one by one, but skip complex objects | ||
foreach ($originalSettings as $key => $value) { | ||
try { | ||
// Skip complex objects that Laravel generates dynamically | ||
if (is_object($value) && ( | ||
$value instanceof \Illuminate\Routing\UrlGenerator || | ||
$value instanceof \Illuminate\Http\Request || | ||
$value instanceof \Illuminate\Contracts\Foundation\Application || | ||
$value instanceof \Closure || | ||
method_exists($value, '__toString') === false | ||
)) { | ||
continue; | ||
} | ||
|
||
$crud->set($key, $value); | ||
} catch (\Exception $e) { | ||
// Silently continue with restoration | ||
} | ||
} | ||
|
||
// Restore operation-specific settings generically | ||
$this->restoreOperationSettings($crud, $originalOperation, $originalOperationSettings); | ||
|
||
// Restore core properties if they were changed | ||
if ($originalRoute !== null) { | ||
$crud->route = $originalRoute; | ||
} | ||
if ($originalEntityName !== null) { | ||
$crud->entity_name = $originalEntityName; | ||
} | ||
if ($originalEntityNamePlural !== null) { | ||
$crud->entity_name_plural = $originalEntityNamePlural; | ||
} | ||
} | ||
|
||
/** | ||
* Extract all settings for a specific operation. | ||
* | ||
* @param CrudPanel $crud The CRUD panel instance | ||
* @param string $operation The operation name | ||
* @return array Array of operation-specific settings | ||
*/ | ||
private function extractOperationSettings(CrudPanel $crud, string $operation): array | ||
{ | ||
$settings = $crud->settings(); | ||
$operationSettings = []; | ||
$operationPrefix = $operation.'.'; | ||
|
||
foreach ($settings as $key => $value) { | ||
if (str_starts_with($key, $operationPrefix)) { | ||
$operationSettings[$key] = $value; | ||
} | ||
} | ||
|
||
return $operationSettings; | ||
} | ||
|
||
/** | ||
* Restore all settings for a specific operation. | ||
* | ||
* @param CrudPanel $crud The CRUD panel instance | ||
* @param string $operation The operation name | ||
* @param array $operationSettings The settings to restore | ||
*/ | ||
private function restoreOperationSettings(CrudPanel $crud, string $operation, array $operationSettings): void | ||
{ | ||
foreach ($operationSettings as $key => $value) { | ||
try { | ||
// Skip complex objects that Laravel generates dynamically | ||
if (is_object($value) && ( | ||
$value instanceof \Illuminate\Routing\UrlGenerator || | ||
$value instanceof \Illuminate\Http\Request || | ||
$value instanceof \Illuminate\Contracts\Foundation\Application || | ||
$value instanceof \Closure || | ||
method_exists($value, '__toString') === false | ||
)) { | ||
continue; | ||
} | ||
|
||
$crud->set($key, $value); | ||
} catch (\Exception $e) { | ||
// Silently continue with restoration | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Setup a specific operation without reinitializing the entire CRUD panel. | ||
* | ||
* @param object $controller The controller instance | ||
* @param string $operation The operation to setup | ||
* @param CrudPanel $crud The CRUD panel instance | ||
*/ | ||
private function setupSpecificOperation($controller, string $operation, CrudPanel $crud): void | ||
{ | ||
// Setup the specific operation using the existing CrudController infrastructure | ||
$crud->setOperation($operation); | ||
|
||
$controller->setup(); | ||
|
||
// Use the controller's own method to setup the operation properly | ||
$reflection = new \ReflectionClass($controller); | ||
$method = $reflection->getMethod('setupConfigurationForCurrentOperation'); | ||
$method->setAccessible(true); | ||
$method->invoke($controller, $operation); | ||
} | ||
|
||
/** | ||
* Check if a specific operation has been initialized for a controller. | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are failing on Github