fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs#4525
Conversation
…ker installs On a fresh Docker install with an empty database, the ospos_sessions table doesn't exist yet. The CSRF filter triggers session initialization before the login/migration page can be reached. The existing code in Session.php, OSPOS.php, and MY_Migration.php catches DatabaseException, but the MySQLi driver throws mysqli_sql_exception (which extends RuntimeException, not DatabaseException) when the table doesn't exist. This causes an unhandled exception resulting in HTTP 500. Fix: Change all three catch blocks from to so that mysqli_sql_exception and any other unexpected database errors are caught, allowing the app to fall back gracefully: - Session.php: Falls back to FileHandler so sessions work without DB - OSPOS.php: Falls back to empty settings so config loads work - MY_Migration.php: Falls back to version 0 / false so the migration check passes gracefully This allows the login page with migration UI to be served on first access, so the initial schema migration can run. Fixes #4524
There was a problem hiding this comment.
🧹 Nitpick comments (3)
app/Config/OSPOS.php (1)
43-52: Narrow the exception catch to avoid masking non-database errors.
catch (\Exception $e)is sufficient and will still handlemysqli_sql_exception(which inherits from RuntimeException → Exception). Using\Throwableis unnecessarily broad and could silenceTypeErrororValueErrorissues that indicate actual coding problems.♻️ Proposed change
- } catch (\Throwable $e) { + } catch (\Exception $e) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Config/OSPOS.php` around lines 43 - 52, The catch block currently uses catch (\Throwable $e) which is too broad; change it to catch (\Exception $e) in the same try/catch around the settings load so database-related errors (including mysqli_sql_exception) are still handled but programmer errors like TypeError/ValueError are not masked; keep the existing fallback assignment to $this->settings ('language','language_code','company') and the existing comment about migrations so behavior is unchanged except for narrowing the exception type.app/Libraries/MY_Migration.php (1)
46-49: Narrow these migration fallbacks to\Exception.Returning
0/falseon DB exceptions is appropriate for fresh installs, but catching\Throwablemasks all errors, including unrelated PHPErrors andTypeErrors in the migration code itself. In PHP 8.1+,mysqli_sql_exceptionextendsRuntimeExceptionwhich extendsException, socatch (\Exception $e)will still catch database exceptions while allowing programming errors to surface and fail the migration appropriately.♻️ Proposed narrowing
- } catch (\Throwable $e) { + } catch (\Exception $e) { // Database not available yet (e.g. fresh install before schema). // Catches mysqli_sql_exception which is not a DatabaseException. return 0; @@ - } catch (\Throwable $e) { + } catch (\Exception $e) { // Database not available yet (e.g. fresh install before schema). // Catches mysqli_sql_exception which is not a DatabaseException. }Also applies to: 80-83
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Libraries/MY_Migration.php` around lines 46 - 49, The catch blocks in MY_Migration that currently use catch(\Throwable $e) should be narrowed to catch(\Exception $e) so programming Errors/TypeErrors are not swallowed; update the two occurrences (the block returning 0/false for DB-not-ready fallbacks and the other similar fallback around lines 80–83) to catch \Exception instead, preserving the existing return 0/false behavior and logging if present so database-related exceptions (like mysqli_sql_exception) are still handled but actual PHP errors surface.app/Config/Session.php (1)
141-147: Prefer catching\Exceptionto avoid masking unrelated PHP errors.In PHP 8.2+,
mysqli_sql_exceptionextendsRuntimeExceptionwhich extendsException, socatch (\Exception $e)will still catch all database exceptions. However,catch (\Throwable $e)unnecessarily catchesErrorand its subclasses (such asTypeError,ParseError), which could hide legitimate PHP errors. Narrow the catch to\Exceptionto be explicit about what this code handles.♻️ Proposed narrowing
- } catch (\Throwable $e) { + } catch (\Exception $e) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Config/Session.php` around lines 141 - 147, The catch block in the Session class currently catches \Throwable which can mask PHP Engine errors; update the catch to catch \Exception instead so only exceptions are handled. In the catch that assigns $this->driver = FileHandler::class and $this->savePath = WRITEPATH . 'session' (inside the Session class initialization where the DB-backed session setup occurs), replace catch (\Throwable $e) with catch (\Exception $e) and keep the existing fallback logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@app/Config/OSPOS.php`:
- Around line 43-52: The catch block currently uses catch (\Throwable $e) which
is too broad; change it to catch (\Exception $e) in the same try/catch around
the settings load so database-related errors (including mysqli_sql_exception)
are still handled but programmer errors like TypeError/ValueError are not
masked; keep the existing fallback assignment to $this->settings
('language','language_code','company') and the existing comment about migrations
so behavior is unchanged except for narrowing the exception type.
In `@app/Config/Session.php`:
- Around line 141-147: The catch block in the Session class currently catches
\Throwable which can mask PHP Engine errors; update the catch to catch
\Exception instead so only exceptions are handled. In the catch that assigns
$this->driver = FileHandler::class and $this->savePath = WRITEPATH . 'session'
(inside the Session class initialization where the DB-backed session setup
occurs), replace catch (\Throwable $e) with catch (\Exception $e) and keep the
existing fallback logic unchanged.
In `@app/Libraries/MY_Migration.php`:
- Around line 46-49: The catch blocks in MY_Migration that currently use
catch(\Throwable $e) should be narrowed to catch(\Exception $e) so programming
Errors/TypeErrors are not swallowed; update the two occurrences (the block
returning 0/false for DB-not-ready fallbacks and the other similar fallback
around lines 80–83) to catch \Exception instead, preserving the existing return
0/false behavior and logging if present so database-related exceptions (like
mysqli_sql_exception) are still handled but actual PHP errors surface.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 340cb147-7aab-407e-86a5-3fde499deb4f
📒 Files selected for processing (3)
app/Config/OSPOS.phpapp/Config/Session.phpapp/Libraries/MY_Migration.php
…nused JOIN 1. Narrow catch blocks from Throwable to Exception per review feedback. mysqli_sql_exception extends RuntimeException which extends Exception, so Exception is sufficient and doesn't mask programming errors like TypeError or ValueError that should surface. 2. Remove unused suppliers JOIN in get_item_id() per review feedback. The method selects nothing from the suppliers table and the WHERE clause doesn't reference it, making the LEFT JOIN pure overhead on a hot lookup path (called via sale_lib->get_item_id()).

Summary
mysqli_sql_exception(not justDatabaseException) in three database fallback handlerssuppliersLEFT JOIN inItem::get_item_id()per review feedback\Throwableto\Exceptionper review feedbackProblem
On a fresh
docker compose up, the database starts completely empty (no tables). The first HTTP request triggers this chain:service('security')→ callsservice('session')DatabaseHandler::read()onospos_sessionstablemysqli_sql_exception: Table 'ospos.ospos_sessions' doesn't existThe existing fallback code from PR #4467 catches
DatabaseException, but MySQLi throwsmysqli_sql_exceptionwhich extendsRuntimeException, notDatabaseException. So the catch block never fires and the exception goes unhandled.Changes
1. Session.php — Catch
\Exceptioninstead ofDatabaseExceptionWhen sessions can't use the database (no tables), fall back to
FileHandlerso the app can still serve the login/migration page.2. OSPOS.php — Catch
\Exceptioninstead ofDatabaseExceptionWhen
app_configtable doesn't exist, fall back to empty default settings.3. MY_Migration.php — Catch
\Exceptionin two placesWhen checking current schema version on an empty database, return
0/falsegracefully instead of crashing.4. Item.php — Remove unused
suppliersLEFT JOINThe
get_item_id()method LEFT JOINssuppliersbut selects nothing from it and the WHERE clause doesn't reference it. This is pure overhead on a hot lookup path (called viasale_lib->get_item_id()). Per CodeRabbit review on PR #4520.Why
\Exceptioninstead of\ThrowablePer review feedback from CodeRabbit:
mysqli_sql_exceptionextendsRuntimeException → Exception, so\Exceptionis sufficient. Using\Throwablewould unnecessarily mask programming errors likeTypeErrorandValueErrorthat should surface during debugging.Testing
Reproduced the error with a fresh
docker compose up:Verified that the Session fallback to
FileHandlercorrectly triggers with the\Exceptioncatch, allowing the login page to render (HTTP 200).Note: The current
jekkos/opensourcepos:masterDocker image doesn't include the PR #4467 Session fallback at all (127 lines vs 150), so it needs to be rebuilt. This fix will take effect once the image is updated.Related
DatabaseExceptioncatch but didn't account formysqli_sql_exceptionsuppliersJOIN removal)