- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
125 lines
3.1 KiB
PHP
125 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Pagination\ValueObjects;
|
|
|
|
use App\Framework\Serializer\Json\JsonSerializer;
|
|
use App\Framework\Serializer\Exception\SerializeException;
|
|
use App\Framework\Serializer\Exception\DeserializeException;
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Cursor for cursor-based pagination
|
|
*/
|
|
final readonly class Cursor
|
|
{
|
|
public function __construct(
|
|
public string $value,
|
|
public Direction $direction = Direction::ASC
|
|
) {
|
|
if (empty($this->value)) {
|
|
throw new InvalidArgumentException('Cursor value cannot be empty');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create cursor from base64 encoded value
|
|
*/
|
|
public static function fromEncoded(string $encodedValue, Direction $direction = Direction::ASC): self
|
|
{
|
|
$decoded = base64_decode($encodedValue, true);
|
|
if ($decoded === false) {
|
|
throw new InvalidArgumentException('Invalid cursor encoding');
|
|
}
|
|
|
|
return new self($decoded, $direction);
|
|
}
|
|
|
|
/**
|
|
* Create cursor from field value
|
|
*/
|
|
public static function fromField(string $field, mixed $value, Direction $direction = Direction::ASC): self
|
|
{
|
|
$serializer = new JsonSerializer();
|
|
|
|
try {
|
|
$cursorValue = $serializer->serialize([$field => $value]);
|
|
} catch (SerializeException $e) {
|
|
throw new InvalidArgumentException('Cannot encode cursor value: ' . $e->getMessage(), 0, $e);
|
|
}
|
|
|
|
return new self($cursorValue, $direction);
|
|
}
|
|
|
|
/**
|
|
* Get base64 encoded cursor value
|
|
*/
|
|
public function toEncoded(): string
|
|
{
|
|
return base64_encode($this->value);
|
|
}
|
|
|
|
/**
|
|
* Parse cursor value as array
|
|
*/
|
|
public function parseValue(): array
|
|
{
|
|
$serializer = new JsonSerializer();
|
|
|
|
try {
|
|
$parsed = $serializer->deserialize($this->value);
|
|
} catch (DeserializeException $e) {
|
|
throw new InvalidArgumentException('Cursor value is not valid JSON: ' . $e->getMessage(), 0, $e);
|
|
}
|
|
|
|
if (!is_array($parsed)) {
|
|
throw new InvalidArgumentException('Cursor value is not an array');
|
|
}
|
|
|
|
return $parsed;
|
|
}
|
|
|
|
/**
|
|
* Get field value from cursor
|
|
*/
|
|
public function getFieldValue(string $field): mixed
|
|
{
|
|
$parsed = $this->parseValue();
|
|
return $parsed[$field] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Check if cursor is for ascending direction
|
|
*/
|
|
public function isAscending(): bool
|
|
{
|
|
return $this->direction->isAscending();
|
|
}
|
|
|
|
/**
|
|
* Check if cursor is for descending direction
|
|
*/
|
|
public function isDescending(): bool
|
|
{
|
|
return $this->direction->isDescending();
|
|
}
|
|
|
|
/**
|
|
* Create new cursor with different direction
|
|
*/
|
|
public function withDirection(Direction $direction): self
|
|
{
|
|
return new self($this->value, $direction);
|
|
}
|
|
|
|
public function toString(): string
|
|
{
|
|
return $this->toEncoded();
|
|
}
|
|
|
|
public function __toString(): string
|
|
{
|
|
return $this->toString();
|
|
}
|
|
} |