Skip to content

Pull Request: Upgrade to PHP 7.4+ with PHP 8.0/8.1 support#389

Merged
armanist merged 11 commits into
quantum-php:masterfrom
Wiltzsu:feature/php-7.4-compatibility
Jan 4, 2026
Merged

Pull Request: Upgrade to PHP 7.4+ with PHP 8.0/8.1 support#389
armanist merged 11 commits into
quantum-php:masterfrom
Wiltzsu:feature/php-7.4-compatibility

Conversation

@Wiltzsu

@Wiltzsu Wiltzsu commented Jan 1, 2026

Copy link
Copy Markdown
Collaborator

PR: Upgrade to PHP 7.4+ with PHP 8.0/8.1 support

Overview

The upgrade includes:

  • CI pipeline updates to test against PHP 7.4, 8.0, and 8.1
  • PHP 8.1 deprecation fixes (null parameter handling)
  • A few PHP 8.4 forward compatibility fixes (implicitly nullable parameters)
  • Selective adoption of declare(strict_types=1) in Exception classes only

Test Results: 941/942 tests passing across all PHP versions (1 flaky external API test for YouTube)

Key Changes

Full details in CHANGELOG.md and commit history. Summary:

  • CI: GitHub Actions now tests PHP 7.4, 8.0, 8.1 with strict error reporting
  • Compatibility fixes: Null checks added before explode(), parse_url(), str_replace()
  • Forward compatibility: Explicit nullable types added (?string, ?JwtToken) to avoid PHP 8.4 deprecations
  • Type hints: Magic method __get() now has typed parameter

Important decisions & rationale

1. Rector TYPE_DECLARATION automated refactoring - SKIPPED

Rector suggested aggressive modernization:

  • 309 files would be modified (~60% of codebase)
  • 16,394 lines of changes
  • Affects models, controllers, adapters, traits, and internal helpers

Decision: Skip automated refactoring

Reasons:

  • Breaking changes - Adding types to public methods breaks user code that extends framework classes (QtController, QtMiddleware, QtModel, adapters)
  • Too aggressive - 309 files at once is too risky for a stable release
  • Task guidelines - Per requirements: "Do not over-tighten types on controllers, middleware, ORM/DBAL adapters, anything users extend"

My suggestion:
Add types manually and incrementally in future versions.


2. Strict types policy (declare(strict_types=1))

Added to: Exception classes only (37 files)

All exception classes now have declare(strict_types=1) to enforce type safety on their static factory methods.

Benefits:

  • Prevents accidental type errors in exception creation
  • Enforces consistency in framework's internal error handling
  • Exceptions are only instantiated by framework code, not user data

NOT added to: Validators, Enums, or Public APIs

Validators (excluded):

  • Accept mixed input types from forms, APIs, and databases
  • Need flexibility to handle strings, integers, floats, arrays
  • Type coercion is intentional behavior (e.g., form input "25" vs application int 25)
  • Tests confirmed: Adding strict_types breaks real-world usage patterns

Enums (excluded):

  • Pure constant classes with no methods or parameters
  • declare(strict_types=1) has no effect on constant access
  • Removing it keeps code cleaner

Public APIs (excluded):

  • Controllers, Middleware, Models, Adapters remain flexible
  • Users extend these classes and override methods
  • Strict types would create breaking changes for user code
  • Aligns with guideline: "Avoid in controllers & adapters"

Decision rationale:

  • Selective adoption only where it provides clear value
  • No BC breaks. Avoided public/extensible APIs
  • Proven by tests: 941/942 tests passing validates the approach

This conservative approach allows strict types to be expanded gradually in future versions (v3.1+) as type coverage increases across the codebase.


Known issues & notes

Third-party deprecations (PDO)

52 deprecation warnings from j4mie/idiorm library (PDO::quote(): Passing null to parameter). These are in third-party code and cannot be fixed without library updates.

cURL error message differences

PHP 7.4 uses "host name" while PHP 8.1+ uses "hostname" in cURL error messages. Tests aligned with PHP 7.4 format.

Flaky external test

1 YouTube API test occasionally fails due to external API cookie changes.

Travis CI status

.travis.yml is present in the repo but not actively triggered on recent commits. GitHub Actions is the primary CI. Should Travis CI be updated to match or removed?


Testing strategy

  • Tested manually on PHP 7.4, 8.0, 8.1
  • CI pipeline validates all three versions automatically
  • Strict error reporting enabled (E_ALL, --fail-on-warning, --fail-on-risky)
  • 941/942 tests passing

This is a conservative upgrade that maintains backward compatibility while enabling PHP 8.0/8.1 support.

- Updated PHP requirement from >=7.3 to ^7.4
- Removed redundant E_STRICT from error_reporting (included in E_ALL since PHP 5.4)
- Dependencies frozen at current versions (no updates triggered)

Security note: Audit found 4 vulnerabilities in league/commonmark and twig/twig.
These will be addressed in a separate  PR.

Related: quantum-php#307
- Add dynamic property declarations for PHP 8.2 compatibility
- Replace == with === for strict comparisons
- Simplify empty() checks to explicit array comparisons
- Replace key_exists() with array_key_exists()
- Add explicit type checks for closures and objects
- Simplify boolean return logic
- Combine nested if statements
- Fix property visibility in test classes

Fixes quantum-php#307 - Phase 1: CODE_QUALITY

Test results: 907/942 passing (35 environmental errors, 2 flaky API tests)
- Replace list() with array destructuring []
- Convert closures to arrow functions (fn)
- Use null coalescing assignment (??=)
- Replace __CLASS__ with self::class
- Use ::class constants instead of string class names
- Replace rand() with random_int()
- Use array_key_last() instead of end()/key()
- Modern setcookie() array syntax

Fixes quantum-php#307 - Phase 2: UP_TO_PHP_74
Test results: 907/942 passing (1 flaky external API test)
- Add explicit glue parameter to implode() calls for PHP 8.0+ compatibility
- Fix mailer adapters: MailgunAdapter, MandrillAdapter, SendgridAdapter, SendinblueAdapter, SmtpAdapter

Fixes quantum-php#307 - Phase 3: UP_TO_PHP_80
Test results: 941/942 passing (1 flaky external API test)
Add declare(strict_types=1) to 37 exception classes for type safety
on static factory methods. Excluded from validators (need flexibility)
and enums (no methods to type-check).

Part of quantum-php#307
Tests: 941/942 passing
This commit addresses PHP 8.1 null parameter deprecations and updates
the CI pipeline to test against PHP 7.4, 8.0, and 8.1.

**PHP 8.1 compatibility fixes:**
- Fixed null parameter deprecation in Query::getQueryParam()
  Added null check before explode() to prevent deprecation warning
- Fixed null parameter deprecation in Url::getAllSegments()
  Added null check before parse_url() to prevent deprecation warning
- Fixed null parameter deprecations in test cache configuration
  Added ?? '' fallback for env('APP_NAME') calls in 4 cache adapters

**CI pipeline updates:**
- Updated .github/workflows/php.yml to test PHP 7.4, 8.0, 8.1 (was 7.3, 7.4)
- Added error_reporting=E_ALL to ini-values for stricter testing
- Added --fail-on-warning and --fail-on-risky flags to PHPUnit

**Test results:**
- PHP 7.4: 940/942 tests pass (99.8%)
- PHP 8.0: 940/942 tests pass (99.8%)
- PHP 8.1: 939/942 tests pass (99.7%)
- Remaining failures: 2 environmental (Docker GD), 1 flaky external API

Part of quantum-php#307
Addresses implicit nullable parameter deprecations introduced in PHP 8.4.
These changes are backward compatible with PHP 7.4+ and prevent future deprecation warnings.

**Changes:**
- JwtAuthAdapter: Changed 'JwtToken $jwt = null' to '?JwtToken $jwt = null'
- SleekDbal: Changed 'string $modelName = null' to '?string $modelName = null'
- SleekDbal: Added missing type hint to __get() magic method (string $key)
- Validator: Changed 'string $rule = null' to '?string $rule = null'

**Compatibility:**
- Works on: PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4+
- No breaking changes
- Nullable type hints (?) were introduced in PHP 7.1
- https://www.php.net/manual/en/migration71.new-features.php

Part of quantum-php#307
Document all changes related to PHP 7.4+ compatibility upgrade:

BREAKING CHANGES:
- Minimum PHP version requirement raised from 7.3 to 7.4

CHANGED:
- Modernized codebase syntax with Rector (array destructuring,
  arrow functions, null coalescing assignment, class constants,
  random_int, array_key_last, modern setcookie)
- Updated CI to test PHP 7.4, 8.0, 8.1 with strict error reporting
- Added strict types to all 37 Exception classes

FIXED:
- PHP 8.1 null parameter deprecations
- PHP 8.4 implicitly nullable parameters
- PHP 8.4 missing type hints on magic methods
- Deprecated E_STRICT constant usage
- Cross-version cURL error message compatibility

ADDED:
- Rector dev dependency
- Required CI extensions (bcmath, gd, zip)
- Strict PHPUnit testing flags

REMOVED:
- PHP 7.3 support

Follows Keep a Changelog format for clear upgrade documentation.

Refs quantum-php#307
@Wiltzsu

Wiltzsu commented Jan 1, 2026

Copy link
Copy Markdown
Collaborator Author

Hi!

It looks like the tests for 7.4 and 8.0 are passing, but 8.1 is failing because of 52 errors from third-party j4mie/idiorm deprecations

Should we accept the failures or suppress them? I'm happy to implement any possible solution. Also open to any feedback on the changes or suggestions for improvements!

@armanist

armanist commented Jan 2, 2026

Copy link
Copy Markdown
Collaborator

Hi @Wiltzsu,
Thanks for the excellent work — this is a very solid and conservative upgrade, exactly what we want for Quantum at the moment.

Feedback & notes:

  • Skipping Rector’s aggressive type declarations is the right call for a framework. We’ll add types incrementally in future versions.

  • Selective declare(strict_types=1) applied only to Exception classes is approved.

  • Validators, controllers, middleware, and models should remain non-strict for now.

  • For cURL-related tests, we should avoid asserting full error messages and rely on error codes or stable substrings instead.
    For example:

    $this->assertStringContainsString(
        'CURLE_COULDNT_RESOLVE_HOST',
        $errors['message']
    );
    
  • Deprecation warnings from j4mie/idiorm should not fail CI. We should suppress vendor deprecations or exclude them from failure conditions.
    For example, in phpunit.xml:

    <php>
        <ini name="error_reporting" value="E_ALL &amp; ~E_DEPRECATED"/>
    </php>
    
  • The YouTube API test depends on an external service and can be flaky at times. Since it’s generally stable and useful, it can remain as is for now, but it should not block development unexpectedly.

  • Travis CI can be removed — GitHub Actions is the primary CI, so .travis.yml can be safely dropped.

The PR looks good overall. There are just a few small, mostly CI/test-related tweaks to address before merging.

- HttpClientTest: Use assertStringContainsString for cURL error codes
  Makes tests resilient to error message wording differences across PHP versions

- Suppress vendor deprecations in test suite
  - phpunit.xml: Add error_reporting suppression and convertDeprecationsToExceptions
  - tests/bootstrap.php: Set error_reporting early for ErrorHandler timing

The bootstrap.php change is required because testing showed that Quantums
@Wiltzsu

Wiltzsu commented Jan 3, 2026

Copy link
Copy Markdown
Collaborator Author

Hello @armanist,

I've addressed the feedback from the review:

cURL test compatibility

Changed HttpClientTest to use assertStringContainsString('CURLE_COULDNT_RESOLVE_HOST', ...) instead of asserting full error messages.

Vendor deprecation suppression

Implemented deprecation suppression as suggested with phpunit.xml. I also added error_reporting() in tests/bootstrap.php due to a timing issue:

Why the bootstrap change is needed:
The bootstrap.php change is required because testing showed that Quantum's custom ErrorHandler catches deprecations before PHPUnit applies phpunit.xml ini settings. Early suppression ensures the ErrorHandler respects the configuration from the moment it registers.

If there's a preferred alternative approach for handling this timing issue, I'm happy to refactor.

Removed .travis.yml

Deleted legacy Travis CI configuration as requested.

public function isEmpty(): bool
{
return $this->first() === null;
return !$this->first() instanceof \Quantum\Model\QtModel;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a legitimate bug in that particular Rector rule. The rule probably wasn't accounting for operator precedence correctly when generating the refactored code.

@armanist

armanist commented Jan 4, 2026

Copy link
Copy Markdown
Collaborator

@Wiltzsu latest changes appear to have reduced the test failures, which is good enough for now, so I’m going to merge this.

There are still a few remaining test failures, mainly related to cURL and HttpClient tests. Those are not related to your changes, and I’ll address them separately later.

Thanks for your contribution 👍

@armanist armanist merged commit 871bbcd into quantum-php:master Jan 4, 2026
1 of 4 checks passed
@Wiltzsu

Wiltzsu commented Jan 4, 2026

Copy link
Copy Markdown
Collaborator Author

My pleasure, thank you for the opportunity!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants