c1ecefafc0
- Project model: +regions in fillable + cast via PostgresIntArray
(custom Eloquent cast for PG INT[] — Laravel stock 'array' uses JSON
which Postgres rejects on native INT[] columns)
- StoreProjectRequest / UpdateProjectRequest: drop region_mask/mode rules,
add regions array validation (1..89 each, present/sometimes)
- ProjectService::create: dual-write — regions источник истины + legacy
region_mask=255 + region_mode='include' для PhonePrefixService/LeadRouter
compatibility (Plan 6.5 cleanup will remove dual-write)
- +5 Pest tests covering create/update/dual-write/validation rejection
- Drive-by: SchemaDeltaTest indexes pin 117 → 118 (Plan 6 v8.20 carryover
from Task 1; should ideally have landed in Task 1 commit c487641)
- phpstan-baseline: +3 entries for Project::$regions until next ide-helper
regen; existing Pest actingAs counts bumped 9→12 / 6→8 for new tests
Verified: Pest --parallel 747/744/3sk/0/0 (5 new tests pass +
SchemaDeltaTest now green), phpstan 0 errors, pint clean, gitleaks 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.3 KiB
PHP
83 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Casts;
|
|
|
|
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Eloquent cast for PostgreSQL native INT[] columns.
|
|
*
|
|
* Laravel stock 'array' cast uses json_encode/json_decode and sends `[1,2,3]`
|
|
* (JSON), which Postgres rejects on INT[] columns (expects `{1,2,3}` array
|
|
* literal). This cast:
|
|
*
|
|
* - get(): parses Postgres array literal `{1,2,3}` (or empty `{}`) into PHP
|
|
* int array.
|
|
* - set(): serializes PHP array `[1,2,3]` into Postgres literal `{1,2,3}`.
|
|
*
|
|
* Used for projects.regions INT[] (Plan 6).
|
|
*
|
|
* @implements CastsAttributes<list<int>, list<int>|null>
|
|
*/
|
|
class PostgresIntArray implements CastsAttributes
|
|
{
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
* @return list<int>
|
|
*/
|
|
public function get(Model $model, string $key, mixed $value, array $attributes): array
|
|
{
|
|
if ($value === null || $value === '' || $value === '{}') {
|
|
return [];
|
|
}
|
|
|
|
// PG returns literal like "{1,2,3}".
|
|
if (is_string($value)) {
|
|
$trimmed = trim($value, '{}');
|
|
|
|
if ($trimmed === '') {
|
|
return [];
|
|
}
|
|
|
|
return array_map('intval', explode(',', $trimmed));
|
|
}
|
|
|
|
// Defensive: if driver already gave array.
|
|
if (is_array($value)) {
|
|
return array_values(array_map('intval', $value));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function set(Model $model, string $key, mixed $value, array $attributes): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
// Defensive: interface phpdoc says list<int>|null, but $value is mixed at PHP level;
|
|
// protect against runtime misuse (e.g., string passed mistakenly).
|
|
// @phpstan-ignore function.alreadyNarrowedType
|
|
if (! is_array($value)) {
|
|
throw new \InvalidArgumentException(
|
|
"PostgresIntArray cast expects array for key '{$key}', got ".gettype($value)
|
|
);
|
|
}
|
|
|
|
if ($value === []) {
|
|
return '{}';
|
|
}
|
|
|
|
$ints = array_map('intval', $value);
|
|
|
|
return '{'.implode(',', $ints).'}';
|
|
}
|
|
}
|