Phalcon\Talon\Database\Schema\AbstractSchema and run talon schema. Configured with schema_source, schema_namespace, schema_output, schema_pre and schema_post. #30talon schema writes a directory per dialect: _preSchema.sql, one .sql per table, manifest.json, _postSchema.sql. #30Connection::loadSchema() accepts a dialect directory as well as a flat .sql file. #30Connection added addTable() and tableExists(); DatabaseTrait added addTable(). #30SchemaWriter throws SchemaTableDuplicate when two definitions declare the same table. #30resources/schema/{mysql,pgsql,sqlite}.sql are now generated directories, and dump_file in the four resources/phpunit.*.xml configs points at them. #30composer test-coverage and test-coverage-html now run the unit, mariadb, mysql, and pgsql suites and merge the results, so coverage reflects every driver instead of the sqlite path alone. #26composer test-coverage and test-coverage-html now require live MariaDB, MySQL, and PostgreSQL servers. composer test is unchanged and still runs without them. #26driver=mariadb and configure DATA_MARIADB_HOST, DATA_MARIADB_PORT, DATA_MARIADB_NAME, DATA_MARIADB_USER, DATA_MARIADB_PASS, and DATA_MARIADB_CHARSET. #26getDatabaseDsn('mariadb') returns a mysql:-prefixed DSN, since MariaDB connects through pdo_mysql. No new PHP extension is required. #26mariadb test suite, run with vendor/bin/talon run mariadb. Backed by resources/phpunit.mariadb.xml and resources/schema/mariadb.sql. #26mariadb:11.4 service in docker-compose.yml, and the matching service in both CI jobs. #26phpunit/phpcov (^9) as a dev dependency, used to merge the per-suite coverage into tests/_output/coverage.xml. #26Phalcon\Talon\Database\Dialect, an enum resolving a PDO connection to Mysql, Pgsql, or Sqlite, with per-dialect identifier quoting. #27DatabaseTrait::getDialect(), returning the connection's Dialect. Use it to choose SQL syntax; use getDriver() to tell MariaDB from MySQL. #27Connection::select() now matches a NULL criterion with IS NULL instead of col = :col, which matched nothing. assertNotInDatabase('t', ['x' => null]) previously passed regardless of the data. #27Connection::select() now quotes table and column identifiers per dialect, so reserved words such as order and key work. #27DATA_POSTGRES_SCHEMA is now applied to the connection as SET search_path. It was previously read into the options and ignored. #27resources/schema/mariadb.sql, a byte-identical copy of mysql.sql. DatabaseIntegrationTest now loads the schema named by each suite's dump_file rather than deriving the path from the driver name. #27Phalcon\Talon\Traits\RestTrait (the full verb set, file uploads, request headers that persist across requests, amBearerAuthenticated()/amHttpAuthenticated(), redirect control, and response grabbers), Phalcon\Talon\Traits\RestAssertionsTrait (status, range, body, header, and JSON assertions), and Phalcon\Talon\PHPUnit\AbstractRestTestCase which composes both. A raw string body is sent as application/json unless a content type is set, and $files takes either a plain path or the $_FILES shape. #19Phalcon\Talon\Http\HttpCode - HTTP status constants plus getDescription(), which returns the 404 (Not Found) form. It is deliberately an independent implementation of the standard reason phrases rather than a lookup into the application under test, so that asserting an application's emitted status string against it actually asserts something. #19Phalcon\Talon\Http\JsonType - validates a decoded JSON document against a map of type expectations (string, integer, float, boolean, array, null, the :date filter, | unions, and nested maps). Keys absent from the map are ignored, so a map can describe just the part of an envelope a test cares about. float accepts a whole number too - JSON has a single number type, so {"price": 10} decodes to an int and a strict float would fail on every round value. integer stays strict. #19Phalcon\Talon\Http\JsonSubset - recursive subset matching, so a fragment can be asserted against a full document. Keys and list elements present in the response but absent from the expectation are ignored, and list elements match in any order. An empty expected list is the exception: ['data' => []] asserts that data is empty rather than matching any data at all. #19TALON_REST_URL to Settings::fromEnv(), readable via Settings::get('rest_url') and defaulting to http://127.0.0.1:8080. It is resolved through Talon's shared Settings and so is the same for every test in a run; RestTrait::useRestBaseUrl() points an individual test somewhere else. #19AbstractUnitTestCase::setUp() now guards its Di::reset() call behind phalconAvailable(), so packages that use the Talon abstract test cases without Phalcon (or without the DI component) no longer hit a fatal Class "Phalcon\Di\Di" not found at setup time - because AbstractUnitTestCase is the root of the hierarchy, every abstract (AbstractServicesTestCase/AbstractDatabaseTestCase/AbstractBrowserTestCase/AbstractFunctionalTestCase) inherits the fix and can be extended without a DI. When Phalcon is available the reset still runs unchanged. #14AbstractBrowserTestCase now clears $_SESSION in tearDown() as well as setUp(), so a test that logs a user in no longer leaks that session into a following test that does not reset it itself. The browser fixture session persists across the in-process app rebuilds and Di::reset() does not clear it, which surfaced as a random-order failure of FixtureSmokeTest::testSecuredShowsGuestWithoutSession (it read a sarah session left behind by BrowserTraitTest). #16composer test/test-coverage/test-coverage-html and the CI database step now route through bin/talon (php bin/talon run mysql pgsql replaces the two explicit PHPUnit invocations). Infection still drives PHPUnit directly by design; the raw vendor/bin/phpunit -c ... invocations keep working as a fallback. #5talon CLI runner: vendor/bin/talon run [suites...] [-- passthrough] fronts PHPUnit per mapped suite, talon suites lists the map. Suites come from a root talon.php (per-suite config/php/env/args, global php/env merged in) or, with zero config, from discovered phpunit*.xml files (phpunit.mysql.xml -> mysql; phpunit.xml.dist -> unit, the default). Options forward to PHPUnit starting at the first option talon does not recognize (everything after -- always forwards verbatim); the reserved name all runs every suite sequentially with a max() exit code; subprocess re-exec keeps per-suite ini flags (e.g. extension=phalcon.so) and env vars possible. Adds phalcon/cli-options-parser (^2.0) as a runtime dependency. #5infection/infection (^0.29) as a dev dependency, configured via resources/infection.json5 (source src/, [@default](https://github.com/default) mutators, logs and temp files under tests/_output/infection/), with a composer test-mutation script and a report-only step in the CI coverage job. The suite was hardened until the mutation score plateaued at 99% MSI / 99% covered MSI under the ext-phalcon (v5) test image - the five surviving mutants are provider-specific and are killed under the phalcon/phalcon (v6) provider the CI step runs on. Every config-level mutator ignore is individually justified in resources/infection.json5. #7AbstractUnitTestCase::mockWithConstructor()'s $ctorArgs docblock type widened from array<int, mixed> to array<array-key, mixed> - the implementation always normalized string-keyed arguments via array_values(); the annotation now matches. No behavior change - static-analysis-only fix. #7ServicesTrait::setMemcachedKey(), setRedisKey(), and clearMemcached() now assert the backing operation succeeded (a failed Memcached::set()/flush() or a non-OK Redis SET reply reports as a test failure with the key in the message) instead of silently discarding the result - a failed seed write could previously let a test pass without exercising its intent. #10ServicesTrait::hasMemcachedKey() (and doesNotHaveMemcachedKey()) now checks Memcached::getResultCode() against RES_NOTFOUND, so a stored literal false is correctly reported as present - previously indistinguishable from a missing key. #10FileSystemTrait::safeDeleteFile() and safeDeleteDirectory() still tolerate a missing target, but when it exists every unlink()/rmdir() is now asserted - a failed delete reports as a clean test failure with the path (instead of a PHP warning) rather than silently leaking state into subsequent tests. #10Settings::getMemcachedOptions(), getRedisOptions(), and getRedisClusterOptions() are removed. Use getServiceOptions('memcached'), getServiceOptions('redis'), and getServiceOptions('redisCluster') instead - same return shape, same values, no other change needed at call sites.Settings's internal storage is unified: db's three drivers (mysql/pgsql/sqlite) and every other service (redis/memcached/redisCluster/beanstalk) are now all stored as named ServiceOptions instances in one collection. getDatabaseOptions(string $driver)/getDatabaseDsn(string $driver) keep their exact existing public signatures and behavior (including throwing UnknownDriver for unsupported names) - only their internals changed.Settings::fromArray()'s config shape: the separate top-level 'redis'/'memcached'/'redisCluster' keys are replaced by one 'services' => ['redis' => [...], 'memcached' => [...], 'redisCluster' => [...], 'beanstalk' => [...]] section (matching how beanstalk already worked). Each entry may be a raw options array or an already-constructed ServiceOptions instance. 'db' => [...] keeps its existing shape unchanged.Phalcon\Talon\ServiceOptions (new) - a small value object (getName(): string, getOptions(): array) used internally by Settings to represent every named service's (including each db driver's) options uniformly.Settings::fromEnv() now reads dump_file and initial_queries (no DATA_* prefix, matching the existing lowercase driver env-var convention DatabaseTrait already used) into the settings' extra-config bag, retrievable via Settings::get().Database\Connection now applies PRAGMA journal_mode = WAL for the sqlite driver and runs an optional initial_queries SQL string (from Settings::get('initial_queries')) immediately after connecting, before any other statement.Traits\DatabaseTrait::getConnection() now automatically loads the schema from Settings::get('dump_file') the first time a connection is built for a given driver, guarded by the same per-driver cache used for connection reuse. No-op when dump_file is unset (fully backward compatible).Settings::getDatabaseOptions('pgsql') now includes a schema key, read from DATA_POSTGRES_SCHEMA.Traits\DatabaseTrait::getDriver(): string (new, public) exposes the driver getConnection() already resolves internally.Settings::getServiceOptions(string $name): array (new, also on Contracts\Settings) - a generic, key/name-based accessor for simple host/port-shaped third-party services, reading redis, memcached, redisCluster, beanstalk, and each db driver's options. fromEnv() populates it from a small internal declarative table; fromArray() reads a services section shaped the same way db is.Bootstrap\Runner::initEnvironment() now also sets display_errors/display_startup_errors, a en_US.utf-8 locale, the mbstring internal encoding default, clears the stat cache, and (when the xdebug extension is loaded) tunes xdebug.cli_color/xdebug.dump_globals/xdebug.show_local_vars/xdebug.max_nesting_level/xdebug.var_display_max_depth for readable CLI debugging. error_reporting(E_ALL) and the UTC timezone it already set are unchanged. (mb_substitute_character('none') was deliberately not ported despite being part of the source this was consolidated from - it makes mb_convert_encoding() silently drop unrepresentable characters instead of substituting ?, which conflicts with at least one real test's expectations.)Bootstrap\Runner::isExtensionLoaded(string $extension): bool (new, protected) - wraps the extension_loaded() check initEnvironment()'s xdebug tuning uses, so a subclass can fake it in tests.Traits\FunctionalTrait::resolveDi(InjectionAwareInterface $application): DiInterface (new, protected) - factored out of the internal di() helper so a subclass can fake DI resolution in tests, without needing a real InjectionAwareInterface::getDI() implementation that returns null (not expressible on every Phalcon provider - see Fixed).Traits\FunctionalTrait's internal di() helper now throws Exceptions\ResponseNotDispatched (matching its existing precondition-failure behavior elsewhere in the same method) instead of returning a null DI silently typed as non-nullable - InjectionAwareInterface::getDI() can genuinely return null, which was previously unaccounted for.Traits\ResultSetTrait::mockResultSet()'s return type now fully specifies Phalcon\Mvc\Model\Resultset's and Phalcon\Mvc\ModelInterface's generic type parameters. No behavior change - static-analysis-only fix.Browser\Client now emits the application's response cookies as Set-Cookie headers, so cookies set through the Phalcon cookies service round-trip through the BrowserKit cookie jar between in-process requests (and cookie deletions evict them). The application under test must run with cookie encryption disabled.Browser\Client caps redirect-following (MAX_REDIRECTS); an in-process redirect cycle now raises a clean LogicException instead of recursing until the runtime's stack overflows. The previous default (-1, unbounded) could segfault the Phalcon extension on a redirect loop.BrowserTrait::setCookie() scopes the cookie to the request host, so a cookie set in a test can be expired by an application response — an empty-domain cookie (the previous default) could never be cleared.AbstractUnitTestCase::mockWithoutConstructor() / mockWithConstructor() - build a test
double with the constructor disabled or invoked (with constructor arguments), keeping the
non-overridden methods real. Method overrides become stubs (a Closure body, a return value,
or null for a type-safe default) and property overrides are set via reflection. Built as a
stub on PHPUnit 12+ (so no "mock object without expectations" notice is raised); works across
PHPUnit 10.5-13.ServicesTrait now skips (instead of erroring) when the backing client is unavailable: the
memcached helpers skip when ext-memcached is not loaded, and the redis helpers skip when the
predis/predis package is not installed.Initial Release
How can I help you explore Laravel packages today?