Lines 100.00% 13 / 13
Functions and Methods 100.00% 4 / 4
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
DetectsConnectionLoss 100.00% 13 / 13 100.00% 4 / 4 10 100.00% 1 / 1
 isStale 100.00% 1 / 1 100.00% 1 / 1 1
 markStale 100.00% 1 / 1 100.00% 1 / 1 1
 clearStale 100.00% 1 / 1 100.00% 1 / 1 1
 isConnectionLoss 100.00% 10 / 10 100.00% 1 / 1 7
1<?php
2
3declare(strict_types=1);
4
5namespace BlueprintAU\Radiant\Database\Concerns;
6
7/**
8 * Detects a connection whose transport has died, and tracks that state.
9 *
10 * Extracted from {@see \BlueprintAU\Radiant\Database\Connections\SqlConnection}
11 * so any connection backend can share the same failure semantics: a
12 * connection-loss error marks the instance stale, and a caching layer (e.g.
13 * {@see \BlueprintAU\Radiant\Database\DatabaseManager}) consults
14 * {@see isStale()} before handing the instance out again — one server
15 * restart no longer poisons the cached connection for the life of the
16 * process.
17 *
18 * The SQLSTATE handling is SQL-specific; a non-SQL backend using this trait
19 * would override {@see isConnectionLoss()} (or simply never call it, since
20 * nothing here auto-marks without a call site).
21 */
22trait DetectsConnectionLoss
23{
24    /**
25     * Whether a connection-loss error has marked this connection dead.
26     *
27     * @var bool
28     */
29    protected bool $stale = false;
30
31    /**
32     * Whether this connection has been marked dead by a connection-loss
33     * error and should be discarded by a caching layer.
34     *
35     * @return bool
36     */
37    public function isStale(): bool
38    {
39        return $this->stale;
40    }
41
42    /**
43     * Mark this connection dead after a connection-loss error.
44     *
45     * Public so a caching layer can also force-evict; the query paths set
46     * it automatically when they detect a connection loss.
47     */
48    public function markStale(): void
49    {
50        $this->stale = true;
51    }
52
53    /**
54     * Clear the stale flag — used after a successful reconnect so a rebuilt
55     * or recovered connection is served normally again.
56     */
57    public function clearStale(): void
58    {
59        $this->stale = false;
60    }
61
62    /**
63     * Whether a PDOException looks like a lost connection rather than a
64     * statement-level failure.
65     *
66     * @param  \PDOException  $e
67     * @return bool
68     */
69    protected function isConnectionLoss(\PDOException $e): bool
70    {
71        $sqlstate = (string) ($e->errorInfo[0] ?? $e->getCode());
72        if (in_array($sqlstate, ['08001', '08003', '08006', '08007', '08S01', '28000'], true)) {
73            return true;
74        }
75
76        if ($sqlstate === 'HY000') {
77            return str_contains($e->getMessage(), 'server has gone away')
78                || str_contains($e->getMessage(), 'Lost connection')
79                || str_contains($e->getMessage(), 'Error while sending')
80                || str_contains($e->getMessage(), 'broken pipe')
81                || str_contains($e->getMessage(), 'connection closed');
82        }
83
84        return false;
85    }
86}