repeater('gallery')->field(0, 'image'); * $meta->repeater('links')->addRow(['url' => '...', 'title' => '...']); * $meta->repeater('gallery')->setField(0, 'caption', 'New caption'); */ class Repeater { protected Meta $meta; protected string $name; protected array $data; protected array $config; public function __construct(Meta $meta, string $name) { $this->meta = $meta; $this->name = $name; $this->data = $meta->get($name) ?: []; $this->config = $meta->config($name) ?? []; // Ensure data is array if (!is_array($this->data)) { $this->data = []; } } // ───────────────────────────────────────────────────────────── // Read Operations // ───────────────────────────────────────────────────────────── /** * Get all rows */ public function all(): array { return $this->data; } /** * Get a specific row */ public function row(int $index): ?array { return $this->data[$index] ?? null; } /** * Get first row */ public function first(): ?array { return $this->data[0] ?? null; } /** * Get last row */ public function last(): ?array { if (empty($this->data)) { return null; } return $this->data[array_key_last($this->data)]; } /** * Get field value from specific row */ public function field(int $index, string $field): mixed { return $this->data[$index][$field] ?? null; } /** * Get row count */ public function count(): int { return count($this->data); } /** * Check if empty */ public function isEmpty(): bool { return empty($this->data); } /** * Check if row exists */ public function hasRow(int $index): bool { return isset($this->data[$index]); } // ───────────────────────────────────────────────────────────── // Write Operations // ───────────────────────────────────────────────────────────── /** * Set field value in specific row */ public function setField(int $index, string $field, mixed $value): self { if (!isset($this->data[$index])) { $this->data[$index] = []; } $this->data[$index][$field] = $value; $this->sync(); return $this; } /** * Add a new row */ public function addRow(array $data = []): self { $this->data[] = $data; $this->sync(); return $this; } /** * Insert row at specific index */ public function insertRow(int $index, array $data = []): self { array_splice($this->data, $index, 0, [$data]); $this->sync(); return $this; } /** * Update entire row (merge with existing) */ public function updateRow(int $index, array $data): self { $this->data[$index] = array_merge($this->data[$index] ?? [], $data); $this->sync(); return $this; } /** * Replace entire row */ public function replaceRow(int $index, array $data): self { $this->data[$index] = $data; $this->sync(); return $this; } /** * Remove a row */ public function removeRow(int $index): self { unset($this->data[$index]); $this->data = array_values($this->data); // Re-index $this->sync(); return $this; } /** * Remove last row */ public function pop(): self { array_pop($this->data); $this->sync(); return $this; } /** * Remove first row */ public function shift(): self { array_shift($this->data); $this->sync(); return $this; } /** * Clear all rows */ public function clear(): self { $this->data = []; $this->sync(); return $this; } /** * Reorder rows */ public function reorder(array $newOrder): self { $reordered = []; foreach ($newOrder as $index) { if (isset($this->data[$index])) { $reordered[] = $this->data[$index]; } } $this->data = $reordered; $this->sync(); return $this; } /** * Move row to new position */ public function moveRow(int $from, int $to): self { if (!isset($this->data[$from])) { return $this; } $row = $this->data[$from]; unset($this->data[$from]); $this->data = array_values($this->data); array_splice($this->data, $to, 0, [$row]); $this->sync(); return $this; } // ───────────────────────────────────────────────────────────── // Query Operations // ───────────────────────────────────────────────────────────── /** * Find rows where field equals value */ public function where(string $field, mixed $value): array { return array_filter($this->data, fn($row) => ($row[$field] ?? null) === $value); } /** * Find first row where field equals value */ public function firstWhere(string $field, mixed $value): ?array { foreach ($this->data as $row) { if (($row[$field] ?? null) === $value) { return $row; } } return null; } /** * Find row index where field equals value */ public function findIndex(string $field, mixed $value): ?int { foreach ($this->data as $index => $row) { if (($row[$field] ?? null) === $value) { return $index; } } return null; } /** * Filter rows by callback */ public function filter(callable $callback): array { return array_filter($this->data, $callback); } /** * Map rows through callback */ public function map(callable $callback): array { return array_map($callback, $this->data); } /** * Pluck single field from all rows */ public function pluck(string $field): array { return array_column($this->data, $field); } /** * Sort rows by field */ public function sortBy(string $field, string $direction = 'asc'): self { usort($this->data, function($a, $b) use ($field, $direction) { $aVal = $a[$field] ?? null; $bVal = $b[$field] ?? null; $result = $aVal <=> $bVal; return $direction === 'desc' ? -$result : $result; }); $this->sync(); return $this; } // ───────────────────────────────────────────────────────────── // Bulk Operations // ───────────────────────────────────────────────────────────── /** * Replace all rows */ public function setAll(array $rows): self { $this->data = $rows; $this->sync(); return $this; } /** * Append multiple rows */ public function addRows(array $rows): self { foreach ($rows as $row) { $this->data[] = $row; } $this->sync(); return $this; } /** * Update field in all rows */ public function setFieldAll(string $field, mixed $value): self { foreach ($this->data as $index => $row) { $this->data[$index][$field] = $value; } $this->sync(); return $this; } /** * Remove field from all rows */ public function removeFieldAll(string $field): self { foreach ($this->data as $index => $row) { unset($this->data[$index][$field]); } $this->sync(); return $this; } // ───────────────────────────────────────────────────────────── // Internal // ───────────────────────────────────────────────────────────── /** * Sync data back to Meta instance */ protected function sync(): void { $this->meta->set($this->name, $this->data); } /** * Get field config for subfield */ public function fieldConfig(string $field): ?array { return $this->config['fields'][$field] ?? null; } /** * Get row label field name */ public function rowLabelField(): ?string { return $this->config['row_label'] ?? null; } }