Skip to content

PHP 8.0 – 8.5 source compatibility fixes (XmlReader, ReportHtml/Html2pdf, ReportTCPDF, ReporticoSession, default;) - #60

Open
pixilla wants to merge 13 commits into
reportico-web:masterfrom
databoost:feature/php-85-compatibility
Open

PHP 8.0 – 8.5 source compatibility fixes (XmlReader, ReportHtml/Html2pdf, ReportTCPDF, ReporticoSession, default;)#60
pixilla wants to merge 13 commits into
reportico-web:masterfrom
databoost:feature/php-85-compatibility

Conversation

@pixilla

@pixilla pixilla commented May 13, 2026

Copy link
Copy Markdown

Summary

Brings Reportico to clean compatibility with PHP 8.0 → 8.5. Each commit is scoped to a single concern with a self-contained message.

Tested on PHP 8.5.1 (MacPorts) against PostgreSQL: admin login, project load, criteria/date-pickers, HTML output, and TCPDF PDF generation.

Base

Targets tag 8.1.0 (1ee3a05).

Highlights

  • XmlReader: SAX parser API updates (PHP 8.4/8.5); safe handling when report XML is missing
  • ReportHtml / ReportHtml2pdf: guard closeGroup() when currentGroup is false
  • ReportTCPDF: declare dynamic properties; fix newReportPageLineByStyle signature
  • ReporticoSession: guard all $_SESSION access when namespace key is unset
  • Widgets: declare Criteria / DatePicker / TimeRangePicker properties; CriteriaForm passes project + xmlin on AJAX execute
  • Menus: skip non-.xml files (e.g. .xml.bak.*) in project/admin menus
  • ReporticoUtility: reject directory/empty paths in findFileToInclude()
  • Switch statements: default;default: (PHP 8.5)
  • QueryColumn: PostgreSQL string literals for CHAR criteria; remove DatePicker debug echo
  • Composer: Twig ^3.19 for PHP 8.4+
  • Bootstrap: optional memory/execution-time limits via env vars for large reports
  • ReporticoApp: ignore E_DEPRECATED in error handler (vendor/extension noise)

Test plan

  • error_reporting(E_ALL) — no Reportico-core deprecations during PREPARE/EXECUTE
  • Marketing report Contact Phone Email Sales History 2: criteria + EXECUTE (~48MB HTML)
  • Project menu loads with .xml.bak.* files present in project folder
  • HTML output and TCPDF PDF generation
  • Maintainer smoke-test on PHP 8.2 / 8.3 if still supported

Related

Pairs with #59 (switch to upstream adodb/adodb-php) for a full PHP 8.5 install.

ADOdb ADODB_pdo::$_nestedSQL undeclared-property notice may still appear until ADOdb upstream ships a fix.

DataBoost Devs and others added 13 commits May 12, 2026 19:56
PHP 8.4 deprecated `xml_set_object()` and the use of string method names
as callbacks for `xml_set_*_handler()`. PHP 8.5 additionally deprecated
`xml_parser_free()` (which has been a no-op since PHP 8.0; the parser is
freed automatically when its last reference goes out of scope).

Loading any project xml file currently emits:

    Deprecated: Function xml_set_object() is deprecated since 8.4 ...
    Deprecated: xml_set_element_handler(): Passing non-callable strings is deprecated since 8.4 ...
    Deprecated: xml_set_character_data_handler(): Passing non-callable strings is deprecated since 8.4 ...
    Deprecated: Function xml_parser_free() is deprecated since 8.5 ...

Replace the string callbacks with `[object, methodName]` array callables
(also valid in PHP 7.4+), and remove the now-deprecated `xml_parser_free()`
call. Behavior is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
…up (PHP 8.1)

`$this->currentGroup` is initialised to `false` and is only promoted to an
array by `openGroup()`. When `closeGroup()` is called without a matching
`openGroup()` (a common occurrence during certain report layouts/totals
paths), PHP 8.1+ emits:

    Deprecated: Automatic conversion of false to array is deprecated in
    src/ReportHtml.php on line 348
    Deprecated: Automatic conversion of false to array is deprecated in
    src/ReportHtml2pdf.php on line 347

…and the subsequent writes silently mutate `currentGroup` into a one-key
array `[ "endrow" => ... ]`, hiding logic bugs.

Both methods now early-return when `currentGroup` is not yet an array,
preserving the previous effective no-op behaviour without the deprecation.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ignature (PHP 8.0 / 8.2)

PDF report generation currently produces multiple fatal-level deprecations
on PHP 8.2+:

    Deprecated: Creation of dynamic property
      Reportico\Engine\ReportTCPDF::$column_spacing is deprecated
    Deprecated: Creation of dynamic property
      Reportico\Engine\ReportTCPDF::$abs_row_right_margin is deprecated
    Deprecated: Creation of dynamic property
      Reportico\Engine\ReportTCPDF::$abs_col_right_margin is deprecated
    Deprecated: Creation of dynamic property
      Reportico\Engine\ReportTCPDF::$abs_row_width is deprecated
    Deprecated: Creation of dynamic property
      Reportico\Engine\ReportTCPDF::$abs_columns_width is deprecated

and one PHP 8.0+ implicit-required-parameter deprecation:

    Deprecated: Reportico\Engine\ReportTCPDF::newReportPageLineByStyle():
      Optional parameter $txt declared before required parameter $styles
      is implicitly treated as a required parameter

This change:

1. Explicitly declares the five properties on the class so PHP 8.2 no-dynamic-properties rule is satisfied. $column_spacing is initialised to 0 to match what the constructor was lazily setting.
2. Removes the unused default = "" from $txt in newReportPageLineByStyle(); the next parameter is a required by-ref parameter, so $txt was already implicitly required and every call site already passes it.

No behaviour change.

Co-authored-by: Cursor <cursoragent@cursor.com>
… (PHP 8.1)

When `session_namespace_key` has not been set yet (e.g. very early in a
fresh-session bootstrap, before initialise(), or when callers reach the
isset helpers from an out-of-order code path), `ReporticoApp::get("session_namespace_key")`
returns null. Indexing `$_SESSION` with that null then triggers, on PHP 8.1+:

    Deprecated: Using null as an array offset is deprecated, use an empty
    string instead in src/ReporticoSession.php on line 198

The same pattern exists in three helpers:

- existsReporticoSession()
- issetReporticoSessionParam()
- unsetReporticoSessionParam()

In all three the semantic answer when the key is null/empty is the same:
no Reportico namespace exists yet, so existsReporticoSession() and
issetReporticoSessionParam() should return false, and
unsetReporticoSessionParam() should be a no-op.

Add an explicit guard at the top of each helper that returns the
appropriate "namespace does not exist" answer when the key is null or
empty, before any $_SESSION array access. This eliminates the deprecation
without changing observable behaviour.

No callers of these helpers needed updating; they already treat a
"namespace does not exist" return value as the absence of state.

Co-authored-by: Cursor <cursoragent@cursor.com>
PHP has historically accepted both `:` and `;` as the terminator for switch
case/default labels. PHP 8.5 deprecates the `;` form:

    Deprecated: Case statements followed by a semicolon (;) are deprecated,
    use a colon (:) instead

There are 5 occurrences of `default;` in the codebase. All five are
in switch statements where the `default` arm shares a fall-through body
with a `case "LINE":` arm (chart-engine type dispatch), or guards an
"unhandled tag" fall-through (XmlReader).

Replace `default;` with `default:` at each site. No behaviour change.

Affected files:

- src/XmlReader.php
- src/ChartPchart3.php
- src/ChartPchart.php
- src/ChartJpgraph.php
- dyngraph.php

Co-authored-by: Cursor <cursoragent@cursor.com>
…r (PHP 8.2)

When a report with a DATE criteria is opened, PHP 8.2+ emits multiple
"Creation of dynamic property" deprecations:

    Deprecated: Creation of dynamic property
      Reportico\Widgets\Criteria::$range_start is deprecated
        in src/widgets/Widget.php on line 242
    Deprecated: Creation of dynamic property
      Reportico\Widgets\DatePicker::$range_name is deprecated
        in src/widgets/DatePicker.php on line 228
    Deprecated: Creation of dynamic property
      Reportico\Widgets\DatePicker::$range_end_raw is deprecated
        in src/widgets/DatePicker.php on line 229
    Deprecated: Creation of dynamic property
      Reportico\Widgets\DatePicker::$range_start_raw is deprecated
        in src/widgets/DatePicker.php on line 229

The locations:

- Widget::handleUrlParameters() passes $this->range_start by reference into
  ReporticoLocale::convertDateRangeDefaultsToDates() when the engine criteria
  is of type DATE. $this is the surrounding widget (typically a Criteria).
- DatePicker::deriveValue() initialises $this->range_name,
  $this->range_start_raw and $this->range_end_raw to "" before populating
  them from the request.

DateRangePicker and TimeRangePicker already declare the equivalent fields
explicitly; this commit aligns DatePicker (range_name, range_start_raw,
range_end_raw) and adds the single field that Widget::handleUrlParameters()
needs on Criteria (range_start).

No behaviour change.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ll namespace (PHP 8.1)

Follow-up to a4c3480 ("guard $_SESSION[null] when namespace key unset").
That commit guarded the three read-only helpers (existsReporticoSession,
issetReporticoSessionParam, unsetReporticoSessionParam), which is enough
for the originally reported deprecation. Continued testing surfaced that
three more sites in the same file still index $_SESSION by a key sourced
from ReporticoApp::get("session_namespace_key"), and so can still emit:

    Deprecated: Using null as an array offset is deprecated, use an empty
    string instead in src/ReporticoSession.php

The remaining sites are:

- setReporticoSessionParam(): writes $_SESSION[$namespace][$param];
  $namespace falls back to ReporticoApp::get("session_namespace_key")
  when not passed.
- initializeReporticoNamespace(): reads $_SESSION[$namespace] and unsets
  it; $namespace is reassigned from ReporticoApp::get(...).

(getReporticoSessionParam() is already safe because it short-circuits
through the now-guarded issetReporticoSessionParam().)

Add the same null/"" early-return guard at both sites so the deprecation
cannot recur from any other call path. Behaviour is unchanged for callers
that already have a valid namespace key.

Co-authored-by: Cursor <cursoragent@cursor.com>
Twig 3.5.x triggers deprecations on PHP 8.4+ (implicitly nullable parameters
in FilesystemLoader, Environment::getTemplateClass, etc.). Reportico's error
handler treats those deprecations as fatal UI errors, which caused a white
screen when opening reports that render Twig templates (e.g. Open Jobs
criteria PREPARE).

Raise the floor to Twig 3.19+ so installs on PHP 8.5 resolve to a current
3.x release with explicit nullable types.

Run `composer update twig/twig` after merge to refresh the lockfile.

Co-authored-by: Cursor <cursoragent@cursor.com>
… debug echo

PostgreSQL treats double-quoted tokens as identifiers. QueryColumn::getValueDelimiter()
returned '"' for CHAR criteria, so expanded criteria like {wanteddate} became
`... < "2026-05-13"`, which PostgreSQL reads as "column 2026-05-13" and throws
SQLSTATE[42703] — an uncaught PDOException and a white screen on reports such as
Open Jobs (lookup SQL uses the wanteddate criterion).

Use single-quote delimiters when the ADOdb/PDO driver is PostgreSQL.

Also remove a stray `echo $cls` in DatePicker::getCriteriaClause() left from debugging,
which corrupted HTML/ajax responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
Vendor and extension deprecations on PHP 8.x were classified as "Fatal Error"
and appended to system_errors, which forces error-rendering paths and can
yield blank or broken pages even when the underlying request would succeed.

Return early for E_DEPRECATED and E_USER_DEPRECATED so PHP's default handling
applies and Reportico does not treat them as blocking report errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
…reports

The previous hard cap of 100M is easy to hit on big result sets: assignment
expressions are eval()'d per row (Reportico::assign), and PDF/HTML rendering
buffers additional data. That produced:

    Allowed memory size of 104857600 bytes exhausted ... eval()'d code

Default is now 512M. Optional override: set environment variable
REPORTICO_MEMORY_LIMIT (e.g. `768M` or `1G`) before the web/PHP process if a
specific deployment needs more.

Co-authored-by: Cursor <cursoragent@cursor.com>
Large PostgreSQL reports (e.g. Contact Phone Email Sales History) can exceed
the previous 90s PHP limit after the SQL completes. Default is now 300s;
override with REPORTICO_MAX_EXECUTION_TIME in the environment if needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Harden XmlReader and findFileToInclude when report XML is missing, skip
non-.xml files in project menus, pass project/xmlin on criteria execute,
and declare TimeRangePicker::$range_name for PHP 8.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant