Skip to content

Commit 147d9d4

Browse files
authored
Migrate to PHP 8.4 (Ouzo 4.0.0) (#334)
1 parent 4df329d commit 147d9d4

60 files changed

Lines changed: 1444 additions & 2319 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.coveralls.yml

Lines changed: 0 additions & 2 deletions
This file was deleted.

.github/workflows/build.yaml

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,62 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
php: [ '8.2.7' ]
13+
php: [ '8.4' ]
1414
db: [ 'MySQL', 'PostgreSQL', 'SQLite' ]
1515
include:
1616
- db: MySQL
17-
phpunitArgs: "--exclude-group postgres,sqlite3"
18-
phpunitEnv: db=mysql # from config/test/config.php
17+
phpunitArgs: "--exclude-group postgres --exclude-group sqlite3"
18+
phpunitEnv: db=mysql
1919
- db: PostgreSQL
20-
phpunitArgs: "--exclude-group mysql,sqlite3"
21-
phpunitEnv: db=postgres # from config/test/config.php
20+
phpunitArgs: "--exclude-group mysql --exclude-group sqlite3"
21+
phpunitEnv: db=postgres
22+
coverage: pcov
2223
- db: SQLite
23-
phpunitArgs: "--exclude-group mysql,postgres,non-sqlite3"
24-
phpunitEnv: db=sqlite3 # from config/test/config.php
24+
phpunitArgs: "--exclude-group mysql --exclude-group postgres --exclude-group non-sqlite3"
25+
phpunitEnv: db=sqlite3
2526
services:
2627
mysql:
2728
image: mysql:8
2829
env:
2930
MYSQL_DATABASE: ouzo_test
3031
MYSQL_ROOT_PASSWORD: password
31-
# Set health checks to wait until mysql has started
3232
options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5
3333
ports:
3434
- 3306:3306
3535
postgres:
36-
image: postgres:10.0
36+
image: postgres:18
3737
env:
3838
POSTGRES_DB: ouzo_test
3939
POSTGRES_USER: ouzo_user
4040
POSTGRES_PASSWORD: password
41-
# Set health checks to wait until postgres has started
4241
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
4342
ports:
4443
- 5432:5432
4544

4645
steps:
4746
- name: Checkout
48-
uses: actions/checkout@v2
47+
uses: actions/checkout@v6
4948

5049
- name: Use PHP ${{ matrix.php }}
5150
uses: shivammathur/setup-php@v2
5251
with:
5352
php-version: ${{ matrix.php }}
5453
tools: composer:v2
55-
coverage: "${{ matrix.coverage }}"
54+
coverage: ${{ matrix.coverage || 'none' }}
5655

5756
- name: Seed database (PostgreSQL)
58-
if: "${{ matrix.db == 'PostgreSQL' }}"
57+
if: matrix.db == 'PostgreSQL'
5958
run: PGPASSWORD=password psql -h 127.0.0.1 -U ouzo_user -v ON_ERROR_STOP=1 -e -f test/test-db/recreate_schema.sql ouzo_test
6059

6160
- name: Seed database (MySQL)
62-
if: "${{ matrix.db == 'MySQL' }}"
61+
if: matrix.db == 'MySQL'
6362
run: |
6463
mysql -h 127.0.0.1 -u root -ppassword -e "CREATE USER travis; GRANT ALL ON *.* TO travis;"
6564
cat test/test-db/recreate_schema_mysql.sql | mysql -h 127.0.0.1 -u root -ppassword ouzo_test
6665
6766
- name: Seed database (SQLite)
68-
if: "${{ matrix.db == 'SQLite' }}"
67+
if: matrix.db == 'SQLite'
6968
run: echo | sqlite3 ouzo_test -init test/test-db/recreate_schema_sqlite3.sql
70-
# I don't know how to run sqlite without it going into interactive mode, so I piped empty stream to it
7169

7270
- name: Cache dependencies installed with Composer
7371
uses: actions/cache@v4
@@ -77,12 +75,32 @@ jobs:
7775
restore-keys: php${{ matrix.php }}-composer-
7876

7977
- name: Install dependencies
80-
run: composer install --no-progress;
78+
run: composer install --no-progress
8179

8280
- name: Run tests
83-
run: ${{ matrix.phpunitEnv }} ./vendor/bin/phpunit ${{ matrix.phpunitArgs }} test
81+
if: matrix.coverage == ''
82+
run: ${{ matrix.phpunitEnv }} ./vendor/bin/phpunit ${{ matrix.phpunitArgs }}
8483

85-
- name: Upload coverage results to Coveralls
86-
env:
87-
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88-
run: ./vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v
84+
- name: Run tests with coverage
85+
if: matrix.coverage != ''
86+
run: ${{ matrix.phpunitEnv }} ./vendor/bin/phpunit ${{ matrix.phpunitArgs }} --coverage-clover build/logs/clover.xml --coverage-html build/coverage-html
87+
88+
- name: Coverage summary
89+
if: matrix.coverage != ''
90+
run: |
91+
php -r '
92+
$xml = simplexml_load_file("build/logs/clover.xml");
93+
$metrics = $xml->project->metrics;
94+
$stmts = (int)$metrics["statements"];
95+
$covered = (int)$metrics["coveredstatements"];
96+
$pct = $stmts > 0 ? round($covered / $stmts * 100, 2) : 0;
97+
file_put_contents(getenv("GITHUB_STEP_SUMMARY"), "## Code Coverage\n\n**{$pct}%** ({$covered}/{$stmts} statements)\n");
98+
'
99+
100+
- name: Upload coverage report
101+
if: matrix.coverage != ''
102+
uses: actions/upload-artifact@v7
103+
with:
104+
name: coverage-report
105+
path: build/coverage-html
106+
retention-days: 14

.travis.yml

Lines changed: 0 additions & 49 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
11
CHANGELOG
22
=========
33

4-
IN PROGRESS
4+
Release 4.0.0
5+
--------
6+
Support for PHP versions older than 8.4 is dropped. Minimal PHP version required is 8.4.
7+
8+
Breaking changes:
9+
* [Core] `Model::jsonSerialize()` now returns `mixed` (attributes array) instead of `string` (JSON). Use `json_encode($model)` to get JSON string.
10+
* [Core] Upgraded to Symfony Console 7 / Event Dispatcher 7. `Command::execute()` must return `int`.
11+
* [Core] Upgraded to PSR-3 v3. Logger methods require `string|Stringable $message` parameter.
12+
* [Core] Removed `doctrine/annotations` dependency.
13+
14+
Enhancements:
15+
* [ORM] Added support for count query with `DISTINCT` in PostgreSQL dialect.
16+
* [Core] `Bootstrap::withErrorHandler(ErrorHandler $errorHandler)` set custom error handler which is registered on `Bootstrap::runApplication()`.
17+
* [Tests] Added new assertion `messageContains` to `CatchException`.
18+
* [Tests] Upgraded to PHPUnit 11.
19+
* [Utilities] `Arrays::find()`, `Arrays::any()`, `Arrays::all()` now use native PHP 8.4 `array_find`, `array_any`, `array_all`.
20+
* [Utilities] `Strings::EMPTY_STRING` is deprecated in favour of `Strings::EMPTY` (now using native `#[Deprecated]` attribute).
21+
* [Utilities] Deprecated global helper functions in ViewHelper and FormHelper now use native `#[Deprecated]` attribute instead of JetBrains annotations.
22+
* [Core] Adopted PHP 8.4 "new without parentheses" syntax.
23+
24+
Release 3.0.0
525
--------
626
Support for PHP 8.0 is dropped. Minimal PHP version required is 8.2.
727

CLAUDE.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Language
6+
7+
All commit messages, PR titles, PR descriptions, code comments, and documentation must be written in English.
8+
9+
## Code Style
10+
11+
### Imports
12+
- Always import classes with `use` statements. Never use fully qualified class names (FQCN) inline.
13+
- Order: PHP built-in → Ouzo framework → third-party.
14+
15+
### Types
16+
- Always declare parameter types and return types.
17+
- Fluent methods return `static` (not `self`).
18+
- Use `is_null()` instead of `=== null`.
19+
20+
### Strings & Arrays
21+
- Single quotes by default: `'value'`.
22+
- Double quotes only for interpolation: `"prefix_{$var}"`.
23+
- Always `[]` for arrays, never `array()`.
24+
25+
### Docblocks
26+
- Only when types cannot be expressed in code or behavior needs explanation.
27+
- Do not add `@param`/`@return` when method signatures are sufficient.
28+
29+
### Copyright header (every file)
30+
```php
31+
<?php
32+
/*
33+
* Copyright (c) Ouzo contributors, https://github.com/letsdrink/ouzo
34+
* This file is made available under the MIT License (view the LICENSE file for more information).
35+
*/
36+
```
37+
38+
### Classes
39+
- `final` only on classes with constants only (e.g. `HttpMethod`, `HttpStatus`).
40+
- `readonly class` for immutable value objects.
41+
- No leading underscore on private methods.
42+
- Keep methods short (3-20 lines), extract longer logic into private helpers.
43+
44+
### Error Handling
45+
- Throw exceptions immediately with readable messages, no error codes.
46+
- Never suppress or silently handle errors.
47+
48+
## What is Ouzo
49+
50+
Ouzo is a PHP MVC framework with built-in ORM, dependency injection, and utility libraries. Requires PHP 8.4+.
51+
52+
## Build & Test Commands
53+
54+
```bash
55+
# Install dependencies
56+
composer install
57+
58+
# Run all tests (defaults to PostgreSQL, excludes sqlite3 group)
59+
./vendor/bin/phpunit test
60+
61+
# Run tests against a specific database
62+
db=mysql ./vendor/bin/phpunit --exclude-group postgres,sqlite3 test
63+
db=postgres ./vendor/bin/phpunit --exclude-group mysql,sqlite3 test
64+
db=sqlite3 ./vendor/bin/phpunit --exclude-group mysql,postgres,non-sqlite3 test
65+
66+
# Run a single test file
67+
./vendor/bin/phpunit test/src/Ouzo/Core/Db/ModelQueryBuilderTest.php
68+
69+
# Run a single test method
70+
./vendor/bin/phpunit --filter testMethodName test/src/Ouzo/Core/Db/ModelQueryBuilderTest.php
71+
```
72+
73+
Test databases must be seeded before first run using SQL scripts in `test/test-db/`.
74+
75+
## Architecture
76+
77+
The `Ouzo\` namespace maps to four source modules via PSR-4:
78+
79+
- **`src/Ouzo/Core`** — MVC framework: routing, controllers, views, ORM (Active Record), database layer, config, sessions, middleware, i18n
80+
- **`src/Ouzo/Goodies`** — Standalone utility library: `Arrays`, `Strings`, `Functions`, `Clock`, `FluentArray`, `Optional`, `Json`, `Objects`, validators, iterators. Also provides test helpers (`Assert`, `CatchException`, `Mock`)
81+
- **`src/Ouzo/Inject`** — Dependency injection container using PHP 8 attributes (`#[Inject]`, `#[Named]`)
82+
- **`src/Ouzo/Migrations`** — Database migration system
83+
84+
Goodies and Inject are also published as standalone packages (`letsdrink/ouzo-goodies`, `letsdrink/ouzo-inject`).
85+
86+
### Request lifecycle
87+
88+
`Bootstrap``FrontController` → middleware chain (`SessionStarter``DefaultRequestId``LogRequest` → custom) → `RequestExecutor` resolves route → controller action → `View` renders response.
89+
90+
### ORM
91+
92+
`Model` extends `Validatable`, uses Active Record pattern. Relations: `belongsTo`, `hasOne`, `hasMany`. Query building via `ModelQueryBuilder` with fluent API. Database dialects: `MySqlDialect`, `PostgresDialect`, `Sqlite3Dialect`.
93+
94+
### Routing
95+
96+
Static API on `Route`: `get()`, `post()`, `put()`, `delete()`, `resource()`, `group()`. Annotation-based routing also supported.
97+
98+
## Testing Conventions
99+
100+
- `#[Test]` attribute, not `test` prefix in method names
101+
- Method names: `shouldDoSomething()` pattern
102+
- Test structure: `//given`, `//when`, `//then` comments
103+
- `#[DataProvider('name')]` attribute, not `@dataProvider` annotation
104+
- `#[Group('mysql')]`, `#[Group('postgres')]`, `#[Group('sqlite3')]`, `#[Group('non-sqlite3')]` for DB-specific tests
105+
- Base classes:
106+
- `DbTransactionalTestCase` — wraps each test in a transaction that rolls back
107+
- `ControllerTestCase` — HTTP testing with request/response mocking
108+
- Custom fluent assertions via `Ouzo\Tests\Assert`:
109+
- `Assert::thatArray($arr)->hasSize(3)->contains('x')`
110+
- `Assert::thatString($s)->startsWith('foo')`
111+
- `Assert::thatModel($m)->hasSameAttributesAs($expected)`
112+
- `CatchException::when($obj)->method()` + `CatchException::assertThat()->isInstanceOf(...)` for exception testing
113+
- Custom mock framework in `Ouzo\Tests\Mock\Mock`
114+
115+
## Key Directories
116+
117+
- `bin/` — CLI commands (Symfony Console): model/controller/migration generators, route listing
118+
- `config/test/` — Test environment config (DB connection selected by `db` env var)
119+
- `test/Application/` — Fixture models and controllers used by tests
120+
- `test/test-db/` — SQL schema scripts for each database engine
121+
- `console` — CLI entry point script

Dockerfile-dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM php:8.2.7
1+
FROM php:8.4
22

33
WORKDIR /var/www
44

Dockerfile-postgres

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
FROM postgres:10
1+
FROM postgres:18
22

33
ADD ./test/test-db/recreate_schema.sql /docker-entrypoint-initdb.d/

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
![Ouzo Framework](https://raw.githubusercontent.com/letsdrink/ouzo-website/master/img/logo.png)
22

3-
Ouzo is a PHP MVC framework with built-in ORM and util libraries. PHP 8.2 or later is required.
3+
Ouzo is a PHP MVC framework with built-in ORM and util libraries. PHP 8.4 or later is required.
44

5-
[![Build Status](https://travis-ci.org/letsdrink/ouzo.png?branch=master)](https://travis-ci.org/letsdrink/ouzo)
65
[![Build Status](https://github.com/letsdrink/ouzo/workflows/build/badge.svg?branch=master)](https://github.com/letsdrink/ouzo/actions)
76
[![Documentation Status](https://readthedocs.org/projects/ouzo/badge/?version=latest)](https://readthedocs.org/projects/ouzo/?badge=latest)
8-
[![Coverage Status](https://coveralls.io/repos/letsdrink/ouzo/badge.svg?branch=master)](https://coveralls.io/r/letsdrink/ouzo?branch=master)
97
[![Latest Stable Version](https://poser.pugx.org/letsdrink/ouzo/v/stable.svg)](https://packagist.org/packages/letsdrink/ouzo)
108
[![Total Downloads](https://poser.pugx.org/letsdrink/ouzo/downloads.svg)](https://packagist.org/packages/letsdrink/ouzo)
119
[![License](https://poser.pugx.org/letsdrink/ouzo/license.svg)](https://packagist.org/packages/letsdrink/ouzo)
@@ -45,6 +43,9 @@ See the [release notes](https://github.com/letsdrink/ouzo/blob/master/CHANGELOG.
4543
* Don't forget to update the docs!
4644
* And the changelog as well.
4745

48-
# Support for PHP versions older than 8.2
46+
# Support for older PHP versions
4947

50-
Ouzo has dropped support for PHP versions older than 8.2 since Ouzo 3.x. If you want to use Ouzo with PHP 5.6 or 7.x, please try Ouzo 1.x branch, for PHP 8.0 use 2.x branch.
48+
Ouzo 4.x requires PHP 8.4 or later. For older PHP versions:
49+
* PHP 8.2 — use Ouzo 3.x branch
50+
* PHP 8.0 — use Ouzo 2.x branch
51+
* PHP 5.6 / 7.x — use Ouzo 1.x branch

bin/ControllerGeneratorCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function configure()
3131
->addArgument('action', InputArgument::OPTIONAL, 'Action name');
3232
}
3333

34-
public function execute(InputInterface $input, OutputInterface $output)
34+
public function execute(InputInterface $input, OutputInterface $output): int
3535
{
3636
$this->input = $input;
3737
$this->output = $output;

bin/MigrationDumperCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
class MigrationDumperCommand extends MigrationCommand
1818
{
19-
public function configureCommand()
19+
public function configureCommand(): void
2020
{
2121
$this->setName('migration:dump')
2222
->addArgument('schema_file', InputArgument::REQUIRED, 'Schema output file')
2323
->addArgument('data_file', InputArgument::REQUIRED, 'Data output file');
2424
}
2525

26-
public function executeCommand(InputInterface $input, OutputInterface $output)
26+
public function executeCommand(InputInterface $input, OutputInterface $output): int
2727
{
2828
$dbConfig = new MigrationDbConfig($input);
2929
$schemaFile = $input->getArgument('schema_file');

0 commit comments

Comments
 (0)