Skip to content
yarunoka.dev

Usage

View Markdown

The bridge adds three surfaces on top of the engine: container services built from the config, Eloquent casts for storing schedules in columns, and validation rules for rejecting a bad request. Everything the engine itself does — parsing, building, and the three questions — is documented by yarunoka/core.

YrnkEvaluator and YrnkParser come out of the container carrying the configured environment:

use Yarunoka\YrnkEvaluator;
use Yarunoka\YrnkParser;
app(YrnkEvaluator::class)->matches($schedule, now());
app(YrnkParser::class)->parse($json); // knows the config's resolver names

A column holding one schedule — a JSON object judged in the application’s calendar — is cast by naming the wrapper in casts():

use Yarunoka\Laravel\Schedule;
class Routine extends Model
{
protected function casts(): array
{
return ['schedule' => Schedule::class];
}
}

Writing accepts the spellings a schedule arrives in — a PHP array, a JSON string, a YrnkSchedule, or the wrapper itself — and validates on both paths: structure, values, and that every name resolves in the configured environment. An invalid schedule stops on an exception and never reaches the database; a broken column value surfaces on read as InvalidYrnkColumnException, naming the model and the column.

$routine = Routine::create([
'schedule' => ['days' => [25], 'shift' => ['prev', 'or_same', 'business_day'], 'times' => ['10:00']],
]);

Reading gives the wrapper back. It is readonly — a change is a reassignment of the whole attribute — and the model’s toArray() / toJson() show the stored spelling of the DSL as it is. The core’s YrnkSchedule stays exposed as $routine->schedule->yrnkSchedule, so everything the engine can do with a schedule stays reachable.

A column may instead hold a schedules part — a JSON list of schedules, like a document’s schedules — by naming Schedules:

use Yarunoka\Laravel\Schedules;
protected function casts(): array
{
return ['schedules' => Schedules::class];
}

The wrapper holds a list of Schedule (each carrying its YrnkSchedule), and writing additionally accepts a list of Schedule or YrnkSchedule. Two things differ from a document: the calendar and timezone come from the config rather than travelling with the data, and the document-level “no duplicate schedules” rule does not apply — identical schedules are legal in a column and fire as one.

Both wrappers carry the firing decision — Schedules composes its schedules with any:

if ($routine->schedule->isDue(now(), since: $routine->last_run_at)) {
run();
$routine->last_run_at = now();
}

isDue asks whether the schedule has a scheduled point after since, through at — the same half-open question as the engine’s hasMatchIn, so each question’s “now” becomes the next one’s start and every point is seen exactly once across a series of polls. Catch-up, grace, and throttling are the caller’s decisions, made by how the period is cut; the engine documentation covers the patterns.

A column may instead hold a whole Yrnk document — its own timezone and calendar per row, for rows whose rules do not share the application’s environment:

use Yarunoka\Laravel\Casts\AsYrnk;
protected function casts(): array
{
return ['document' => AsYrnk::class];
}

Reading gives a bare Yrnk. A whole-document column stores such a document and grows no judgment conveniences; whoever wants to judge builds an evaluator from the document itself:

use Yarunoka\YrnkEvaluator;
$evaluator = YrnkEvaluator::fromYrnk($routine->document);

Writing accepts a Yrnk, an array, or a JSON string, and always passes construction validation before the database.

The rules try the construction and put the engine’s message on the validation error as it is:

use Yarunoka\Laravel\Rules\ValidYrnk;
use Yarunoka\Laravel\Rules\ValidYrnkSchedule;
use Yarunoka\Laravel\Rules\ValidYrnkSchedules;
$validated = $request->validate([
'schedule' => ['required', new ValidYrnkSchedule()],
'schedules' => ['sometimes', new ValidYrnkSchedules()],
'document' => ['sometimes', new ValidYrnk()],
]);

ValidYrnkSchedule checks one schedule, ValidYrnkSchedules a schedules part, ValidYrnk a whole document — structure, values, and references against the configured environment. What passed validation is exactly what the cast will accept, so validate-then-store never fails in the second step.

JSON varies in key order and whitespace as a string, so the casts compare decoded values: reassigning a column with the same content in a different spelling is not dirty, and no pointless UPDATE runs. Changed content is dirty as usual.

Everything the bridge and the engine throw implements Yarunoka\Exceptions\ExceptionInterface. The bridge adds one exception of its own: InvalidYrnkColumnException, raised when a stored column cannot be read back — it names the model and the column, because a cast’s failure surfaces in the middle of model retrieval where the cause is otherwise hard to see. Validation failures never reach an exception: the rules turn them into validation errors instead.