Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.10% covered (warning)
88.10%
37 / 42
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractInMemoryDataProvider
87.80% covered (warning)
87.80%
36 / 41
50.00% covered (danger)
50.00%
2 / 4
11.22
0.00% covered (danger)
0.00%
0 / 1
 loadRows
n/a
0 / 0
n/a
0 / 0
0
 getDefaultSortColumn
n/a
0 / 0
n/a
0 / 0
0
 fetchList
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
3
 applyFilters
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 matchFilter
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 sortRows
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\About\Application\DataSource;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Domain\DataSource\DynamicDataProviderInterface;
12use App\Core\Engine\Domain\Model\ModuleMetadata;
13use App\Core\Engine\Domain\Model\PermissionContext;
14use App\Core\Grid\GridRequest;
15use App\Core\Grid\GridResult;
16
17/**
18 * Base Abstract In-Memory Data Provider.
19 *
20 * Provides shared filtering, sorting, and pagination for array-based in-memory DataGrid sources.
21 *
22 * @package App\Modules\About\Application\DataSource
23 */
24abstract readonly class AbstractInMemoryDataProvider implements DynamicDataProviderInterface
25{
26    /**
27     * Loads raw data rows for the grid.
28     *
29     * @return array<int, array<string, mixed>> Raw data rows.
30     */
31    abstract protected function loadRows(): array;
32
33    /**
34     * Returns default sort column name.
35     */
36    abstract protected function getDefaultSortColumn(): string;
37
38    /** {@inheritdoc} */
39    public function fetchList(
40        ModuleMetadata    $module,
41        GridRequest       $request,
42        PermissionContext $context,
43        ?int              $filterId = null
44    ): GridResult {
45        $rows = $this->loadRows();
46        $filtered = $this->applyFilters($rows, $request->filters);
47
48        $sortColumn    = !empty($request->sortColumn) ? $request->sortColumn : $this->getDefaultSortColumn();
49        $sortDirection = strtoupper($request->sortDirection) === 'ASC' ? 1 : -1;
50        $sorted = $this->sortRows($filtered, $sortColumn, $sortDirection);
51
52        $totalRecords = count($sorted);
53        $limit        = max(1, $request->limit);
54        $totalPages   = (int) ceil($totalRecords / $limit);
55        $page         = max(1, min($request->page, max(1, $totalPages)));
56        $offset       = ($page - 1) * $limit;
57
58        $pagedRows = array_slice($sorted, $offset, $limit);
59
60        return new GridResult(
61            rows:         $pagedRows,
62            totalRecords: $totalRecords,
63            gridRequest:  new GridRequest(
64                $page,
65                $limit,
66                $sortColumn,
67                $request->sortDirection,
68                $request->filters
69            ),
70            columns:      []
71        );
72    }
73
74    /**
75     * Filters rows by request criteria.
76     *
77     * @param array<int, array<string, mixed>> $rows    Raw rows.
78     * @param array<string, string>            $filters Column filter criteria.
79     * @return array<int, array<string, mixed>> Filtered rows.
80     */
81    protected function applyFilters(array $rows, array $filters): array
82    {
83        return array_values(array_filter($rows, function (array $row) use ($filters): bool {
84            foreach ($filters as $col => $val) {
85                if ($val !== '' && !$this->matchFilter($row, (string) $col, (string) $val)) {
86                    return false;
87                }
88            }
89            return true;
90        }));
91    }
92
93    /**
94     * Tests a single filter condition against a row.
95     */
96    protected function matchFilter(array $row, string $column, string $filterValue): bool
97    {
98        $fieldVal = (string) ($row[$column] ?? '');
99        if ($column === 'status') {
100            return (string) ($row['status'] ?? '') === $filterValue;
101        }
102        return stripos($fieldVal, $filterValue) !== false;
103    }
104
105    /**
106     * Sorts rows by column and direction.
107     *
108     * @param array<int, array<string, mixed>> $rows          Rows to sort.
109     * @param string                           $sortColumn    Sort column name.
110     * @param int                              $sortDirection 1 for ASC, -1 for DESC.
111     * @return array<int, array<string, mixed>> Sorted rows.
112     */
113    protected function sortRows(array $rows, string $sortColumn, int $sortDirection): array
114    {
115        usort($rows, static function (array $a, array $b) use ($sortColumn, $sortDirection): int {
116            $valA = (string) ($a[$sortColumn] ?? '');
117            $valB = (string) ($b[$sortColumn] ?? '');
118            if ($sortColumn === 'id') {
119                return ((int) $valA <=> (int) $valB) * $sortDirection;
120            }
121            return strnatcasecmp($valA, $valB) * $sortDirection;
122        });
123
124        return $rows;
125    }
126}