Skip to content

Releases: phalcon/cphalcon

v5.14.2

12 Jun 21:07
fe76d45

Choose a tag to compare

5.14.2 (2026-06-12)

Tools

  • Zephir Parser v2.0.4
  • Zephir 0.23.0 (development - 27535f802)

Changed

  • Changed Phalcon\Cli\Console::handle() to process module definitions the same way as Phalcon\Mvc\Application::handle(). The module is now resolved through the inherited getModule(), so an unregistered module throws Phalcon\Application\Exceptions\ModuleNotRegistered (as Console::getModule() already did) instead of Phalcon\Cli\Console\Exceptions\ConsoleModuleNotRegistered. Closure module definitions are now supported and are invoked with the container, matching MVC. A definition that is neither an array nor a Closure throws the new Phalcon\Cli\Console\Exceptions\InvalidModuleDefinition instead of InvalidModuleDefinitionPath. #17107
  • Changed Phalcon\Config\Adapter\Ini::parseIniString() to use Phalcon\Config\Config::DEFAULT_PATH_DELIMITER for the key nesting separator instead of a hardcoded . (no behavior change). #17134
  • Changed Phalcon\Config\Adapter\Json and Phalcon\Config\Adapter\Php to throw Phalcon\Config\Exceptions\CannotLoadConfigFile when the configuration file cannot be read, instead of failing inside the JSON decoder (Json) or with a fatal require error (Php). All file based Config adapters now share the same failure contract. #17134
  • Changed Phalcon\Config\ConfigFactory to resolve adapter-specific constructor arguments (mode for ini, callbacks for yaml) through a single internal parameter map consulted by both load() and newInstance(), instead of two hardcoded switches. load() now also resolves the yml adapter name / file extension to the yaml adapter. #17134
  • Consolidated the allowEmpty handling of Phalcon\Filter\Validation into the validator (Phalcon\Filter\Validation\AbstractValidator::isAllowEmpty()). The per-field allowEmpty map is also honored. #17124
  • Moved the resolution of an array attribute option from Phalcon\Filter\Validation\AbstractValidator::getOption() into Phalcon\Filter\Validation\Validator\Uniqueness::getOption(). #17124

Added

  • Added Phalcon\Filter\Filter::getDefaultMapper(), for mapper services used also by Phalcon\Filter\FilterFactory::getServices(). #17124
  • Added Phalcon\Filter\Validation\Validator\File\Resolution\AspectRatio, validating that an uploaded image has an exact aspect ratio. The ratio option uses the 16x9 format (per-field array form supported) and is compared with integer cross-multiplication, so the dimensions must match exactly: 1920x1080 matches 16x9, 1366x768 does not. Also available through the composite Phalcon\Filter\Validation\Validator\File via the aspectRatio and messageAspectRatio options. #17104
  • Added SessionUpdateTimestampHandlerInterface support to the Phalcon\Session adapters (Noop, Stream, Redis, Libmemcached), enabling PHP's session.lazy_write (on by default): when the session data is unchanged at close, PHP now calls updateTimestamp() instead of write(). Stream touches the session file without rewriting its data; Redis and Libmemcached delegate to write() to refresh the TTL. With session.use_strict_mode enabled, the new validateId() rejects uninitialized session ids. #17129
  • Added a stripPrefix option (default true) to the Phalcon\Storage / Phalcon\Cache adapters, controlling whether a leading prefix is stripped from incoming keys (the behavior introduced for #17089). Phalcon\Session\Adapter\Redis and Phalcon\Session\Adapter\Libmemcached disable it by default: session ids are externally generated, so an id that happens to start with the prefix text must not collide with another session. #17127
  • Added an optional Phalcon\Config\ConfigFactory constructor parameter to Phalcon\Config\Adapter\Grouped. The factory is created once and reused for every configuration fragment, so custom adapters registered on a supplied factory are now visible when loading grouped configurations. #17134
  • Added dialect-specific operators to PHQL: @@, @>, <@, &&, ||, ->, ->>, #>, #>>. Each is parsed into a binary expression and emitted only by the dialects that support it (PostgreSQL: all nine; MySQL: ->, ->>; SQLite: ||, ->, ->>); using an operator on a dialect that does not support it throws Phalcon\Db\Exceptions\UnsupportedOperator. The jsonb existence operators (?, ?|, ?&, @?) and the ~ regex family are intentionally unsupported - use their function equivalents (e.g. jsonb_exists(), regexp_like()). #14954 #14579
  • Added geometry value objects under Phalcon\Db\Geometry (Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection) and read-side hydration of spatial columns. When orm.cast_on_hydrate is enabled, spatial column values (MySQL WKB / PostGIS EWKB) are decoded into these objects on model read; otherwise the raw value is returned unchanged. #17110 #14769 #13670
  • Added opt-in session locking to Phalcon\Session\Adapter\Redis, preventing concurrent requests from racing on the same session (stale reads / lost writes). When enabled with the new lockingEnabled constructor option, read() acquires a per-session lock (SET NX EX, retried with pauses) and close() / destroy() release it with a token-guarded delete, so an adapter can only remove a lock it still owns. A read that cannot acquire the lock throws Phalcon\Session\Adapter\Exceptions\AdapterRuntimeError. Tunable via the lockExpiry (lock TTL in seconds, default 30), lockRetries (maximum attempts, default 100) and lockWaitTime (microseconds between attempts, default 50000) options. Locking is off by default. #17126
  • Added table comment support to the MySQL and PostgreSQL dialects. Comment values are single-quote escaped (the existing PostgreSQL column-comment emission is now escaped as well). SQLite has no native table comment and ignores the option. #15258
  • Added the Phalcon\Contracts\Filter\Sanitizer interface and moving array recursion in Phalcon\Filter\Filter::sanitize(). #17124

Fixed

  • Fixed Phalcon\Config\Config::cloneEmpty() so that filter(), map(), sort() and where() no longer fail on adapter instances (Ini, Json, Php, Yaml, Grouped). The override clones the current instance and replaces its data instead of invoking the adapter constructor with the parent (array $data, ...) signature. #17134
  • Fixed Phalcon\Config\Config::merge() emptying the configuration before validating its argument. Invalid merge data still throws Phalcon\Config\Exceptions\InvalidMergeData, but the current configuration now survives intact. #17134
  • Fixed Phalcon\Config\Config::setData() bypassing the Phalcon\Support\Collection runtime type guard: a type passed to the constructor is now enforced on leaf values at every nesting depth (arrays become nested Config objects, which validate their own leaves). Nested Config objects also inherit the strictNull and type flags in addition to insensitive. #17134
  • Fixed Phalcon\Http\Cookie::send() fataling when the cookie has a non-empty definition and no DI container is set. The session integration (service lookup, started check, and the _PHCOOKIE_ key convention) is now consolidated in private getStartedSession()/getSessionKey() helpers shared by delete(), restore() and send(). #17127
  • Fixed Phalcon\Mvc\Model::groupResult() declaring a Phalcon\Mvc\Model\ResultsetInterface return type while returning a scalar (int, float, string, or null) for non-grouped aggregate queries (count(), sum(), average(), maximum(), minimum()). The return type declaration has been removed (@return int|float|string|null|ResultsetInterface), so model subclasses can override groupResult() without risking a TypeError. #17114
  • Fixed Phalcon\Mvc\Model::save() and Phalcon\Mvc\Model::update() to run the record-existence check on the write connection instead of the read connection. On master-replica setups, replication lag could make the check miss a row already written to the master, causing save() to attempt an INSERT instead of an UPDATE, or update() to fail with Record cannot be updated because it does not exist. create() has used the write connection since #14256. #17105
  • Fixed Phalcon\Session\Bag calling getDI() on its Phalcon\Session\ManagerInterface constructor parameter - a method the interface does not declare - which fataled for any manager implementing only the interface. The container i...
Read more

v5.14.1

08 Jun 19:28
4e4bb37

Choose a tag to compare

5.14.1 (2026-06-08)

Tools

  • Zephir Parser v2.0.4
  • Zephir 0.23.0 (development - bae82f7bd)

Changed

  • Consolidated the Phalcon\Auth dual-container handling (new Phalcon\Container\Container vs legacy Phalcon\Di\Di) behind a single internal Phalcon\Auth\Internal\ContainerResolver. #17088 [doc]
  • Renamed the private Phalcon\Events\Manager dispatch hot-loop helper to runQueue(). #17006 [doc]
  • Reworked the Phalcon\Auth access gates into Specification-style policies. Phalcon\Contracts\Auth\Access\Access::isAllowed() now receives the current identity and the request context: isAllowed(Guard $guard, string $actionName, array $context = []), where context carries handler (controller / task / Micro component name), module (MVC module, when present), and params (dispatcher or route parameters). #17088 [doc]
  • Phalcon\Auth\Manager::access() now resolves gates through Phalcon\Auth\Access\AccessLocator from the container instead of constructing them directly. #17088 [doc]

Added

  • Added Phalcon\Auth\Access\Acl - an ACL-backed access gate that incorporates the role-based authorization of the old Firewall component (#14630) into the Auth layer. The gate checks the authenticated user's role against a Phalcon\Acl\Adapter\AdapterInterface: the ACL component is the handler context key (prefixed with module and a configurable separator when present), the ACL access is the action name, and params are passed through to callable ACL rules. Unauthenticated requests resolve to a configurable guest role (default guest); authenticated users supply their role via Phalcon\Acl\RoleAwareInterface. #17088 [doc]
  • Added Phalcon\Auth\Micro\AuthMicroListener to enforce the active Auth access gate on Phalcon\Mvc\Micro route execution (attach to the micro event space).#17088 [doc]
  • Added Phalcon\Events\Manager::dispatch(object $event, string|array|null $name = null, ?object $source = null) for object/class-based event dispatch built on Phalcon's own Phalcon\Contracts\Events\Stoppable. Listeners are routed by an explicit name (a string, or a [class, method] array) or by the event's class name and receive the event object. #17006 [doc]
  • Added beforeBind and afterBind hook methods to Phalcon\Forms\Form. When defined on a form subclass, beforeBind(array $data, ?object $entity) runs at the start of bind() (returning false cancels the bind) and afterBind(?object $entity) runs after the data has been assigned. Both also fire when bind() is reached through isValid(). #14598 [doc]
  • Added a sync option to many-to-many (hasManyToMany) relations and a chainable Phalcon\Mvc\Model::setSync() method to synchronize related records on save. When enabled, saving deletes the intermediate rows for records no longer present in the assigned array (add/update/delete), instead of only appending. #17071 [doc]
  • Added a trace() method to Phalcon\Logger\Logger together with a new TRACE log level (value 9, label trace). #17047 [doc]
  • Added a {% verbatim %}/{% endverbatim %} tag to Volt. Its body is emitted exactly as written, without being parsed by Volt, so {{ ... }}, {% ... %}, {# ... #} and constructs such as <?xml ... ?> or client-side templates (Handlebars, Mustache, Angular) pass through untouched. #17085 [doc]
  • Added support for JOIN clauses in PHQL UPDATE statements (e.g. UPDATE Invoices INNER JOIN Customers ON ... SET ... WHERE Customers.cst_id = :id:). The join is used to filter the records to update; the statement still targets a single model. #16984 [doc]

Fixed

  • Fixed PHQL parser cache to use string-keyed lookups (zend_hash_str_find/zend_hash_str_update) instead of integer keys derived from zend_inline_hash_func, eliminating hash collisions that caused different PHQL queries to return identical cached ASTs #14791
  • Fixed Phalcon\Annotations\Reader failing to parse a docblock when an annotation argument is a string literal containing a parenthesis (e.g. @SomeAnnotation(key='value(')). The docblock pre-scan that locates each @Annotation(...) span counted every (/), including those inside quoted values, so an unbalanced parenthesis in a string consumed the rest of the comment and produced a "Scanning error". #16084
  • Fixed Phalcon\Di\Injectable::__get() to no longer cache resolved services as dynamic object properties. Services accessed via magic properties (e.g. $this->request) are now re-resolved through the container on each access, so replacing or updating a service in the container is reflected in controllers, views, and other injectable classes. Properties already declared on the class continue to be populated. #17052
  • Fixed Phalcon\Encryption\Crypt::decrypt() to verify the HMAC tag with the constant-time hash_equals() instead of the identity operator, removing an observable timing discrepancy in the tag comparison (CWE-208, CWE-347) . The tag is now also verified before the decrypted text is unpadded, and truncated tags are rejected by the unequal-length path of hash_equals(). #17090 [doc]
  • Fixed Phalcon\Mvc\Model\Query\Builder::orderBy() when the array syntax is used with complex PHQL expressions. Previously any array item containing a space was split as a simple column direction pair, corrupting expressions such as CASE WHEN inv_status_flag = 1 THEN 0 ELSE 1 END ASC. The builder now only treats a trailing ASC/DESC as the direction (autoescaping a simple column) and preserves complex expressions verbatim. #17077
  • Fixed Phalcon\Mvc\Model\Query (PHQL) parsing of identifiers whose name begins with the NOT keyword. Columns, tables, and aliases such as notice_id were truncated to ice_id (the leading not was dropped), causing the database to report the column as unknown - most visibly in Phalcon\Mvc\Model\Query\Builder join conditions built via createBuilder(). The scanner's re2c backtracking marker shared the token-start pointer, so the NOT BETWEEN rule advanced it past not; escaped identifiers containing internal escapes (e.g. [col\[0\]]) were corrupted by the same root cause. #16831 #17087
  • Fixed the compilation failure ('name_zv' undeclared) in Phalcon\Container\Container::callableGet() and callableNew(). Both closures captured the typed string name parameter directly via use (name). #17078

Removed

  • Removed the unfinished {% raw %}/{% endraw %} Volt tag. It never produced output (compilation threw UnknownVoltStatement) and its body was parsed rather than emitted literally. Use {% verbatim %} instead. #17085

v5.14.0

04 Jun 15:07
33617b0

Choose a tag to compare

5.14.0 (2026-06-04)

Tools

  • Zephir Parser v2.0.2
  • Zephir 0.22.0 (development - 9d2def774)

Changed

  • Alignment with v6; docblocks; sorting; return types; minor fixes (image watermark opacity calc, serializer/helpers, readonly-becoming-mutable, ACL local access). #17055
  • Changed return types to -> <static> or -> <self> in various components. The change is a covariant narrowing on implementation methods and does not touch any interface contracts, so userland classes that implement Phalcon interfaces and return the interface type continue to work unchanged. #17035
  • Internal performance work across Autoload, Dispatcher, Annotations, Db, Mvc\Model, Mvc\Model\Query, Tag, Assets, Acl\Adapter\Memory, Http\Request, Encryption\Crypt. Behavior preserved. #17049
  • Moved CI tools/scripts in resources/ removed unused ones #17066
  • Moved docker in resources/ #17066
  • Refactored docker images (more flexible less cruft) #17066
  • Reorganization of quality tool config files (in resources/) #17066
  • Phalcon\Autoload\Loader getters (getDirectories, getExtensions, getFiles) return arrays keyed by the value string instead of by a SHA256 hash of it; iteration order and contents are unchanged. #17049 [doc]
  • Phalcon\Mvc\Router::handle() internal optimizations: O(1) hash lookup for literal-URI routes; per-HTTP-method buckets; hot-loop reads; PCRE patterns chunked; per-route metadata cache deduplicated by route id. #17012 [doc]
  • Phalcon\Mvc\Router\Route::getCompiledHostName() now uses cache for hostname/converters. #17012 [doc]

Added

  • Added a new dependency-injection container under Phalcon\Container, with its contracts under Phalcon\Contracts\Container. It adds:
    • Phalcon\Container\Container / Phalcon\Container\ContainerFactory - the container and its factory, configured through Phalcon\Contracts\Container\Service\Provider providers (Phalcon\Container\Provider\Web, Phalcon\Container\Provider\Cli).
    • Phalcon\Container\Definition\ServiceDefinition - fluent service definitions with autowiring, factories, extenders, tags, aliases, and configurable service lifetimes (Phalcon\Container\Definition\ServiceLifetime).
    • Phalcon\Container\Resolver\Resolver - reflection-based constructor / method / parameter autowiring, plus the Phalcon\Container\Resolver\Lazy\* family for lazy resolution (Get, GetCall, NewInstance, Call, Env, CsEnv, ArrayValues, etc.).
    • Phalcon\Container\Exceptions\* - granular, per-cause exceptions (ServiceNotFound, CircularAliasFound, FrozenDefinition, CannotResolveParameter, NoProcessorFound, etc.). #16897 [doc]
  • Added a new authentication and authorization layer under Phalcon\Auth, with its contracts under Phalcon\Contracts\Auth. Built on top of Phalcon\Container, it adds:
    • Phalcon\Auth\Manager / Phalcon\Auth\ManagerFactory - the central manager that wires guards and access gates together, and its factory.
    • Phalcon\Auth\AuthUser - a lightweight user value object returned by array-backed adapters when no application model class is configured.
    • Guards under Phalcon\Auth\Guard - Session and Token (with AbstractGuard and UserRemember), resolved via Phalcon\Auth\Guard\GuardLocator and configured through Phalcon\Auth\Guard\Config\* (AbstractGuardConfig, SessionGuardConfig, TokenGuardConfig).
    • Adapters under Phalcon\Auth\Adapter - Memory, Model, and Stream user providers (with AbstractAdapter and AbstractArrayAdapter), resolved via Phalcon\Auth\Adapter\AdapterLocator and configured through Phalcon\Auth\Adapter\Config\* (AbstractAdapterConfig, MemoryAdapterConfig, ModelAdapterConfig, StreamAdapterConfig).
    • Access gates under Phalcon\Auth\Access - Auth and Guest (with AbstractAccess), resolved via Phalcon\Auth\Access\AccessLocator.
    • Dispatcher listeners Phalcon\Auth\Mvc\AuthDispatcherListener and Phalcon\Auth\Cli\AuthDispatcherListener (with Phalcon\Auth\AbstractAuthDispatcherListener) to guard MVC and CLI dispatch.
    • Phalcon\Auth\Exception plus granular Phalcon\Auth\Exceptions\* (AccessDenied, ConfigRequiresNonEmptyValue, DataMustContainIdKey, DoesNotImplement, FileCannotRead, FileDoesNotContainJson, FileDoesNotExist, FileNotValidJson).
    • Contracts under Phalcon\Contracts\Auth - Manager, AuthUser, AuthRemember, RememberToken, Access\Access, Adapter\Adapter, Adapter\AdapterConfig, Adapter\RememberAdapter, Guard\Guard, Guard\GuardConfig, Guard\GuardStateful, Guard\BasicAuth.
    • Phalcon\Support\AbstractLocator - the shared service-locator base extended by the guard, adapter, and access locators. #16273 [doc]
  • Added granular exception classes across the framework. Every namespace that previously surfaced failures through a single umbrella Phalcon\<Namespace>\Exception (or its sub-namespace counterpart) now ships per-cause classes under a sibling Exceptions/ folder. Each new class extends the existing per-namespace parent so catch (Phalcon\<Namespace>\Exception $e) continues to work unchanged. New classes:
    • Phalcon\Acl\Exceptions
      • AccessRuleNotFound
      • CircularInheritanceError
      • ElementNotFound
      • ForbiddenWildcard
      • InvalidAccessList
      • InvalidComponentImplementation
      • InvalidRoleImplementation
      • InvalidRoleType
      • MissingFunctionParameters
      • ParameterTypeMismatch
      • RoleNotFoundException
    • Phalcon\Annotations\Exceptions
      • AnnotationNotFound
      • AnnotationsDirectoryNotWritable
      • CannotReadAnnotationData
      • UnknownAnnotationExpression
    • Phalcon\Application\Exceptions
      • ModuleNotRegistered
    • Phalcon\Assets\Exceptions
      • AssetSourceTargetCollision
      • CannotReadAsset
      • CollectionNotFound
      • InvalidAssetSourcePath
      • InvalidAssetTargetPath
      • InvalidFilter
      • InvalidTargetPath
      • TargetPathIsDirectory
    • Phalcon\Autoload\Exceptions
      • LoaderDirectoriesNotArray
      • LoaderMethodNotCallable
    • Phalcon\Cache\Exception
      • CacheKeysNotIterable
      • InvalidCacheKey
    • Phalcon\Cli\Console\Exceptions
      • ConsoleModuleNotRegistered
      • ContainerRequired
      • InvalidModuleDefinitionPath
      • ModuleDefinitionPathNotFound
    • Phalcon\Cli\Router\Exceptions
      • BeforeMatchNotCallable
      • InvalidRoutePaths
      • RouterArgumentsInvalidType
    • Phalcon\Config\Exceptions
      • CannotLoadConfigFile
      • ConfigNotArrayOrObject
      • GroupedAdapterRequiresArray
      • InvalidMergeData
      • MissingConfigOption
      • MissingFileExtension
      • MissingYamlExtension
    • Phalcon\DataMapper\Pdo\Exception
      • DriverNotSupported
      • UnknownDriverMethod
      • UnknownQueryMethod
    • Phalcon\Db\Exceptions
      • CannotInsertWithoutData
      • CannotPrepareStatement
      • CheckExpressionRequired
      • ColumnTypeRejectsAutoIncrement
      • ColumnTypeRejectsScale
      • ColumnTypeRequired
      • ConflictTargetColumnRequired
      • ConflictUpdateColumnRequired
      • ForeignKeyColumnsRequired
      • GeneratedAutoIncrementConflict
      • GeneratedDefaultConflict
      • IncompleteBindTypes
      • InvalidBindParameter
      • InvalidCheckExpression
      • InvalidGenerationExpression
      • InvalidGroupByExpression
      • InvalidIndexColumns
      • InvalidIndexDirections
      • InvalidIndexWhere
      • InvalidListExpression
      • InvalidOrderByExpression
      • InvalidSqlExpression
      • InvalidSqlExpressionType
      • InvalidUnaryExpression
      • InvalidWhereConditions
      • MatchedParameterNotFound
      • MaterializedViewsNotSupported
      • MissingDefinitionKey
      • MissingForeignKeyChecks
      • MissingSqliteDatabase
      • MysqlOnConflictNotSupported
      • NestedTransactionChangeBlocked
      • NoActiveTransaction
      • ReferencedColumnCountMismatch
      • ReferencedColumnsRequired
      • ReferencedTableRequired
      • ReturningNotSupported
      • ReturningRequiresColumn
      • SavepointsNotSupported
      • SqliteAlterCheckNotSupported
      • SqliteAlterColumnNotSupported
      • SqliteAlterForeignKeyNotSupported
      • SqliteAlterPrimaryKeyNotSupported
      • SqliteDropCheckNotSupported
      • SqliteDropForeignKeyNotSupported
      • SqliteDropPrimaryKeyNotSupported
      • TableMustHaveColumn
      • UnrecognizedDataType
      • UpdateFieldCountMismatch
    • Phalcon\Di\Exceptions
      • AliasAlreadyInUse
      • AliasNameMustBeString
      • ArgumentTypeRequired
      • CallArgumentsMustBeArray
      • `CircularAliasRefere...
Read more

v5.13.0

18 May 15:20
70ec768

Choose a tag to compare

5.13.0 (2026-05-18)

Changed

  • Changed Phalcon\Contracts\Support\Collection to declare the expanded method surface (column, each, filter, first, getType, isEmpty, keys, last, map, reduce, replace, sort, values, where) so the contract matches the implementation #17000 [doc]
  • Changed Phalcon\Events\Event to be declared final. The class is a value object holding type, source, data, cancelable, and stopped; no subclasses exist in the cphalcon tree and any future typed-event work would add new sibling classes implementing EventInterface rather than extending Event. Marking it final lets the C extension fold the per-fire getters (getType, getSource, getData, isCancelable, isStopped, isPropagationStopped) into direct dispatch. BC note: any userland class MyEvent extends Event now fails #17006 [doc]
  • Changed Phalcon\Events\Manager::attach() to classify the handler kind once at attach time so the dispatch loop can route via a single branch instead of running instanceof Closure / is_callable / typeof per fire per listener. Four kinds are recognized: 0 = Closure (direct invocation via Zephir's {handler}(args) syntax, no arg-array allocation), 1 = [obj, method] array callable (direct dynamic dispatch handler[0]->{handler[1]}(args), no call_user_func_array overhead), 2 = plain object with method named after the event - the classic Phalcon listener pattern (class name is captured at attach time and stored on the tuple to skip get_class() per fire), 3 = generic callable (string function name, invokable object, [class, staticMethod]) routed through call_user_func_array. The subscriber array-form attach paths ([methodName, priority] and [[methodA, priorityA], [methodB, priorityB]]) now route through insertHandlerEntry directly with kind=1, bypassing the classification cascade since the resulting handler shape is already known. methodExistsCache access in the dispatch loop is tightened to a single isset fast path #17006 [doc]
  • Changed Phalcon\Events\Manager::detach() to drop the eventType key entirely when its queue empties (removing the last listener), so hasListeners() and fire()'s short-circuit tell the truth. Previously an emptied queue left the key in place with an empty array value, causing isset($this->events[$eventType]) to return true with no actual listeners to dispatch to. The matching DetachTest expectations are updated to reflect the new contract #17006 [doc]
  • Changed Phalcon\Events\Manager::detachAll(null) to reset events to [] instead of null. The previous null reset broke isset($this->events[$key]) semantics inside attach() and fire() until the next assignment refilled the property; the empty-array form keeps all access paths consistent #17006 [doc]
  • Changed Phalcon\Events\Manager::fire() and Phalcon\Events\Manager::fireAll() to wrap dispatch in try { ... } catch \Throwable, ex { cleanup; throw ex; }. A throwing listener restores stashed responses (if nested) and decrements fireDepth back to its pre-call value before the exception propagates, so manager state stays clean for the next fire - important for long-lived managers (workers, daemons) where a single dirty teardown would poison every subsequent fire #17006 [doc]
  • Changed Phalcon\Events\Manager::fire() to parse the eventType only once per unique string and cache the result ([typePrefix, eventName]) in an internal eventNameCache keyed by the original string. Repeated fires of the same event name (db:beforeQuery × N per request, model lifecycle events on hot save paths) collapse to a single hash lookup after warm-up. The cache never needs invalidation because the parse is deterministic for a given input string. fire() also short-circuits before allocating the Event instance when no listeners match either the type queue or the fully-qualified queue - in production most fires have zero matching listeners (model lifecycle events with no user-attached behavior, DB events without a tracer), so the avoided allocation is significant in hot paths #17006 [doc]
  • Changed Phalcon\Events\Manager::fire(), Phalcon\Events\Manager::attach(), and Phalcon\Events\Manager::fireQueue() to be declared final. The class itself stays open so genuine subclasses that only add new methods continue to work, but the dispatch hot path is locked to enable C-level direct dispatch on the three methods that run per-event. The remaining public surface (addSubscriber, removeSubscriber, detach, detachAll, getListeners, getResponses, getSubscribers, hasListeners, isCollecting, isValidHandler, collectResponses, enablePriorities, arePrioritiesEnabled, isStrict, setStrict, fireAll, clearSubscribers) is left non-final so decorator-style subclasses that wrap these less-hot methods can still override them. BC note: subclasses that override fire, attach, or fireQueue now fail #17006 [doc]
  • Changed Phalcon\Events\Manager::fireQueue() to be a thin BC-preserving wrapper around a new private Phalcon\Events\Manager::dispatch() helper. The public signature fireQueue(array $queue, EventInterface $event) is unchanged; the framework's own fire() path bypasses fireQueue and calls dispatch() directly with hoisted arguments (eventName, source, data, cancelable, collect) so the second dispatch leg of a two-queue fire() (type queue + fully-qualified queue) does not re-extract identical values from the event. The dispatch path also applies a single-handler fast path: when the queue has exactly one listener (very common in unit tests and lightly-instrumented production), the foreach plus per-iteration cancelable/isStopped check is skipped and dispatch goes straight through the type-branch #17006 [doc]
  • Changed Phalcon\Events\Manager dispatch return-value contract to last non-null wins. Previously every listener return overwrote the running status, so a chain of ("value", null) ended with fire() returning null and silently losing the earlier real value. The new contract only updates status when the listener return is non-null - the last meaningful return survives. The stop() determinism rule overrides last-non-null: when a listener calls $event->stop() (and cancelable=true), that listener's return value is what fire() returns - even if null - because the caller asked for the stopping listener's verdict explicitly. Returning false from a listener does not short-circuit the queue; callers wanting to stop downstream listeners must call $event->stop(). Consumers like Phalcon\Mvc\Dispatcher that interpret a false return from fire() as a cancel signal are unaffected because that check happens in their own dispatch logic, not in the events manager #17006 [doc]
  • Changed Phalcon\Events\Manager listener storage from SplPriorityQueue to a sorted array of [handler, type, priority] tuples (with an additional className element on type=2 tuples). The SplPriorityQueue::EXTR_BOTH clone-per-fire and O(n) setExtractFlags() rebuild on detach are eliminated; the "empty heap" warnings produced by SplPriorityQueue on never-fired event types disappear as a side effect. Insert order under the same priority is preserved (FIFO). When enablePriorities is off (the default), insertHandlerEntry short-circuits to a plain append - the sorted-insert loop only runs when priorities are explicitly enabled. When it does run, the insert uses array_splice instead of a per-element rebuild #17006 [doc]
  • Changed Phalcon\Html\Escaper into a façade over five per-context escapers (Phalcon\Html\Escaper\HtmlEscaper, AttributeEscaper, CssEscaper, JsEscaper, UrlEscaper); each is reachable via getXxxEscaper()/setXxxEscaper() so individual contexts can be swapped without subclassing the façade. The legacy setEncoding(), setFlags(), and setDoubleEncode() setters fan out to all sub-escapers so existing code keeps working #16971 [doc]
  • Changed Phalcon\Html\Helper\AbstractSeries::__toString() to ksort() its store before rendering so entries are emitted in position order rather than registration order. #16971 [doc]
  • Changed Phalcon\Html\Helper\Input\Checkbox and Phalcon\Html\Helper\Input\Radio to extend a new shared Phalcon\Html\Helper\Input\AbstractChecked; Radio no longer extends Checkbox. Two paths now render checked="checked": the unconditional opt-ins ["checked" => "checked"] (case-insensitive) and ["checked" => true], and a value-match path comparing the supplied checked attribute against the input's value (== by default for mixed i...
Read more

v5.12.1

30 Apr 21:34
bfe646a

Choose a tag to compare

5.12.1 (2026-04-30)

Added

  • Added Phalcon\Db\Column::TYPE_UUID constant (value 29) and added support for PostgreSQL native uuid column type in Phalcon\Db\Adapter\Pdo\Postgresql and Phalcon\Db\Dialect\Postgresql #16840
  • Added support for Phalcon\Mvc\Url static base URI in Phalcon\Assets\Manager; when a DI container is set and a url service is available, local asset paths are now resolved via getStatic() instead of a bare / prefix #16570

Fixed

  • Fixed Phalcon\Mvc\Model\MetaDataInterface::readMetaDataIndex() and Phalcon\Mvc\Model\MetaData::readMetaDataIndex() declaring return type as array|null when the method can also return a string (e.g. for MODELS_IDENTITY_COLUMN), causing a PHP fatal error on PHP 8+ #16613
  • Fixed Phalcon\Mvc\View\Engine\Volt\Compiler::statementList() returning null instead of string when processing templates that consist entirely of block-mode statements, causing a PHP fatal error on PHP 8+ #16613
  • Fixed Phalcon\Forms\Element\Select::render() multiselect regression introduced in v5.12.0 (#16894) by reverting to Phalcon\Tag\Select::selectField(); the new Html\Helper\Input\Select only supports a single selected value and does not handle array values required for multiselect #16946
  • Fixed Phalcon\Html\Helper\Input\AbstractInput::setValue() ignoring empty string "" as a valid value, causing Checkbox and Radio inputs with value="" to never render checked="checked" even when the checked attribute matched #16648
  • Fixed Phalcon\Http\Response\Cookies::get() throwing an opaque fatal error when no DI container has been set; it now throws Phalcon\Http\Cookie\Exception with a descriptive message before accessing the container #16650
  • Fixed Phalcon\Mvc\Model\MetaData::writeMetaDataIndex() prematurely initializing a child model's metadata with the parent's source table when skipAttributes() (or skipAttributesOnCreate()/skipAttributesOnUpdate()) is called inside a parent model's initialize() and the child calls parent::initialize() before setting its own source, corrupting the child's attribute list and breaking relationship resolution #16544
  • Fixed Phalcon\Storage\Serializer\Json::serialize() rejecting plain objects (e.g. stdClass) that do not implement JsonSerializable; json_encode() handles such objects natively and the guard was unnecessary #16630
  • Fixed Phalcon\Mvc\Model\Manager retaining a model instance in lastInitialized after initialization and Phalcon\Mvc\Model not clearing the reusable-records cache after save(), causing memory to grow unboundedly in long-running processes #16566
  • Fixed Phalcon\Paginator\Adapter\QueryBuilder::paginate() returning wrong total item count when the query uses DISTINCT columns; the count now uses COUNT(DISTINCT ...) for a single column and a subquery for multiple columns #16581
  • Fixed Phalcon\Mvc\Model\Query\Builder::autoescape() incorrectly wrapping function expressions (e.g. DATE_PART(...)) in brackets when used in groupBy(), causing a "Column does not belong to any of the selected models" exception #16599
  • Fixed Phalcon\Mvc\Model - saving a model with multiple fields relations threw "Not implemented" #16029

v5.12.0

29 Apr 21:21
83ee909

Choose a tag to compare

5.12.0 (2026-04-29)

Changed

  • Changed Phalcon\Assets\Manager filter type check from is_object() to typeof and updated the error message to "The filter is not valid" #16889
  • Changed Phalcon\Cache\AbstractCache::doDeleteMultiple() to delegate to the storage adapter's deleteMultiple() instead of looping over individual delete() calls #16859
  • Changed Phalcon\Di\Exception message for missing services from "was not found in the dependency injection container" to "is not registered in the container" #16889
  • Changed Phalcon\Di\Service\Builder error messages for service parameters to use double quotes instead of single quotes #16889
  • Changed Phalcon\Forms\Element\AbstractElement::getLocalTagFactory() to throw Phalcon\Forms\Exception instead of silently creating a new TagFactory when neither setTagFactory() nor a parent Form provides one #16894
  • Changed Phalcon\Forms\Element\Select::render() to use TagFactory-based Html\Helper\Input\Select instead of the deprecated Phalcon\Tag\Select #16894
  • Changed Phalcon\Html\TagFactory to accept an optional ResponseInterface in the constructor (useful for preload) #16892
  • Changed Phalcon\Mvc\Controller and Phalcon\Mvc\View\Engine\AbstractEngine to be events aware #16890
  • Changed Phalcon\Mvc\View\Engine\Volt\Compiler::setOptions to return $this now #16891
  • Changed calls to globals_get and globals_set in the code with Phalcon\Support\Settings::get()/set() #16884
  • Changed exception messages across multiple components to use "does not" instead of "doesn't" for consistency #16889

Added

  • Added Phalcon\Encryption\Security\Uuid factory and versioned adapters (Version1Version7) with a UuidInterface carrying standard RFC 4122 namespace constants; each version is a singleton cached by the factory, invoked via v1()v7() #16326
  • Added Phalcon\Html\Helper\FriendlyTitle - available via TagFactory as friendlyTitle [#16892(https://github.com//issues/16892)
  • Added Phalcon\Html\Helper\Input\Select::fromData() to populate select options from a SelectDataInterface provider, with optgroup support #16894
  • Added Phalcon\Html\Helper\Input\Select\SelectDataInterface, Phalcon\Html\Helper\Input\Select\ArrayData, and Phalcon\Html\Helper\Input\Select\ResultsetData as data providers for the Select helper #16894
  • Added Phalcon\Html\Helper\Preload - available via TagFactory as preload; TagFactory now accepts an optional ResponseInterface as its third constructor parameter [#16892(https://github.com//issues/16892)
  • Added Phalcon\Mvc\Model\Resultset::refresh() to re-execute the underlying query and update the resultset with fresh data from the database #16409
  • Added deleteMultiple() to Phalcon\Storage\Adapter\* to delete multiple keys in a single operation using native batch capabilities per adapter #16859
  • Added key validation per entry in Phalcon\Cache\AbstractCache::doDeleteMultiple() throwing InvalidArgumentException for keys containing invalid characters #16859
  • Added named static factory methods Phalcon\Forms\Exception::tagFactoryNotFound() and Phalcon\Forms\Exception::usingParameterRequired() #16894

Fixed

  • Fixed Phalcon\Db\Dialect\Postgresql::modifyColumn() to generate correct SQL when changing a boolean column's default value: replaced empty check with hasDefault() to avoid treating false as "no default", removed the boolean-only branch that omitted the ALTER TABLE prefix, and fixed castDefault() to return PostgreSQL literals true/false instead of raw PHP booleans #15829
  • Fixed Phalcon\Db\Result\PdoResult::$rowCount to use null as the uninitialised sentinel instead of false, preventing a count of 0 rows being confused with "not yet counted" #16409
  • Fixed Phalcon\Dispatcher\AbstractDispatcher::dispatch() to refresh the local eventsManager and hasEventsManager variables after initialize() returns, so that an events manager attached to the dispatcher inside initialize() is honoured for all subsequent dispatch events (afterInitialize, afterExecuteRoute, afterDispatch, afterDispatchLoop, etc.) #16440
  • Fixed Phalcon\Filter\Validation::bind() to skip the dependency injection container lookup when data is empty, preventing unnecessary Di\Exception errors #16889
  • Fixed Phalcon\Filter\Validation\AbstractValidator::allowEmpty() to support a value-list array (e.g. [null, '']) in addition to the per-field map syntax, using strict === comparison so that '0' is never silently treated as empty #15491
  • Fixed Phalcon\Filter\Validation\AbstractValidator::messageFactory() to pass the joined field string to Phalcon\Messages\Message instead of the raw array when multiple fields are provided #16889
  • Fixed Phalcon\Filter\Validation\Validator\Alpha::validate() to return false when allowEmpty is explicitly set to false and the submitted value is null or an empty string #16200
  • Fixed Phalcon\Forms\Form::isValid() to apply field filters even when no validators are specified (again) #16830
  • Fixed Phalcon\Html\Escaper::css() and Phalcon\Html\Escaper::js() to return an empty string instead of false when the input is empty or contains only a null codepoint #16889
  • Fixed Phalcon\Html\Helper\AbstractHelper::renderAttributes() to emit boolean HTML5 attributes (e.g. async, defer) as standalone attribute names instead of async="1" when the attribute value is true #16304
  • Fixed Phalcon\Html\Helper\Breadcrumbs to support subdirectory installs: added getPrefix()/setPrefix() for a manual string prefix, and an optional UrlInterface constructor parameter that resolves links through url->get() (including base URI prepending and double-slash normalisation); TagFactory accepts an optional fourth UrlInterface argument and passes it to Breadcrumbs automatically #14957
  • Fixed Phalcon\Http\Response::setStatusCode() exception message from "Non-standard statuscode given without a message" to "Non-standard status-code given without a message" #16889
  • Fixed Phalcon\Image\Adapter\AbstractAdapter::crop() to correctly handle offsetX = 0 and offsetY = 0 by changing the parameter types from int to var; the previous int typing caused Zephir to compile the null check as 0 == offset in C, making explicit zero offsets indistinguishable from omitted (center) offsets #16156
  • Fixed Phalcon\Image\Adapter\Gd::processResize() to preserve PNG alpha channel transparency by replacing imagescale() with imagecreatetruecolor() + imagealphablending(false) + imagesavealpha(true) + imagecopyresampled() #16316
  • Fixed Phalcon\Image\Adapter\Imagick::processPixelate() to explicitly cast division result to int to prevent implicit float-to-int deprecation #16889
  • Fixed Phalcon\Mvc\Model::__get() to return the already-loaded related record instead of re-fetching from the database, preventing modifications to relation properties from being discarded #15554
  • Fixed Phalcon\Mvc\Model::__unserialize() and Phalcon\Mvc\Model::unserialize() to call onConstruct() after deserialization, so typed properties initialized in onConstruct are correctly set when a model is restored from cache #15906
  • Fixed Phalcon\Mvc\Model::__unserialize() and Phalcon\Mvc\Model::unserialize() to restore snapshot as the current attributes (instead of null) when a model is deserialized with no pending changes, preventing getChangedFields() from throwing after cache retrieval #15837
  • Fixed Phalcon\Mvc\Model::cloneResultMap() to call model setter methods (e.g. setName()) during ORM hydration when orm.disable_assign_setters is false, making hydration behaviour consistent with assign(); setters in localMethods (Phalcon internals) are excluded #14810
  • Fixed Phalcon\Mvc\Model::collectRelatedToSave() to skip unmodified hasOne/hasMany related records that have snapshot data, preventing spurious INSERT/UPDATE statem...
Read more

v5.11.1

04 Apr 19:19
a05f6aa

Choose a tag to compare

5.11.1 (2026-04-04)

Changed

Added

  • Added Phalcon\Storage\Adapter\RedisCluster adapter to support Redis Cluster connections #16867
  • Added Phalcon\Support\Settings to be used for ini settings throughout the framework #16873

Fixed

  • Fixed Phalcon\Encryption\Security::computeHmac() to catch \ValueError thrown by PHP 8.1+ when an unknown hashing algorithm is passed #16893

Removed

v5.11.0

03 Apr 23:45
2a6a764

Choose a tag to compare

5.12.0 (2026-04-03)

Changed

  • Changed Phalcon\Filter\Sanitize\IP to optimize the sanatization of IP address #16838
  • Changed Phalcon\Encryption\Security\JWT\Builder::setPassphrase() to require digits and special characters #16847
  • Changed Phalcon\Encryption\Security\JWT\Builder::getAudience() to return an empty array if not set #16846
  • Changed Phalcon\Encryption\Security\Random::base() to use 16 bits by default #16845
  • Changed Phalcon\Logger\Logger to use lowercase when reporting log levels (previously uppercase) #16852
  • Changed Phalcon\Logger\Adapter\Stream to use a more efficient way to write messages in the logger instead of opening and closing the stream per message #16852
  • Changed Phalcon\Logger\Adapter\Syslog to use the Enum instead of Logger constants #16852
  • Changed the whole testing suite to run on phpunit only #16860 #16861 #16862

Added

  • Added PIE (PHP Installer for Extensions) support #16832
  • Added the ability to specify aliases for Phalcon\Di\Di services.#13042
  • Added Phalcon\Encryption\Security\JWT\Validator::validateClaim() to validate custom claims #16843

Fixed

  • Fixed Phalcon\Forms\Form::isValid() to apply field filters even when no validators are specified #16936
  • Fixed Phalcon\Http\Request method getClientAddress() when using trustForwardedHeader #16836
  • Fixed Phalcon\Acl\Adapter\Memory::isAllowed() and Phalcon\Mvc\Model\Binder to handle PHP 8.1+ union and intersection types by checking for ReflectionNamedType before calling getName() #16261
  • Fixed memory leak in PHQL parser (phql_internal_parse_phql()) during repeated query execution. #16854
  • Fixed a deadlock issue when running the db suite #16862

Removed

v5.10.0

24 Dec 19:33
011b92b

Choose a tag to compare

5.10.0 (2025-12-25)

Changed

  • Changed bind() and validate() method in Phalcon\Filter\Validation and Phalcon\Filter\Validation\ValidationInterface to accept $whitelist array of only allowed fields to be mutated when using entity #16800
  • Changed Phalcon\Storage\Adapters\Libmemcached::getAdapter() to use 50ms for \Memcached::OPT_CONNECT_TIMEOUT #16818
  • Changed Phalcon\Html\Helper\Input\* to honor Docbloc directives #16778

Added

  • Added fails() method helper to Phalcon\Filter\Validation useful for standalone validation #16798

Fixed

  • Fixed Phalcon\Config\Adapter\Yaml constructor to handle null return values from yaml_parse_file(), ensuring empty configuration files are treated as empty arrays instead of throwing errors.
  • Fixed Phalcon\Http\Request method getClientAddress(true) to return correct IP address from trusted forwarded proxy. #16777
  • Fixed Phalcon\Http\Request method getPost() to correctly return json data as well and unified both getPut() and getPatch() to go through the same parsing method. #16792
  • Fixed Phalcon\Filter\Validation method bind() and validate() to correctly bind data when using entity as well as skip binding of fields not included in $whitelist #16800
  • Fixed Phalcon\Http\Request method getPostData() when Content-Type header is not set #16804
  • Fixed Phalcon\Events\ManagerInterface adding priority property #16817
  • Fixed Phalcon\Storage\Adapters\Libmemcached::getAdapter() to correctly merge adapter options #16818
  • Fixed Phalcon\Encryption\Crypt method checkCipherHashIsAvailable(string $cipher, string $type) to correctly check the cipher or hash type #16822
  • Fixed Phalcon\Mvc\Model docblocks #16825

v5.9.3

22 Apr 18:56
a58902f

Choose a tag to compare

5.9.3 (2025-04-19)

Changed

  • Added Multi-Stage Dockerfile and Github action for release Docker images to ghcr.io and Docker Hub. #16752

Added

  • Added Phalcon\Mvc\Router::setUriSource() to allow URI processing from $_GET['url'] or $_SERVER['REQUEST_URI'] as it was in v3 #16741

Fixed

  • Fixed Phalcon\Mvc\Router to correctly handle numeric URI parts as it was in v3 #16741
  • Fixed Phalcon\Mvc\Model\Binder to use ReflectionParameter::getType() instead of deprecated method, PHP 8.0 or higher issue. #16742
  • Fixed Phalcon\Mvc\Model\Query to check if cache entry exists. #16747
  • Fixed Phalcon\Mvc\Router to correctly match route when using query string URIs. #16749
  • Fixed Phalcon\Mvc\Model::cloneResultset to properly parse fields that do not accept null values #16736

Removed