github phalcon/cphalcon v5.17.0

5 hours ago

5.17.0 (2026-07-17)

Tools

  • Zephir 1.1.0 (80d17e6a0)

Changed

  • Changed Phalcon\Acl\Adapter\Memory so a freshly constructed adapter returns an empty array instead of null from getRoles(), getComponents() and getInheritedRoles(). #17220 [doc]
  • Changed the Phalcon\Auth array adapters (Memory, Stream) to compare non-password credential fields against configured row values as strings, so string input from a request (e.g. '1') matches a typed row value (e.g. 1 or true) instead of failing a strict type comparison. #17220 [doc]
  • Changed the Phalcon\Auth layer to throw granular Phalcon\Auth\Exceptions\* subclasses instead of the base Phalcon\Auth\Exception: AccessNotRegistered, ActiveAccessRequired, DefaultGuardNotRegistered and GuardNotDefined (Phalcon\Auth\Manager), UnknownAdapter and UnknownGuard (Phalcon\Auth\ManagerFactory), OptionRequiresArray and OptionRequiresString (fromOptions() option parsing), SessionNamesMustDiffer (Phalcon\Auth\Guard\Config\SessionGuardConfig), and MissingHandlerContext (Phalcon\Auth\Access\Acl). Each extends Phalcon\Auth\Exception, so existing catch blocks keep working. #17220 [doc]
  • Deprecated Phalcon\Acl\Adapter\Memory::getActiveKey() (use getActiveRole(), getActiveComponent() and getActiveAccess()) and the legacy ACL interfaces Phalcon\Acl\Adapter\AdapterInterface, Phalcon\Acl\RoleInterface, Phalcon\Acl\ComponentInterface, Phalcon\Acl\RoleAwareInterface and Phalcon\Acl\ComponentAwareInterface in favour of their Phalcon\Contracts\Acl\... equivalents. #17220 [doc]
  • Note: Phalcon\Auth\ManagerFactory validates the required guard configuration up front and throws a Phalcon\Auth\Exception subclass on a missing key, where earlier versions emitted a PHP notice followed by a TypeError; handlers that caught TypeError there should catch Phalcon\Auth\Exception instead. #17220 [doc]
  • Refactored the read path of model hydration by extracting Phalcon\Mvc\Model::cloneResultMapHydrate() into the dedicated Phalcon\Mvc\Model\Hydration\CloneResultMapHydrate class and the case-insensitive column-map lookup into Phalcon\Mvc\Model\Hydration\CaseInsensitiveColumnMap. #17228 [doc]
  • Phalcon\Support\Helper\Str\Concat::__invoke() was reimplemented as a typed variadic (string $delimiter, string ...$many) - previously it declared no parameters and read them through func_get_args() - with unchanged behavior. #17273 [doc]

Added

  • Added @Connect, @Head, @Purge and @Trace method annotations to Phalcon\Mvc\Router\Annotations, matching the existing @Get, @Post, @Put, @Patch, @Delete and @Options shortcuts. [doc]
  • Added Phalcon\Acl\Adapter\Storage, a storage-backed ACL adapter that persists the entire policy as a versioned, serializer-agnostic snapshot to any Phalcon\Storage backend (Redis, Apcu, Stream, Memcached) and reloads it on construction, defined by the new Phalcon\Contracts\Acl\Adapter\Persistable contract (save()/load()). Callable (closure) rules are not serializable, so they are persisted as DENY (fail closed) and must be re-registered after load(); load() returns false for a snapshot without a version marker and throws Phalcon\Acl\Exceptions\InvalidSnapshot on an incompatible version or a malformed structure. Single-writer contract: save() writes the whole snapshot (last-write-wins; use external locking for concurrent writers). #17220 [doc]
  • Added Phalcon\Acl\Exceptions\InvalidSnapshot, thrown by Phalcon\Acl\Adapter\Storage::load() on an incompatible or malformed policy snapshot. #17220 [doc]
  • Added Phalcon\Encryption\Security\JWT\Validator::validateSubject(), which compares the token's sub claim against the expected subject and reports Validation: incorrect subject on a mismatch. A null subject expresses no expectation and is skipped. #17361 [doc]
  • Added Phalcon\Filter\Validation::setDefaultMessages() and Phalcon\Filter\Validation::getDefaultMessage() for registering global default validator failure messages keyed by validator class name (e.g. Validation::setDefaultMessages([PresenceOf::class => 'Default message :field is required'])). A registered default overrides a validator's built-in class default message, while a message set on the validator instance (the constructor message/template option or setTemplate()) still takes precedence; it applies to validators whose message is produced through getTemplate()/messageFactory(). #17257 [doc]
  • Added Phalcon\Filter\Validation\Validator\Files, a validator for an array of uploaded files (<input name="files[]" type="file" multiple>). It accepts the same options as Phalcon\Filter\Validation\Validator\File, normalizes a single-file or transposed multi-file $_FILES node into individual files, and validates each by delegating to File, failing on the first file that violates a rule. #17259 [doc]
  • Added Phalcon\Mvc\Model\Query::setResultsetRowClass() and Phalcon\Mvc\Model\Query::getResultsetRowClass() to control the class used to hydrate rows that are not mapped to a model (custom-column SELECTs and joins). When set, those result rows are built as the given subclass of Phalcon\Mvc\Model\Row instead of Row itself - on both the simple (custom columns) and complex (join) resultset paths - so they can carry reusable helper methods. The class is validated when set (it must exist and be a subclass of Phalcon\Mvc\Model\Row), throwing the new Phalcon\Mvc\Model\Query\Exceptions\ResultsetRowClassNotFound or Phalcon\Mvc\Model\Query\Exceptions\InvalidResultsetRowClass. #17337 [doc]
  • Added Phalcon\Mvc\Model\Query\Builder::setResultsetRowClass() and Phalcon\Mvc\Model\Query\Builder::getResultsetRowClass() so the custom resultset row class can be set on a query builder, which forwards it to the Phalcon\Mvc\Model\Query it produces in getQuery(). Because Phalcon\Paginator\Adapter\QueryBuilder builds its query through the builder, paginate() now returns the given Phalcon\Mvc\Model\Row subclass for its non-model result rows. #17337 [doc]
  • Added a set of reusable traits under the Phalcon\Traits namespace that framework classes compose instead of carrying duplicated logic. The public traits:
    • Phalcon\Traits\Support\Helper\Arr\GetTrait - read an array element by key with a default value and an optional cast.
    • Phalcon\Traits\Support\Helper\Arr\FilterTrait - filter a collection with array_filter() and an optional callable.
    • Phalcon\Traits\Support\Helper\Str\DirFromFileTrait - build a nested directory path from a file name, with an optional path-safety flag.
    • Phalcon\Traits\Support\Helper\Str\DirSeparatorTrait - ensure a directory string ends with exactly one DIRECTORY_SEPARATOR.
    • Phalcon\Traits\Support\Helper\Str\EndsWithTrait / StartsWithTrait - case-insensitive, multibyte-aware suffix/prefix checks.
    • Phalcon\Traits\Support\Helper\Str\InterpolateTrait - PSR-3 %placeholder% message interpolation.
    • Phalcon\Traits\Support\Helper\Str\CamelizeTrait / UncamelizeTrait - convert between CamelCase and delimited strings.
    • Phalcon\Traits\Support\Helper\Str\LowerTrait / UpperTrait - multibyte-safe lower/upper casing.
    • Phalcon\Traits\Support\Helper\Json\EncodeTrait / DecodeTrait - json_encode()/json_decode() wrappers that throw the native \JsonException.
    • Phalcon\Traits\Php\ApcuTrait - wrappers for apcu_* methods.
    • Phalcon\Traits\Php\Base64Trait - wrappers for base64_* methods.
    • Phalcon\Traits\Php\FileTrait - wrappers for file_* methods.
    • Phalcon\Traits\Php\HashTrait - wrappers for hash_* methods.
    • Phalcon\Traits\Php\HeaderTrait - wrapper for headers_sent.
    • Phalcon\Traits\Php\IgbinaryTrait - wrappers for igbinary_* methods.
    • Phalcon\Traits\Php\InfoTrait - wrappers for extension_loaded and `function_exists.
    • Phalcon\Traits\Php\IniTrait - wrappers for ini_* methods.
    • Phalcon\Traits\Php\MbCaseTrait - wrappers for mb_case_* methods.
    • Phalcon\Traits\Php\MsgpackTrait - wrappers for msgpack_* methods.
    • Phalcon\Traits\Php\OpensslTrait - wrappers for openssl_* methods.
    • Phalcon\Traits\Php\SerializeTrait - wrappers for serialize and unserialize.
    • Phalcon\Traits\Php\UrlTrait - wrappers for parse_url, rawurldecode and rawurlencode.
    • Phalcon\Traits\Php\YamlTrait - - wrappers for yaml_* methods. These calls can be substituted in tests. #17273 [doc]
  • Added an opt-in "sticky" read/write connection mode to Phalcon\Mvc\Model\Manager. After Phalcon\Mvc\Model\Manager::setSticky(true), once a model has written to its write connection during the current request cycle, any further reads for that write service are served from the write connection, so data written earlier in the request can be read back immediately. Writes are recorded via the new Phalcon\Mvc\Model\Manager::registerWrite() (called internally on a successful insert/update/delete), and Phalcon\Mvc\Model\Manager::resetConnectionState() clears the per-request tracking for long-running runtimes (e.g. Swoole, RoadRunner) that reuse the manager across requests. Sticky is off by default, preserving the existing read/write split; the transaction connection still takes precedence. The three methods are added to Phalcon\Mvc\Model\ManagerInterface. #17256 [doc]
  • Added an opt-in allowWildcards option to Phalcon\Filter\Validation\Validator\File\MimeType (and passed through from Phalcon\Filter\Validation\Validator\File via the same key) that, when true, matches each configured type against the detected MIME type as an anchored regular expression (e.g. image/.*, video/.*) in addition to an exact string match; it defaults to false, preserving the existing exact-match behavior. #17254 [doc]
  • Added closure binding to Phalcon\Filter\Validation\Validator\Callback: a Closure passed as the callback option is now bound to the validator instance ($this) before it is invoked, so the callback can call the validator's own public methods - for example $this->setTemplate('...') to set a per-failure message from inside the callback. Non-closure callables (function-name strings, [object, method] arrays) are left unbound, and the callback's return contract is unchanged (bool, or a Phalcon\Filter\Validation\ValidatorInterface to delegate). #17255 [doc]
  • Added support for absolute and current-template-relative paths in the Volt {% extends %} and {% include %} tags (Phalcon\Mvc\View\Engine\Volt\Compiler): an absolute path (Unix /..., or a Windows drive/UNC path) is now used as-is, and a path beginning with ./ or ../ is resolved against the directory of the template currently being compiled instead of the views directory; every other path keeps the existing views-directory-relative resolution. #17269 [doc]
  • Added the Phalcon\Contracts\Acl contracts - Phalcon\Contracts\Acl\Adapter\Adapter, Phalcon\Contracts\Acl\Adapter\Persistable, Phalcon\Contracts\Acl\Role, Phalcon\Contracts\Acl\Component, Phalcon\Contracts\Acl\RoleAware and Phalcon\Contracts\Acl\ComponentAware - as the canonical homes for the ACL interfaces; the legacy Phalcon\Acl\...\*Interface types remain as deprecated bridges that extend them. #17220 [doc]
  • Added the Phalcon\Contracts\Queue\Inspectable contract (getStats(Queue $queue): array), implemented by Phalcon\Queue\Adapter\Beanstalk\BeanstalkContext, exposing the Beanstalkd stats-tube fields (current-jobs-ready, current-jobs-reserved, current-jobs-delayed, current-jobs-buried, current-jobs-urgent, total-jobs, the cmd-* counters, ...) for queue backlog/depth monitoring. The returned array is adapter-native; the current-jobs-* keys are always present (zero for an unknown tube) and the read runs on its own short-lived connection. Backed by a new Phalcon\Queue\Adapter\Beanstalk\BeanstalkConnection::statsTube() wire command. #17209 [doc]
  • Added the granular Phalcon\Auth\Exceptions\* exceptions AccessNotRegistered, ActiveAccessRequired, DefaultGuardNotRegistered, GuardNotDefined, MissingHandlerContext, OptionRequiresArray, OptionRequiresString, SessionNamesMustDiffer, UnknownAdapter and UnknownGuard. #17220 [doc]

Fixed

  • Fixed Phalcon\Auth login timing leaking account existence: the credential adapters now perform a throwaway password hash on the user-not-found path, so an attempt for an unknown identifier costs the same as one for a real account with a wrong password (mitigates login-timing user enumeration). #17220 [doc]
  • Fixed Phalcon\Db\Dialect::getSqlExpression() throwing The argument is not initialized or iterable() while resolving a case expression when the expression array is held as a PHP reference, by fetching the when-clauses list through array_values() before iterating it. #17225 [doc]
  • Fixed Phalcon\Encryption\Crypt::encrypt() to properly capture exceptions thrown by openssl_random_pseudo_bytes. #17326 [doc]
  • Fixed Phalcon\Encryption\Security\JWT\Token\Token::validate() throwing Phalcon\Encryption\Security\JWT\Exceptions\InvalidAudienceType when handed a freshly constructed Phalcon\Encryption\Security\JWT\Validator, which made a default Validator impossible to pass to it. #17361 [doc]
  • Fixed Phalcon\Encryption\Security\JWT\Validator::validateIssuedAt() and Phalcon\Encryption\Security\JWT\Validator::validateNotBefore() rejecting a token whose iat/nbf claim falls on exactly the validated timestamp. #17361 [doc]
  • Fixed Phalcon\Forms\Element\AbstractElement::render() to cast a non-null element value to string before passing it to the input helper, so a numeric default set via setDefault() (e.g. setDefault(10) or setDefault(10.5) on a Phalcon\Forms\Element\Numeric) renders as value="10" instead of raising a TypeError for passing an int/float to the helper's string $value parameter. #17232 [doc]
  • Fixed Phalcon\Http\Response::getStatusCode() and Phalcon\Http\Response::getReasonPhrase() raising a TypeError (substr(): Argument #1 ($string) must be of type string, bool given) when no Status header had been set (e.g. a response built with only setContent()), because Phalcon\Http\Response\Headers::get('Status') returns false for an absent header; the header value is now cast to string before substr(), so both methods return null as documented. #17248 [doc]
  • Fixed Phalcon\Image\Adapter\AbstractAdapter::resize() truncating the scaled master-mode (Enum::WIDTH, Enum::HEIGHT, Enum::PRECISE) dimension to an int before rounding, so a value whose fractional part was >= 0.5 came out one pixel short (e.g. a 1820x694 source resized to height 80 produced width 209 instead of 210); the scaled width/height are now rounded before the integer cast. #17225 [doc]
  • Fixed Phalcon\Mvc\Model::cloneResult() and Phalcon\Mvc\Model::cloneResultMap() calling a model setter (or throwing Phalcon\Mvc\Model\Exceptions\PropertyNotAccessible when no setter exists) while hydrating a declared private property, because the property write from Model scope fell back to __set() and its possibleSetter() routing regardless of the orm.call_setters_on_hydration setting. #16454 [doc]
  • Fixed Phalcon\Mvc\Model::create() and Phalcon\Mvc\Model::update() passing null to the field argument of Phalcon\Messages\Message (typed string since v5.14), which raised a Passing null to parameter #2 ($field) of type string is deprecated warning when calling create() on an existing record or update() on a non-existent one; they now pass an empty string. #17224 [doc]
  • Fixed Phalcon\Mvc\Model\Query::executeUpdate() raising a PDO Invalid parameter number: mixed named and positional parameters error for a PHQL UPDATE whose SET clause is an expression carrying a bound placeholder (e.g. SET col = col + :inc:): the named placeholder is now resolved from the bind parameters and inlined into the expression before the Phalcon\Db\RawValue is built, so it no longer collides with the positional ? marker of the primary-key WHERE clause, and the placeholder is removed from the bind parameters forwarded to the pre-update SELECT. #16976 [doc]
  • Fixed a segmentation fault when rendering a Volt template that extends a parent chain but defines no blocks of its own, where a block declared higher in the chain calls {{ partial() }}: Phalcon\Mvc\View\Engine\Volt\Compiler::compileSource() passed a null blocks value to array_key_exists(), which read it as an array from an uninitialized pointer; blocks is now coerced to an empty array so any inheritance depth is handled. #17294 [doc]
  • Fixed the PHP 8.4/8.5 deprecation notices raised by the extension: removed the imagedestroy() calls in Phalcon\Image\Adapter\Gd (a no-op since PHP 8.0), the finfo_close() calls in Phalcon\Http\Request\File and Phalcon\Filter\Validation\Validator\File\MimeType and the ReflectionProperty::setAccessible() call in Phalcon\Support\Debug\Dump (no-ops since PHP 8.1), clamped the random pad byte in Phalcon\Encryption\Crypt\Padding\Iso10126 to chr(rand() % 256) to avoid the out-of-range chr() deprecation on PHP 8.5, and guarded Phalcon\Messages\Messages::offsetSet() against an implicit null array offset. #17253 [doc]

Removed

  • Removed the deprecated Serializable interface from Phalcon\Mvc\Model and Phalcon\Mvc\Model\Resultset (deprecated by PHP 8.1); the __serialize() and __unserialize() magic methods remain, so model and resultset serialization is unchanged. #17253 [doc]

Don't miss a new cphalcon release

NewReleases is sending notifications on new releases.