@@ -19,6 +19,8 @@ final class Session
1919
2020 public const string CSRF_TOKEN_KEY = '#csrf_token ' ;
2121
22+ public const string DEFAULT_ERROR_BAG = 'default ' ;
23+
2224 private array $ expiredKeys = [];
2325
2426 private SessionManager $ manager {
@@ -84,14 +86,28 @@ public function get(string $key, mixed $default = null): mixed
8486 }
8587
8688 /** @return \Tempest\Validation\Rule[] */
87- public function getErrorsFor (string $ name ): array
89+ public function getErrorsFor (string $ name, ? string $ bagName = null ): array
8890 {
89- return $ this ->get (self ::VALIDATION_ERRORS )[$ name ] ?? [];
91+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
92+ $ errors = $ this ->get (self ::VALIDATION_ERRORS );
93+
94+ if ($ errors !== null && ! $ this ->isBaggedStructure ($ errors )) {
95+ return $ bagName === self ::DEFAULT_ERROR_BAG ? ($ errors [$ name ] ?? []) : [];
96+ }
97+
98+ return $ errors [$ bagName ][$ name ] ?? [];
9099 }
91100
92- public function getOriginalValueFor (string $ name , mixed $ default = '' ): mixed
101+ public function getOriginalValueFor (string $ name , mixed $ default = '' , ? string $ bagName = null ): mixed
93102 {
94- return $ this ->get (self ::ORIGINAL_VALUES )[$ name ] ?? $ default ;
103+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
104+ $ values = $ this ->get (self ::ORIGINAL_VALUES );
105+
106+ if ($ values !== null && ! $ this ->isBaggedStructure ($ values )) {
107+ return $ bagName === self ::DEFAULT_ERROR_BAG ? ($ values [$ name ] ?? $ default ) : $ default ;
108+ }
109+
110+ return $ values [$ bagName ][$ name ] ?? $ default ;
95111 }
96112
97113 public function getPreviousUrl (): string
@@ -142,4 +158,96 @@ public function cleanup(): void
142158 $ this ->manager ->remove ($ this ->id , $ key );
143159 }
144160 }
161+
162+ public function flashValidationErrors (array $ errors , ?string $ bagName = null ): void
163+ {
164+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
165+ $ currentErrors = $ this ->get (self ::VALIDATION_ERRORS ) ?? [];
166+
167+ if (! $ this ->isBaggedStructure ($ currentErrors )) {
168+ $ currentErrors = empty ($ currentErrors ) ? [] : [self ::DEFAULT_ERROR_BAG => $ currentErrors ];
169+ }
170+
171+ $ currentErrors [$ bagName ] = $ errors ;
172+ $ this ->flash (self ::VALIDATION_ERRORS , $ currentErrors );
173+ }
174+
175+ public function flashOriginalValues (array $ values , ?string $ bagName = null ): void
176+ {
177+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
178+ $ currentValues = $ this ->get (self ::ORIGINAL_VALUES ) ?? [];
179+
180+ if (! $ this ->isBaggedStructure ($ currentValues )) {
181+ $ currentValues = empty ($ currentValues ) ? [] : [self ::DEFAULT_ERROR_BAG => $ currentValues ];
182+ }
183+
184+ $ currentValues [$ bagName ] = $ values ;
185+ $ this ->flash (self ::ORIGINAL_VALUES , $ currentValues );
186+ }
187+
188+ public function getAllErrors (?string $ bagName = null ): array
189+ {
190+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
191+ $ errors = $ this ->get (self ::VALIDATION_ERRORS );
192+
193+ if ($ errors !== null && ! $ this ->isBaggedStructure ($ errors )) {
194+ return $ bagName === self ::DEFAULT_ERROR_BAG ? $ errors : [];
195+ }
196+
197+ return $ errors [$ bagName ] ?? [];
198+ }
199+
200+ public function clearErrors (?string $ bagName = null ): void
201+ {
202+ $ bagName ??= self ::DEFAULT_ERROR_BAG ;
203+ $ errors = $ this ->get (self ::VALIDATION_ERRORS ) ?? [];
204+
205+ if ($ this ->isBaggedStructure ($ errors )) {
206+ unset($ errors [$ bagName ]);
207+ if (empty ($ errors )) {
208+ $ this ->remove (self ::VALIDATION_ERRORS );
209+ } else {
210+ $ this ->set (self ::VALIDATION_ERRORS , $ errors );
211+ }
212+ } elseif ($ bagName === self ::DEFAULT_ERROR_BAG ) {
213+ $ this ->remove (self ::VALIDATION_ERRORS );
214+ }
215+ }
216+
217+ private function isBaggedStructure (array $ data ): bool
218+ {
219+ if (empty ($ data )) {
220+ return false ; // Empty arrays are considered non-bagged for backward compatibility
221+ }
222+
223+ // Check if this looks like a bagged structure:
224+ // - Bagged: ['default' => ['field' => [...]], 'other' => ['field' => [...]]]
225+ // - Old: ['field' => [...], 'field2' => [...]]
226+
227+ // A bagged structure should have at least one known bag name as key
228+ // or all values should be arrays of arrays (not arrays of objects)
229+ foreach ($ data as $ key => $ value ) {
230+ if ($ key === self ::DEFAULT_ERROR_BAG ) {
231+ return true ;
232+ }
233+
234+ // If value is not an array, it's definitely not bagged
235+ if (! is_array ($ value )) {
236+ return false ;
237+ }
238+
239+ // Check if the value contains objects (old structure for validation errors)
240+ // Old structure: ['field' => [Rule, Rule]]
241+ // New structure: ['bag' => ['field' => [Rule, Rule]]]
242+ foreach ($ value as $ item ) {
243+ if (is_object ($ item )) {
244+ // Contains objects directly, so it's the old structure
245+ return false ;
246+ }
247+ }
248+ }
249+
250+ // If all values are arrays of arrays (no objects), it's likely bagged
251+ return true ;
252+ }
145253}
0 commit comments