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
76 changes: 16 additions & 60 deletions docs/filters-and-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,76 +88,43 @@ won't be available in `$args` unless you configure a filter for it.

There are two approaches to handle this:

### Approach 1: Nested Array Fields (Recommended)
### Approach 1: Using the 'extraParams' config

Group related fields under a single parent key in your form. This way, you only need
one filter and all sub-values are automatically available.
You can specify additional params to retain using the `extraParams` config and
make their values available in the `$args` variable.

**Form fields:**
```php
echo $this->Form->control('search.field_a');
echo $this->Form->control('search.field_b');
echo $this->Form->control('field_a');
echo $this->Form->control('field_b');
```

**Filter configuration:**
```php
$this->callback('search', [
$searchManager->callback('field_a', [
'extraParams' => ['field_b'],
'callback' => function (SelectQuery $query, array $args, $filter) {
$fieldA = $args['search']['field_a'] ?? null;
$fieldB = $args['search']['field_b'] ?? null;

if (!$fieldA || !$fieldB) {
return false;
}

// Your custom query logic using both field values
$query->where([
'Model.field_a' => $fieldA,
'Model.field_b' => $fieldB,
]);

return true;
// $args will now contain 'field_b' key if it is passed as a param
},
]);
```

**Pros:**
- Only need one filter
- No dummy filters required
- Semantically groups related fields
- Cleaner configuration

**Cons:**
- More complex form field names (`search[field_a]` vs `field_a`)
- URL query string is nested (`?search[field_a]=x&search[field_b]=y`)
- Less flexible if fields aren't semantically related
### Approach 2: Nested Array Fields

### Approach 2: Dummy Filters for Separate Fields

Keep fields independent but create dummy filters to make values available.
Group related fields under a single parent key in your form.

**Form fields:**
```php
echo $this->Form->control('field_a');
echo $this->Form->control('field_b');
echo $this->Form->control('search.field_a');
echo $this->Form->control('search.field_b');
```

**Filter configuration:**
```php
// Dummy filter to ensure 'field_b' value is available in $args
$this->callback('field_b', [
'callback' => function (SelectQuery $query, array $args, $filter) {
// Return false to not modify the query, but make $args['field_b'] available
return false;
},
]);

// The actual search logic that uses both 'field_a' and 'field_b'
$this->callback('field_a', [
$this->callback('search', [
'callback' => function (SelectQuery $query, array $args, $filter) {
// Now $args['field_b'] is available thanks to the dummy filter above
$fieldA = $args['field_a'] ?? null;
$fieldB = $args['field_b'] ?? null;
$fieldA = $args['search']['field_a'] ?? null;
$fieldB = $args['search']['field_b'] ?? null;

if (!$fieldA || !$fieldB) {
return false;
Expand All @@ -174,20 +141,9 @@ $this->callback('field_a', [
]);
```

**Pros:**
- Simple, flat field names
- Clean URL structure (`?field_a=x&field_b=y`)
- Fields remain conceptually independent
- More flexibility for unrelated fields

**Cons:**
- Requires dummy filter workaround
- Less obvious that fields are related
- More verbose configuration

**Which approach to use?**
- Use **nested arrays** when fields are semantically related (e.g., date range with start/end, tag search with options)
- Use **separate fields with dummy filters** when fields are independent but happen to be used together in one callback
- Use **`extraParams` config** when fields are independent but happen to be used together in one callback

----------

Expand Down
9 changes: 9 additions & 0 deletions src/Model/Filter/Callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@

class Callback extends Base
{
/**
* Default configuration.
*
* @var array<string, mixed>
*/
protected array $_defaultConfig = [
'extraParams' => [],
];

/**
* Modify query using callback.
*
Expand Down
13 changes: 9 additions & 4 deletions src/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ class Processor
*/
public function process(FilterCollectionInterface $filters, QueryInterface $query, array $params): bool
{
$params = $this->_flattenParams($params, $filters);
$params = $this->_extractParams($params, $filters);
$filterParams = $this->_flattenParams($params, $filters);
$filterParams = $this->_extractParams($filterParams, $filters);

$this->_searchParams = $params;
$this->_searchParams = $filterParams;
$filtered = false;

foreach ($filters as $filter) {
$result = $filter->execute($query, $params);
$extraParams = $filter->getConfig('extraParams', []);
if ($extraParams) {
$filterParams += array_intersect_key($params, array_flip($extraParams));
}

$result = $filter->execute($query, $filterParams);
if ($result !== false) {
$filtered = true;
}
Expand Down
20 changes: 20 additions & 0 deletions tests/TestCase/Model/Behavior/SearchBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Search\Test\TestCase\Model\Behavior;

use Cake\ORM\Query\SelectQuery;
use Cake\TestSuite\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Search\Manager;
Expand Down Expand Up @@ -315,4 +316,23 @@ public function testSearchManager()
$manager = $this->Articles->searchManager();
$this->assertInstanceOf('\Search\Manager', $manager);
}

public function testExtraParams(): void
{
$result = [];
$manager = $this->Articles->searchManager();
$manager->callback('field1', [
'callback' => function (SelectQuery $query, array $args) use (&$result) {
$result = $args;
},
'extraParams' => ['extra_field'],
]);

$this->Articles->find('search', search: ['field1' => 'foo', 'extra_field' => 'bar']);

$this->assertSame([
'field1' => 'foo',
'extra_field' => 'bar',
], $result);
}
}