From c49b336c49b28e6944b83fdb24019533af32c09a Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:52:34 +0200 Subject: [PATCH 1/2] Detect recursion in scheme extension --- formwork/src/Schemes/Scheme.php | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/formwork/src/Schemes/Scheme.php b/formwork/src/Schemes/Scheme.php index 5d651f52c..396e7c30c 100644 --- a/formwork/src/Schemes/Scheme.php +++ b/formwork/src/Schemes/Scheme.php @@ -4,6 +4,7 @@ use Formwork\Data\Contracts\Arrayable; use Formwork\Data\Traits\DataArrayable; +use Formwork\Exceptions\RecursionException; use Formwork\Fields\FieldCollection; use Formwork\Fields\FieldFactory; use Formwork\Fields\Layout\Layout; @@ -28,6 +29,13 @@ class Scheme implements Arrayable */ protected array $data = []; + /** + * Scheme IDs currently being extended. + * + * @var array + */ + protected static array $extending = []; + /** * Scheme path */ @@ -54,6 +62,7 @@ class Scheme implements Arrayable * * @throws InvalidArgumentException If the extended scheme ID is invalid * @throws InvalidArgumentException If a scheme tries to extend itself + * @throws RecursionException If there is recursion in scheme extension */ public function __construct( protected string $id, @@ -65,7 +74,7 @@ public function __construct( $this->data = $data; if (isset($this->data['extend'])) { - $this->extend($this->schemes->get($this->data['extend'])); + $this->extend($this->data['extend']); } $this->options = new SchemeOptions($this->data['options'] ?? []); @@ -135,15 +144,31 @@ public function fields(): FieldCollection /** * Extend the scheme with another scheme * + * @param Scheme|string $scheme Scheme instance or scheme id to extend with + * * @throws InvalidArgumentException If the scheme tries to extend itself + * @throws RecursionException If there is recursion in scheme extension */ - public function extend(Scheme $scheme): void + public function extend(Scheme|string $scheme): void { - if ($scheme->id === $this->id) { + $id = $scheme instanceof Scheme ? $scheme->id : $scheme; + + if ($id === $this->id) { throw new InvalidArgumentException(sprintf('Scheme "%s" cannot be extended by itself', $this->id)); } - $this->extendWith($scheme->data); + if (isset(self::$extending[$this->id])) { + throw new RecursionException(sprintf('Recursion in the extension of the scheme "%s". Extension chain: "%s"', $this->id, implode('" > "', [...array_keys(self::$extending), $this->id]))); + } + + self::$extending[$this->id] = true; + + try { + $base = $scheme instanceof Scheme ? $scheme : $this->schemes->get($id); + $this->extendWith($base->data); + } finally { + unset(self::$extending[$this->id]); + } } /** From a12ddf4c79c71ab4a06fdd0c92cd5e591e52b987 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:02:38 +0200 Subject: [PATCH 2/2] Add missing exception from docblock --- formwork/src/Schemes/Scheme.php | 1 + 1 file changed, 1 insertion(+) diff --git a/formwork/src/Schemes/Scheme.php b/formwork/src/Schemes/Scheme.php index 396e7c30c..00441987f 100644 --- a/formwork/src/Schemes/Scheme.php +++ b/formwork/src/Schemes/Scheme.php @@ -147,6 +147,7 @@ public function fields(): FieldCollection * @param Scheme|string $scheme Scheme instance or scheme id to extend with * * @throws InvalidArgumentException If the scheme tries to extend itself + * @throws InvalidArgumentException If the scheme ID is invalid or not found * @throws RecursionException If there is recursion in scheme extension */ public function extend(Scheme|string $scheme): void