Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
50250bd
[BUGFIX] Improve clarity and robustness of InfoBox texts
froemken Jun 12, 2026
a3ac797
[TASK] Refactor InfoBox system to use constructor injection
froemken Jun 12, 2026
240f0f3
[TASK] Declare InfoBox classes as readonly
froemken Jun 12, 2026
3def9a5
[TASK] Declare InfoBox subclasses as final
froemken Jun 12, 2026
9e9d5ee
[TASK] Decouple InfoBox classes from ViewInterface
froemken Jun 12, 2026
d1cf322
[TASK] Introduce InfoBoxInterface to define TITLE
froemken Jun 12, 2026
3e0f9e8
[TASK] Remove abstract getBody method from AbstractInfoBox
froemken Jun 12, 2026
2f28948
[TASK] Move constructor to each individual InfoBox subclass
froemken Jun 12, 2026
7a93cc8
[TASK] Delete AbstractInfoBox class
froemken Jun 12, 2026
f15f36a
[TASK] Reorder members in InfoBox subclasses
froemken Jun 12, 2026
009d43b
[TASK] Apply TYPO3 Coding Guidelines (CGL)
froemken Jun 12, 2026
2a3f3ef
[TASK] Rename GitHub workflow configuration file
froemken Jun 12, 2026
e052c93
[DOCS] Update InfoBoxSystem specification for stateless architecture
froemken Jun 12, 2026
84a7a75
[TASK] Add TYPO3 v14 metadata to composer.json
froemken Jun 12, 2026
38442e9
[TASK] Add Rector configuration
froemken Jun 12, 2026
b34c1f5
[TASK] Update TYPO3 version constraint in Rector configuration to LTS
froemken Jun 12, 2026
d75e643
[TASK] Introduce new CGL configuration and script
froemken Jun 12, 2026
20db7a0
[TASK] Add ssch/typo3-rector to require-dev
froemken Jun 12, 2026
389c075
[TASK] Apply Rector refactoring and CGL styling
froemken Jun 12, 2026
82f42bf
[TASK] Add packages/ to gitignore
froemken Jun 12, 2026
c58bc35
[TASK] Add scratch/ to gitignore
froemken Jun 12, 2026
bfe990a
[TASK] Update TYPO3 version constraint in ext_emconf.php via Rector
froemken Jun 12, 2026
eed4591
[TASK] Clean ext_localconf.php and remove call_user_func wrapper
froemken Jun 12, 2026
14d2718
[DOCS] Update TYPO3 version and build status badge in README.md
froemken Jun 12, 2026
f936a4e
[TASK] Resolve PHPStan static analysis errors
froemken Jun 12, 2026
493a5cf
[BUGFIX] Use NormalizedParams instead of deprecated GeneralUtility::g…
froemken Jun 12, 2026
cf9c994
[TASK] Upgrade checkout action to v6 and add PHP 8.4 to matrix
froemken Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions .Specs/InfoBoxSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,41 @@ This document specifies the internal "InfoBox" rendering system. It is a reusabl

## Overview

The system is designed to decouple data fetching and rendering from the controllers. Instead of a controller fetching all data and passing it to a monolithic Fluid template, the responsibility is distributed among several smaller, specialized `InfoBox` classes.
The system is designed to decouple database data fetching and rendering from the controllers. Instead of a controller fetching all data and passing it to a monolithic Fluid template, the responsibility is distributed among several smaller, specialized `InfoBox` classes.

The core components of this system are:

1. **`Page` Service**: A DTO that acts as a container for a collection of InfoBoxes for a specific topic (e.g., "InnoDB").
2. **`AbstractInfoBox`**: A base class that defines the contract for all InfoBox objects.
3. **Concrete InfoBox Implementations**: Specific classes (e.g., `UptimeInfoBox`) that extend `AbstractInfoBox` and contain the logic to fetch and prepare data for a single panel.
4. **Service Tagging**: The Symfony Dependency Injection container uses tags to collect all relevant InfoBox implementations and inject them into the correct `Page` service.
1. **`InfoBoxInterface`**: The interface (`Classes/InfoBox/InfoBoxInterface.php`) defining the contract for all InfoBoxes. It requires a `TITLE` constant and a `getBody(): string` method.
2. **Concrete InfoBox Implementations**: Specific `final readonly` classes (e.g., `UptimeInfoBox`) that implement `InfoBoxInterface` (and optional interfaces like `InfoBoxUnorderedListInterface` or `InfoBoxStateInterface`) and contain the logic to calculate and format data.
3. **`RenderInfoBoxFactory`**: A stateless rendering service (`Classes/InfoBox/RenderInfoBoxFactory.php`) that maps the structured InfoBox properties to a Fluid view and generates the final HTML output.
4. **Service Tagging**: InfoBox implementations are tagged with their respective topic tags (e.g., `mysqlreport.infobox.status`) using the PHP attribute `#[AutoconfigureTag]`.
5. **Constructor Autowiring**: The DI container instantiates the models `StatusValues` and `Variables` using repositories and caches them. They are autowired directly to the InfoBox constructors via the global `bind` configuration in `Services.yaml`.

## Component Analysis

### 1. `Page` Service (`Classes/Menu/Page.php`)
### 1. `InfoBoxInterface` (`Classes/InfoBox/InfoBoxInterface.php`)

- A `readonly` DTO that receives an iterable of `AbstractInfoBox` objects via its constructor.
- The `getRenderedInfoBoxes()` method iterates through all its InfoBoxes.
- For each InfoBox, it calls the `updateView()` method, which returns a configured `ViewInterface` object.
- It then calls `render()` on that view and concatenates the resulting HTML strings.
- The final, combined HTML of all InfoBoxes is returned to the controller.
- Defines the contract:
- `public const TITLE = '';`
- `public function getBody(): string;`

### 2. `AbstractInfoBox` (`Classes/InfoBox/AbstractInfoBox.php`)
### 2. `RenderInfoBoxFactory` (`Classes/InfoBox/RenderInfoBoxFactory.php`)

- Defines the basic structure of an InfoBox, including methods for rendering a title, body, and footer.
- Crucially, it contains the `updateView()` method, where the concrete class assigns its fetched data to the view.
- Many InfoBoxes use the `GetStatusValuesAndVariablesTrait` to easily access the `StatusRepository` and `VariablesRepository`.
- Takes the `ViewFactoryInterface` in the constructor.
- The `render(iterable $infoBoxes): string` method iterates over the InfoBoxes, extracts their structured data, assigns them to a Fluid view, and compiles the final HTML.
- It keeps the InfoBox classes completely independent of the view layer (fully headless-ready).

### 3. Service Configuration (`Configuration/Services.yaml`)

This is where the system is wired together.

- **InfoBox Tagging**: Concrete InfoBox classes are automatically tagged using the `#[AutoconfigureTag]` PHP attribute. For example, an InfoBox for the "Status" page will have `#[AutoconfigureTag('mysqlreport.infobox.status')]`.
- **`Page` Service Definition**: For each sub-module that uses this pattern, a dedicated `Page` service is defined.
- **`!tagged_iterator`**: The `arguments` of the `Page` service use the `!tagged_iterator` directive to collect all services with a specific tag. For example, the `mysqlreport.page.information` service receives all InfoBoxes tagged with `mysqlreport.infobox.status`.
- **Controller Injection**: The corresponding controller (e.g., `StatusController`) then gets the correctly configured `Page` service injected.
- **Factories**: `StatusValues` and `Variables` are registered as factory services calling the `findAll` repository methods.
- **Autowiring Bindings**: The types are globally bound to the factory services under `_defaults.bind`.
- **Direct Controller Injection**: The controller (e.g., `StatusController`) is configured to receive the tagged iterator of InfoBoxes directly via `!tagged_iterator`.

## Workflow Example (`StatusController`)

1. `StatusController` has `@mysqlreport.page.information` injected as `$this->page`.
2. The `indexAction` calls `$this->page->getRenderedInfoBoxes()`.
3. The `Page` object iterates through all InfoBoxes tagged with `mysqlreport.infobox.status` (e.g., `UptimeInfoBox`, `ConnectionInfoBox`).
4. Each InfoBox fetches its data (e.g., from `StatusRepository`), prepares it, and renders its own small HTML partial (`Resources/Private/Templates/InfoBox/Default.html`).
5. The controller receives a single string of pre-rendered HTML and assigns it to the main module template (`Resources/Private/Templates/Status/Index.html`).
1. `StatusController` has `iterable $infoBoxes` (tagged with `mysqlreport.infobox.status`) and the `RenderInfoBoxFactory` injected via constructor autowiring.
2. The `indexAction` calls `$this->renderInfoBoxFactory->render($this->infoBoxes)`.
3. Inside the factory, each InfoBox's `TITLE` constant and `getBody()` content are extracted and assigned to `Resources/Private/Templates/InfoBox/Default.html`.
4. The controller receives a single string of pre-rendered HTML and assigns it to the main module template (`Resources/Private/Templates/Status/Index.html`).
3 changes: 2 additions & 1 deletion .github/workflows/typo3_13.yml → .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ jobs:
php:
- '8.2'
- '8.3'
- '8.4'

steps:
- name: 'Checkout'
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: 'Lint PHP'
run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s lint
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
/.ddev
/.Build
/composer.lock
/packages/
/scratch/
*GENERATED*
/Build/testing-docker/.env
/.php-cs-fixer.cache
Expand Down
55 changes: 22 additions & 33 deletions Build/Scripts/runTests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ handleDbmsOptions() {
echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
exit 1
fi
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.11"
if ! [[ ${DBMS_VERSION} =~ ^(10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.5"
if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then
echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2
echo >&2
echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2
Expand Down Expand Up @@ -125,7 +125,7 @@ handleDbmsOptions() {
loadHelp() {
# Load help text into $HELP
read -r -d '' HELP <<EOF
EXT:examples test runner. Check code styles, lint PHP files and some other details.
EXT:mysqlreport test runner. Check code styles, lint PHP files and some other details.

Usage: $0 [options] [file]

Expand All @@ -139,9 +139,9 @@ Options:
- composer: "composer" with all remaining arguments dispatched.
- composerNormalize: "composer normalize"
- composerUpdate: "composer update", handy if host has no PHP
- composerUpdateRector: "composer update", for rector subdirectory
- composerValidate: "composer validate"
- functional: PHP functional tests
- functionalUpdateBaseline: functional tests with UPDATE_BASELINE=1
- lint: PHP linting
- phpstan: PHPStan static analysis
- phpstanBaseline: Generate PHPStan baseline
Expand Down Expand Up @@ -202,9 +202,10 @@ Options:
- 15 maintained until 2027-11-11
- 16 maintained until 2028-11-09

-p <8.2|8.3>
-p <8.1|8.2|8.3>
Specifies the PHP minor version to be used
- 8.2: (default) use PHP 8.2
- 8.1: use PHP 8.1
- 8.2: use PHP 8.2
- 8.3: use PHP 8.3

-x
Expand Down Expand Up @@ -256,7 +257,7 @@ DBMS_VERSION=""
PHP_VERSION="8.2"
PHP_XDEBUG_ON=0
PHP_XDEBUG_PORT=9003
CGLCHECK_DRY_RUN=0
DRY_RUN=0
CI_PARAMS="${CI_PARAMS:-}"
DOCS_PARAMS="${DOCS_PARAMS:=--pull always}"
CONTAINER_BIN=""
Expand Down Expand Up @@ -290,7 +291,7 @@ while getopts "a:b:d:i:s:p:xy:nhu" OPT; do
;;
p)
PHP_VERSION=${OPTARG}
if ! [[ ${PHP_VERSION} =~ ^(8.2|8.3)$ ]]; then
if ! [[ ${PHP_VERSION} =~ ^(8.1|8.2|8.3|8.4)$ ]]; then
INVALID_OPTIONS+=("p ${OPTARG}")
fi
;;
Expand All @@ -301,7 +302,7 @@ while getopts "a:b:d:i:s:p:xy:nhu" OPT; do
PHP_XDEBUG_PORT=${OPTARG}
;;
n)
CGLCHECK_DRY_RUN=1
DRY_RUN=1
;;
h)
loadHelp
Expand Down Expand Up @@ -384,7 +385,7 @@ IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest"
shift $((OPTIND - 1))

SUFFIX=$(echo $RANDOM)
NETWORK="t3docsexamples-${SUFFIX}"
NETWORK="t3mysqlreport-${SUFFIX}"
${CONTAINER_BIN} network create ${NETWORK} >/dev/null

if [ ${CONTAINER_BIN} = "docker" ]; then
Expand All @@ -409,11 +410,11 @@ fi
# Suite execution
case ${TEST_SUITE} in
cgl)
if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --dry-run --diff --config=Build/cgl/.php-cs-fixer.dist.php --using-cache=no ."
else
COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v --config=Build/cgl/.php-cs-fixer.dist.php --using-cache=no ."
DRY_RUN_OPTIONS=''
if [ "${DRY_RUN}" -eq 1 ]; then
DRY_RUN_OPTIONS='--dry-run --diff'
fi
COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v ${DRY_RUN_OPTIONS} --config=Build/cgl/config.php --using-cache=no"
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name cgl-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
SUITE_EXIT_CODE=$?
;;
Expand All @@ -433,7 +434,7 @@ case ${TEST_SUITE} in
SUITE_EXIT_CODE=$?
;;
composerNormalize)
if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
if [ "${DRY_RUN}" -eq 1 ]; then
COMMAND=(composer normalize -n)
else
COMMAND=(composer normalize)
Expand All @@ -442,7 +443,7 @@ case ${TEST_SUITE} in
SUITE_EXIT_CODE=$?
;;
composerUpdate)
rm -rf .Build/bin/ .Build/typo3 .Build/vendor .Build/Web ./composer.lock
rm -rf .Build/bin .Build/typo3 .Build/vendor ./composer.lock
cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig
if [ -f "${ROOT_DIR}/composer.json.testing" ]; then
cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.orig
Expand All @@ -453,18 +454,6 @@ case ${TEST_SUITE} in
cp ${ROOT_DIR}/composer.json ${ROOT_DIR}/composer.json.testing
mv ${ROOT_DIR}/composer.json.orig ${ROOT_DIR}/composer.json
;;
composerUpdateRector)
rm -rf Build/rector/.Build/bin/ Build/rector/.Build/vendor Build/rector/composer.lock
cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.orig
if [ -f "${ROOT_DIR}/Build/rector/composer.json.testing" ]; then
cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.orig
fi
COMMAND=(composer require --working-dir=${ROOT_DIR}/Build/rector --no-ansi --no-interaction --no-progress)
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
SUITE_EXIT_CODE=$?
cp ${ROOT_DIR}/Build/rector/composer.json ${ROOT_DIR}/Build/rector/composer.json.testing
mv ${ROOT_DIR}/Build/rector/composer.json.orig ${ROOT_DIR}/Build/rector/composer.json
;;
composerValidate)
COMMAND=(composer validate "$@")
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
Expand Down Expand Up @@ -522,12 +511,12 @@ case ${TEST_SUITE} in
SUITE_EXIT_CODE=$?
;;
rector)
if [ "${CGLCHECK_DRY_RUN}" -eq 1 ]; then
COMMAND=(php -dxdebug.mode=off Build/rector/.Build/bin/rector -n --config=Build/rector/rector.php --clear-cache "$@")
else
COMMAND=(php -dxdebug.mode=off Build/rector/.Build/bin/rector --config=Build/rector/rector.php --clear-cache "$@")
DRY_RUN_OPTIONS=''
if [ "${DRY_RUN}" -eq 1 ]; then
DRY_RUN_OPTIONS='--dry-run'
fi
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name rector-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}"
COMMAND="php -dxdebug.mode=off .Build/bin/rector process ${DRY_RUN_OPTIONS} --config=Build/rector/rector.php --no-progress-bar --ansi"
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name rector-${SUFFIX} -e COMPOSER_CACHE_DIR=.Build/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}"
SUITE_EXIT_CODE=$?
;;
renderDocumentation)
Expand Down
81 changes: 0 additions & 81 deletions Build/cgl/.php-cs-fixer.dist.php

This file was deleted.

Loading