Phalcon v5.17.0 is here, and for once the release notes do not fit inside a single repository. Along with the framework itself, this cycle brought new packages, a from-scratch testing tool, and a wave of modernized sample applications.
A New Hope
Under the hood, 5.17.0 is the first release built on Zephir 1.1.0, a big step for the toolchain and one that clears the runway for the upcoming v6. On top of that, the core picked up 16 new features and 13 fixes. A few of the highlights:
- Storage-backed ACL. The new
Phalcon\Acl\Adapter\Storagepersists your whole ACL to anyPhalcon\Storagebackend (Redis, APCu, Memcached, Stream) and loads it back on construction, so you no longer have to rebuild the list on every boot. - Sticky read/write connections. Turn on
setSticky(true)on the model manager and, once a model writes, its later reads in the same request go to the write connection. No more reading stale data from a replica right after you saved it. - Custom resultset rows.
Query::setResultsetRowClass()lets custom-column queries and joins hydrate into your ownRowsubclass instead of a plain object.
That is just the top of the list. The rest (new validators, a Files upload validator, JWT subject validation, smarter Volt template paths, the full set of router HTTP-verb annotations, and a public Phalcon\Traits namespace you can use in your own code) is in the changelog below.
Of course every change is mirrored in the v6 repo!
The Expanded Universe
Attack of the Clones
Talon is a PHPUnit-based testing toolkit built specifically for Phalcon. We used to have a few classes in incubator-test but they were outdated. We decided to make our own, that will fit the needs of any Phalcon application
Talon can drive an application in-process with no web server for browser-style tests, ships a talon command-line runner, and runs your suite against both the v5 C extension and the v6 PHP package. We have already converted our top packages to use Talon, so we use it too!!
The Return of the DebugBar
phalcon/debugbar was dormant for years. We decided to tackle that long standing task and rebuild it. It drops a per-request debug bar into your app with eleven collectors (database queries with their bound parameters, routing, views, cache, session, timing, exceptions, logs, and more), a redaction layer that keeps secrets out of the panel, and an access gate so it only appears for you. It boots purely off the events manager, so wiring it in is a one-liner.
In addition - and in anticipation for v7/v8, the Phalcon\Support\Debug has been copied in the debugbar repo. As such, any debugging packages that developers need will live in one spot. The Debug components in the main framework will eventually be deprecated
The Standards Strike Back
Three new bridge packages connect Phalcon to the PSR standards, and each of them works in both directions:
phalcon/bridge-psr3— use Phalcon’s logger as a PSR-3 logger, or feed a PSR-3 logger such as Monolog into Phalcon.phalcon/bridge-psr11— wrap the Phalcon container as a PSR-11 container, or use any PSR-11 container inside Phalcon.phalcon/bridge-psr16— expose Phalcon cache as a PSR-16 cache, or plug a PSR-16 cache into Phalcon’s storage.
We do have planned more bridge packages (especially for PSR-7) and those will come in due time.
The Apps Awaken
The sample applications got a serious refresh. INVO and Vökuró jumped forward several PHP versions, moved to a modern project layout, and picked up real security fixes (proper password hashing and hardened authentication). Like the REST API sample, they now run on both the Phalcon 5 extension and the Phalcon 6 PHP package from a single codebase, chosen at build time. Vökuró even ships with the new DebugBar already wired in.
Speaking of v6: the pure-PHP implementation is moving quickly, with v6.0.0alpha4 already out. Everything above is being built and tested against it as we go.
The Rebel Alliance
A huge thanks to everyone who reported bugs, opened pull requests, and tested along the way. This release is as much yours as it is ours.
Changelog
5.17.0 (2026-07-17)
Tools
- Zephir 1.1.0 (80d17e6a0)
Changed
- Changed
Phalcon\Acl\Adapter\Memoryso a freshly constructed adapter returns an empty array instead ofnullfromgetRoles(),getComponents()andgetInheritedRoles(). #17220 [doc] - Changed the
Phalcon\Autharray 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.1ortrue) instead of failing a strict type comparison. #17220 [doc] - Changed the
Phalcon\Authlayer to throw granularPhalcon\Auth\Exceptions\*subclasses instead of the basePhalcon\Auth\Exception:AccessNotRegistered,ActiveAccessRequired,DefaultGuardNotRegisteredandGuardNotDefined(Phalcon\Auth\Manager),UnknownAdapterandUnknownGuard(Phalcon\Auth\ManagerFactory),OptionRequiresArrayandOptionRequiresString(fromOptions()option parsing),SessionNamesMustDiffer(Phalcon\Auth\Guard\Config\SessionGuardConfig), andMissingHandlerContext(Phalcon\Auth\Access\Acl). Each extendsPhalcon\Auth\Exception, so existingcatchblocks keep working. #17220 [doc] - Deprecated
Phalcon\Acl\Adapter\Memory::getActiveKey()(usegetActiveRole(),getActiveComponent()andgetActiveAccess()) and the legacy ACL interfacesPhalcon\Acl\Adapter\AdapterInterface,Phalcon\Acl\RoleInterface,Phalcon\Acl\ComponentInterface,Phalcon\Acl\RoleAwareInterfaceandPhalcon\Acl\ComponentAwareInterfacein favour of theirPhalcon\Contracts\Acl\...equivalents. #17220 [doc] - Note:
Phalcon\Auth\ManagerFactoryvalidates the required guard configuration up front and throws aPhalcon\Auth\Exceptionsubclass on a missing key, where earlier versions emitted a PHP notice followed by aTypeError; handlers that caughtTypeErrorthere should catchPhalcon\Auth\Exceptioninstead. #17220 [doc] - Refactored the read path of model hydration by extracting
Phalcon\Mvc\Model::cloneResultMapHydrate()into the dedicatedPhalcon\Mvc\Model\Hydration\CloneResultMapHydrateclass and the case-insensitive column-map lookup intoPhalcon\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 throughfunc_get_args()- with unchanged behavior. #17273 [doc]
Added
- Added
@Connect,@Head,@Purgeand@Tracemethod annotations toPhalcon\Mvc\Router\Annotations, matching the existing@Get,@Post,@Put,@Patch,@Deleteand@Optionsshortcuts. [doc] - Added
Phalcon\Acl\Adapter\Storage, a storage-backed ACL adapter that persists the entire policy as a versioned, serializer-agnostic snapshot to anyPhalcon\Storagebackend (Redis, Apcu, Stream, Memcached) and reloads it on construction, defined by the newPhalcon\Contracts\Acl\Adapter\Persistablecontract (save()/load()). Callable (closure) rules are not serializable, so they are persisted asDENY(fail closed) and must be re-registered afterload();load()returnsfalsefor a snapshot without a version marker and throwsPhalcon\Acl\Exceptions\InvalidSnapshoton 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 byPhalcon\Acl\Adapter\Storage::load()on an incompatible or malformed policy snapshot. #17220 [doc] - Added
Phalcon\Encryption\Security\JWT\Validator::validateSubject(), which compares the token’ssubclaim against the expected subject and reportsValidation: incorrect subjecton a mismatch. Anullsubject expresses no expectation and is skipped. #17361 [doc] - Added
Phalcon\Filter\Validation::setDefaultMessages()andPhalcon\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 constructormessage/templateoption orsetTemplate()) still takes precedence; it applies to validators whose message is produced throughgetTemplate()/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 asPhalcon\Filter\Validation\Validator\File, normalizes a single-file or transposed multi-file$_FILESnode into individual files, and validates each by delegating toFile, failing on the first file that violates a rule. #17259 [doc] - Added
Phalcon\Mvc\Model\Query::setResultsetRowClass()andPhalcon\Mvc\Model\Query::getResultsetRowClass()to control the class used to hydrate rows that are not mapped to a model (custom-columnSELECTs and joins). When set, those result rows are built as the given subclass ofPhalcon\Mvc\Model\Rowinstead ofRowitself - 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 ofPhalcon\Mvc\Model\Row), throwing the newPhalcon\Mvc\Model\Query\Exceptions\ResultsetRowClassNotFoundorPhalcon\Mvc\Model\Query\Exceptions\InvalidResultsetRowClass. #17337 [doc] - Added
Phalcon\Mvc\Model\Query\Builder::setResultsetRowClass()andPhalcon\Mvc\Model\Query\Builder::getResultsetRowClass()so the custom resultset row class can be set on a query builder, which forwards it to thePhalcon\Mvc\Model\Queryit produces ingetQuery(). BecausePhalcon\Paginator\Adapter\QueryBuilderbuilds its query through the builder,paginate()now returns the givenPhalcon\Mvc\Model\Rowsubclass for its non-model result rows. #17337 [doc] - Added a set of reusable traits under the
Phalcon\Traitsnamespace 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 witharray_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 oneDIRECTORY_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 betweenCamelCaseand 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 forapcu_*methods.Phalcon\Traits\Php\Base64Trait- wrappers forbase64_*methods.Phalcon\Traits\Php\FileTrait- wrappers forfile_*methods.Phalcon\Traits\Php\HashTrait- wrappers forhash_*methods.Phalcon\Traits\Php\HeaderTrait- wrapper forheaders_sent.Phalcon\Traits\Php\IgbinaryTrait- wrappers forigbinary_*methods.Phalcon\Traits\Php\InfoTrait- wrappers forextension_loadedand `function_exists.Phalcon\Traits\Php\IniTrait- wrappers forini_*methods.Phalcon\Traits\Php\MbCaseTrait- wrappers formb_case_*methods.Phalcon\Traits\Php\MsgpackTrait- wrappers formsgpack_*methods.Phalcon\Traits\Php\OpensslTrait- wrappers foropenssl_*methods.Phalcon\Traits\Php\SerializeTrait- wrappers forserializeandunserialize.Phalcon\Traits\Php\UrlTrait- wrappers forparse_url,rawurldecodeandrawurlencode.Phalcon\Traits\Php\YamlTrait- - wrappers foryaml_*methods. These calls can be substituted in tests. #17273 [doc]
- Added an opt-in “sticky” read/write connection mode to
Phalcon\Mvc\Model\Manager. AfterPhalcon\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 newPhalcon\Mvc\Model\Manager::registerWrite()(called internally on a successful insert/update/delete), andPhalcon\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 toPhalcon\Mvc\Model\ManagerInterface. #17256 [doc] - Added an opt-in
allowWildcardsoption toPhalcon\Filter\Validation\Validator\File\MimeType(and passed through fromPhalcon\Filter\Validation\Validator\Filevia the same key) that, whentrue, 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 tofalse, preserving the existing exact-match behavior. #17254 [doc] - Added closure binding to
Phalcon\Filter\Validation\Validator\Callback: aClosurepassed as thecallbackoption 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 aPhalcon\Filter\Validation\ValidatorInterfaceto 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\Aclcontracts -Phalcon\Contracts\Acl\Adapter\Adapter,Phalcon\Contracts\Acl\Adapter\Persistable,Phalcon\Contracts\Acl\Role,Phalcon\Contracts\Acl\Component,Phalcon\Contracts\Acl\RoleAwareandPhalcon\Contracts\Acl\ComponentAware- as the canonical homes for the ACL interfaces; the legacyPhalcon\Acl\...\*Interfacetypes remain as deprecated bridges that extend them. #17220 [doc] - Added the
Phalcon\Contracts\Queue\Inspectablecontract (getStats(Queue $queue): array), implemented byPhalcon\Queue\Adapter\Beanstalk\BeanstalkContext, exposing the Beanstalkdstats-tubefields (current-jobs-ready,current-jobs-reserved,current-jobs-delayed,current-jobs-buried,current-jobs-urgent,total-jobs, thecmd-*counters, …) for queue backlog/depth monitoring. The returned array is adapter-native; thecurrent-jobs-*keys are always present (zero for an unknown tube) and the read runs on its own short-lived connection. Backed by a newPhalcon\Queue\Adapter\Beanstalk\BeanstalkConnection::statsTube()wire command. #17209 [doc] - Added the granular
Phalcon\Auth\Exceptions\*exceptionsAccessNotRegistered,ActiveAccessRequired,DefaultGuardNotRegistered,GuardNotDefined,MissingHandlerContext,OptionRequiresArray,OptionRequiresString,SessionNamesMustDiffer,UnknownAdapterandUnknownGuard. #17220 [doc]
Fixed
- Fixed
Phalcon\Authlogin 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()throwingThe argument is not initialized or iterable()while resolving acaseexpression when the expression array is held as a PHP reference, by fetching thewhen-clauseslist througharray_values()before iterating it. #17225 [doc] - Fixed
Phalcon\Encryption\Crypt::encrypt()to properly capture exceptions thrown byopenssl_random_pseudo_bytes. #17326 [doc] - Fixed
Phalcon\Encryption\Security\JWT\Token\Token::validate()throwingPhalcon\Encryption\Security\JWT\Exceptions\InvalidAudienceTypewhen handed a freshly constructedPhalcon\Encryption\Security\JWT\Validator, which made a defaultValidatorimpossible to pass to it. #17361 [doc] - Fixed
Phalcon\Encryption\Security\JWT\Validator::validateIssuedAt()andPhalcon\Encryption\Security\JWT\Validator::validateNotBefore()rejecting a token whoseiat/nbfclaim falls on exactly the validated timestamp. #17361 [doc] - Fixed
Phalcon\Forms\Element\AbstractElement::render()to cast a non-nullelement value tostringbefore passing it to the input helper, so a numeric default set viasetDefault()(e.g.setDefault(10)orsetDefault(10.5)on aPhalcon\Forms\Element\Numeric) renders asvalue="10"instead of raising aTypeErrorfor passing anint/floatto the helper’sstring$valueparameter. #17232 [doc] - Fixed
Phalcon\Http\Response::getStatusCode()andPhalcon\Http\Response::getReasonPhrase()raising aTypeError(substr(): Argument #1 ($string) must be of type string, bool given) when noStatusheader had been set (e.g. a response built with onlysetContent()), becausePhalcon\Http\Response\Headers::get('Status')returnsfalsefor an absent header; the header value is now cast to string beforesubstr(), so both methods returnnullas documented. #17248 [doc] - Fixed
Phalcon\Image\Adapter\AbstractAdapter::resize()truncating the scaled master-mode (Enum::WIDTH,Enum::HEIGHT,Enum::PRECISE) dimension to anintbefore rounding, so a value whose fractional part was>= 0.5came out one pixel short (e.g. a1820x694source resized to height80produced width209instead of210); the scaled width/height are now rounded before the integer cast. #17225 [doc] - Fixed
Phalcon\Mvc\Model::cloneResult()andPhalcon\Mvc\Model::cloneResultMap()calling a model setter (or throwingPhalcon\Mvc\Model\Exceptions\PropertyNotAccessiblewhen no setter exists) while hydrating a declaredprivateproperty, because the property write fromModelscope fell back to__set()and itspossibleSetter()routing regardless of theorm.call_setters_on_hydrationsetting. #16454 [doc] - Fixed
Phalcon\Mvc\Model::create()andPhalcon\Mvc\Model::update()passingnullto thefieldargument ofPhalcon\Messages\Message(typedstringsince v5.14), which raised aPassing null to parameter #2 ($field) of type string is deprecatedwarning when callingcreate()on an existing record orupdate()on a non-existent one; they now pass an empty string. #17224 [doc] - Fixed
Phalcon\Mvc\Model\Query::executeUpdate()raising a PDOInvalid parameter number: mixed named and positional parameterserror for a PHQLUPDATEwhoseSETclause 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 thePhalcon\Db\RawValueis built, so it no longer collides with the positional?marker of the primary-keyWHEREclause, and the placeholder is removed from the bind parameters forwarded to the pre-updateSELECT. #16976 [doc] - Fixed a segmentation fault when rendering a Volt template that
extendsa 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 anullblocksvalue toarray_key_exists(), which read it as an array from an uninitialized pointer;blocksis 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 inPhalcon\Image\Adapter\Gd(a no-op since PHP 8.0), thefinfo_close()calls inPhalcon\Http\Request\FileandPhalcon\Filter\Validation\Validator\File\MimeTypeand theReflectionProperty::setAccessible()call inPhalcon\Support\Debug\Dump(no-ops since PHP 8.1), clamped the random pad byte inPhalcon\Encryption\Crypt\Padding\Iso10126tochr(rand() % 256)to avoid the out-of-rangechr()deprecation on PHP 8.5, and guardedPhalcon\Messages\Messages::offsetSet()against an implicitnullarray offset. #17253 [doc]
Removed
- Removed the deprecated
Serializableinterface fromPhalcon\Mvc\ModelandPhalcon\Mvc\Model\Resultset(deprecated by PHP 8.1); the__serialize()and__unserialize()magic methods remain, so model and resultset serialization is unchanged. #17253 [doc]
Upgrade
Developers can upgrade using PIE
pie install phalcon/cphalcon-5.17.0
To compile from source, follow our installation document
Chat - Q&A
Support
Social Media
Videos
<3 Phalcon Team