Skip to content

Save

REST persistence trait (controller-side).

Goals: - Provide a stable save() entry point (single + batch) - Optionally expose controller-mappable create() and update() helpers - Enforce forced mode semantics (create must never update, update must never create) - Keep identity resolution data-driven (row-scoped), not request/global-scoped

Contract: - Single save: { saved: bool, messages: [], data?: mixed, mode?: 'create'|'update' } - Batch save: { saved: bool, messages: [], results: [], stats: } }


  • Full name: \PhalconKit\Mvc\Controller\Traits\Query\Save

Methods

create

Force CREATE semantics.

public create(): array
  • Never updates
  • Fails if identity is present in payload

Throws:

When request payload filtering fails. - Exception When persistence intent resolution returns an inconsistent framework state. - LogicException


update

Force UPDATE semantics.

public update(): array
  • Never creates
  • Fails if identity is missing or does not resolve

Throws:

When request payload filtering fails. - Exception When persistence intent resolution returns an inconsistent framework state. - LogicException


save

Generic save entry point.

public save(?string $forceMode = null): array

Payload shapes: - Single entity: associative array - Batch: list of associative arrays

Mode: - null => auto (create if no identity, else update if identity resolves) - 'create' => force create (no identity allowed) - 'update' => force update (identity must resolve)

Parameters:

Parameter Type Description
$forceMode ?string

Throws:

When request payload filtering fails. - Exception When persistence intent resolution returns an inconsistent framework state. - LogicException


saveMany

Saves many entities (best-effort).

protected saveMany(array $rows, ?string $forceMode): array

Semantics: - Continues on errors - Returns per-row results + stats - Root messages are summary-only (legacy compatibility)

Parameters:

Parameter Type Description
$rows array
$forceMode ?string

saveOne

Saves a single entity.

protected saveOne(array $data, ?string $forceMode): array

Implementation is intentionally split into small, testable phases: - resolvePersistenceIntent(): mode + model selection (create/update) - assignModelFromPayload(): assignment + beforeAssign event - persistAssignedModel(): save + events + eager loading + expose

Parameters:

Parameter Type Description
$data array
$forceMode ?string

Throws:

When intent resolution returns no model or an unsupported persistence mode after reporting no failure. - LogicException


requireResolvedPersistenceIntent

Require a successful persistence-intent tuple.

protected requireResolvedPersistenceIntent(string|null $mode, \Phalcon\Mvc\ModelInterface|null $model): array{0: "create"|"update", 1: \Phalcon\Mvc\ModelInterface}

resolvePersistenceIntent() returns either a REST failure payload or the internal tuple needed by the rest of the save pipeline. This helper keeps the orchestration path readable and documents the invariant explicitly: once no failure was returned, a model instance and one of the two supported persistence modes must be present.

Parameters:

Parameter Type Description
$mode string|null Resolved persistence mode.
$model \Phalcon\Mvc\ModelInterface|null Resolved target model.

Throws:

When intent resolution returns no model or an unsupported persistence mode after reporting no failure. - LogicException


resolvePersistenceIntent

Resolves: - the effective mode ('create'|'update') - the target model instance

protected resolvePersistenceIntent(array $data, ?string $forceMode): array

Forced mode invariants: - force 'create': must NOT update; identity is forbidden - force 'update': must NOT create; identity must resolve to an entity

Returns a 3-tuple: [$mode, $model|null, $failure|null]

Parameters:

Parameter Type Description
$data array
$forceMode ?string

findModelByIdentityPayload

Finds an entity using identity extracted from the provided payload.

protected findModelByIdentityPayload(array $payload): ?\Phalcon\Mvc\ModelInterface

This is intentionally payload-driven, so batch operations do not rely on controller/global params.

Notes: - Uses buildIdentityConditionFromData($data) (from IdentityConditions refactor) - Temporarily overrides the "default" identity condition used by findFirst() - Restores the previous value afterward to prevent leakage across batch rows

Parameters:

Parameter Type Description
$payload array

assignModelFromPayload

Assigns payload to the model using saveFields/mapFields rules.

protected assignModelFromPayload(\Phalcon\Mvc\ModelInterface $model, array& $data): void
  • Strips identity fields before assign to prevent accidental PK changes
  • Fires rest:beforeAssign with references allowing upstream mutation

Parameters:

Parameter Type Description
$model \Phalcon\Mvc\ModelInterface
$data array

persistAssignedModel

Saves the model and returns a canonical REST payload.

protected persistAssignedModel(\Phalcon\Mvc\ModelInterface $model, string $mode): array
  • Fires rest:beforeSave (may return false to abort)
  • Saves model
  • Fires rest:afterSave
  • Optionally eager-loads relations
  • Exposes model

Parameters:

Parameter Type Description
$model \Phalcon\Mvc\ModelInterface
$mode string

buildRestSaveFailure

Builds a canonical failure payload for save operations.

protected buildRestSaveFailure(string $message, string $type, int $code, string|array|null $field = null): array

Naming is intentionally REST/save-specific (not generic "error") because: - This trait is used by REST controllers - The returned shape is part of the REST contract

Parameters:

Parameter Type Description
$message string Human message
$type string Machine-ish type (Phalcon message type)
$code int HTTP-ish code (used by action layer)
$field string|array|null Optional field(s) (e.g., PK attributes)

hasPrimaryKey

Detects whether payload contains identity.

protected hasPrimaryKey(array $data): bool

Override when you add: - composite primary keys - dynamic key names per model

Parameters:

Parameter Type Description
$data array

stripPrimaryKey

Removes identity fields from the payload so they cannot be mass-assigned.

protected stripPrimaryKey(array& $data): void

Override if identity keys differ.

Parameters:

Parameter Type Description
$data array