diff --git a/resources/views/partials/alerts.blade.php b/resources/views/partials/alerts.blade.php
index d34eb8874..3b466dc08 100755
--- a/resources/views/partials/alerts.blade.php
+++ b/resources/views/partials/alerts.blade.php
@@ -1,8 +1,8 @@
-@if($error = session()->get('error'))
+@if($error = \Dcat\Admin\Support\SessionMessage::tryFrom(session()->get('error')))
- {{ class_basename($error->get('type')[0]) }}
- In {{ basename($error->get('file')[0]) }} line {{ $error->get('line')[0] }} :
+ {{ class_basename($errorType) }}
+ In {{ basename($errorFile) }} line {{ $errorLine }} :
{!! $error->first('message') !!}
diff --git a/resources/views/partials/toastr.blade.php b/resources/views/partials/toastr.blade.php
index e9512face..a30881b11 100644
--- a/resources/views/partials/toastr.blade.php
+++ b/resources/views/partials/toastr.blade.php
@@ -1,9 +1,3 @@
-@if(Session::has('dcat-admin-toastr'))
- @php
- $toastr = Session::get('dcat-admin-toastr');
- $type = $toastr->get('type')[0] ?? 'success';
- $message = $toastr->get('message')[0] ?? '';
- $options = admin_javascript_json($toastr->get('options', []));
- @endphp
-
-@endif
\ No newline at end of file
+@if($toastr = \Dcat\Admin\Support\SessionMessage::tryFrom(Session::get('dcat-admin-toastr')))
+
+@endif
diff --git a/src/Http/Controllers/ScaffoldController.php b/src/Http/Controllers/ScaffoldController.php
index 312affa31..c4a97cce6 100644
--- a/src/Http/Controllers/ScaffoldController.php
+++ b/src/Http/Controllers/ScaffoldController.php
@@ -11,13 +11,13 @@
use Dcat\Admin\Scaffold\ModelCreator;
use Dcat\Admin\Scaffold\RepositoryCreator;
use Dcat\Admin\Support\Helper;
+use Dcat\Admin\Support\SessionMessage;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\URL;
-use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
class ScaffoldController extends Controller
@@ -275,10 +275,7 @@ protected function getDatabaseColumns($db = null, $tb = null)
protected function backWithException(\Exception $exception)
{
- $error = new MessageBag([
- 'title' => 'Error',
- 'message' => $exception->getMessage(),
- ]);
+ $error = SessionMessage::make('Error', $exception->getMessage());
return redirect()->refresh()->withInput()->with(compact('error'));
}
@@ -293,10 +290,7 @@ protected function backWithSuccess($paths, $message)
$messages[] = "
$message";
- $success = new MessageBag([
- 'title' => 'Success',
- 'message' => implode('
', $messages),
- ]);
+ $success = SessionMessage::make('Success', implode('
', $messages));
return redirect()->refresh()->with(compact('success'));
}
diff --git a/src/Support/SessionMessage.php b/src/Support/SessionMessage.php
new file mode 100644
index 000000000..e6a3f29e0
--- /dev/null
+++ b/src/Support/SessionMessage.php
@@ -0,0 +1,93 @@
+with() 序列化后变为数组的问题
+ * 2. session.serialization = json 时,protected 属性不会被 json_encode 包含的问题
+ */
+final class SessionMessage implements \JsonSerializable
+{
+ private const JSON_CLASS_KEY = '__dcat_class';
+
+ private const JSON_CLASS_VALUE = self::class;
+
+ private const TOASTR_TYPES = ['success', 'error', 'warning', 'info'];
+
+ public function __construct(
+ protected string $title = '',
+ protected string $message = '',
+ protected array $options = [],
+ ) {
+ }
+
+ public static function make(string $title, string $message = '', array $options = []): self
+ {
+ return new self($title, $message, $options);
+ }
+
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ public function getToastrType(): string
+ {
+ return in_array($this->title, self::TOASTR_TYPES, true) ? $this->title : 'info';
+ }
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * 支持 JSON session 序列化(session.serialization = json).
+ *
+ * 将对象编码为包含类型标识符的数组,确保 protected 属性不丢失。
+ */
+ public function jsonSerialize(): mixed
+ {
+ return [
+ self::JSON_CLASS_KEY => self::JSON_CLASS_VALUE,
+ 'title' => $this->title,
+ 'message' => $this->message,
+ 'options' => $this->options,
+ ];
+ }
+
+ /**
+ * 从 session 中读取的值尝试构造实例.
+ *
+ * 兼容两种情况:
+ * - PHP 序列化(session.serialization = php):值已经是 SessionMessage 对象
+ * - JSON 序列化(session.serialization = json):值是带类型标识的数组
+ */
+ public static function tryFrom(mixed $value): ?self
+ {
+ if ($value instanceof self) {
+ return $value;
+ }
+
+ if (
+ is_array($value)
+ && ($value[self::JSON_CLASS_KEY] ?? null) === self::JSON_CLASS_VALUE
+ ) {
+ return new self(
+ is_string($value['title'] ?? null) ? $value['title'] : '',
+ is_string($value['message'] ?? null) ? $value['message'] : '',
+ is_array($value['options'] ?? null) ? $value['options'] : [],
+ );
+ }
+
+ return null;
+ }
+}
diff --git a/src/Support/helpers.php b/src/Support/helpers.php
index 15b686789..6e5a2da2b 100755
--- a/src/Support/helpers.php
+++ b/src/Support/helpers.php
@@ -2,10 +2,10 @@
use Dcat\Admin\Admin;
use Dcat\Admin\Support\Helper;
+use Dcat\Admin\Support\SessionMessage;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
-use Illuminate\Support\MessageBag;
use Symfony\Component\HttpFoundation\Response;
if (! function_exists('admin_setting')) {
@@ -298,7 +298,7 @@ function admin_base_path($path = '')
if (! function_exists('admin_toastr')) {
/**
- * Flash a toastr message bag to session.
+ * Flash a toastr message to session.
*
* @param string $message
* @param string $type
@@ -306,7 +306,7 @@ function admin_base_path($path = '')
*/
function admin_toastr($message = '', $type = 'success', $options = [])
{
- $toastr = new MessageBag(get_defined_vars());
+ $toastr = SessionMessage::make($type, $message, $options);
session()->flash('dcat-admin-toastr', $toastr);
}
@@ -353,7 +353,7 @@ function admin_warning($title, $message = '')
if (! function_exists('admin_info')) {
/**
- * Flash a message bag to session.
+ * Flash a message to session.
*
* @param string $title
* @param string $message
@@ -361,7 +361,7 @@ function admin_warning($title, $message = '')
*/
function admin_info($title, $message = '', $type = 'info')
{
- $message = new MessageBag(get_defined_vars());
+ $message = SessionMessage::make($title, $message);
session()->flash($type, $message);
}
diff --git a/tests/Unit/SessionMessageTest.php b/tests/Unit/SessionMessageTest.php
new file mode 100644
index 000000000..066a04aac
--- /dev/null
+++ b/tests/Unit/SessionMessageTest.php
@@ -0,0 +1,192 @@
+ 3000]);
+
+ $this->assertSame('success', $msg->getTitle());
+ $this->assertSame('hello', $msg->getMessage());
+ $this->assertSame(['timeOut' => 3000], $msg->getOptions());
+ }
+
+ public function test_make_defaults_message_and_options(): void
+ {
+ $msg = SessionMessage::make('info');
+
+ $this->assertSame('', $msg->getMessage());
+ $this->assertSame([], $msg->getOptions());
+ }
+
+ // -----------------------------------------------------------------------
+ // getToastrType — whitelist
+ // -----------------------------------------------------------------------
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('validToastrTypes')]
+ public function test_get_toastr_type_returns_valid_type(string $type): void
+ {
+ $this->assertSame($type, SessionMessage::make($type)->getToastrType());
+ }
+
+ public static function validToastrTypes(): array
+ {
+ return [
+ ['success'],
+ ['error'],
+ ['warning'],
+ ['info'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('invalidToastrTypes')]
+ public function test_get_toastr_type_falls_back_to_info_for_invalid_type(string $type): void
+ {
+ $this->assertSame('info', SessionMessage::make($type)->getToastrType());
+ }
+
+ public static function invalidToastrTypes(): array
+ {
+ return [
+ [''],
+ ['alert'],
+ ['SUCCESS'],
+ [''],
+ ['error; DROP TABLE'],
+ ];
+ }
+
+ // -----------------------------------------------------------------------
+ // jsonSerialize (JSON session 序列化)
+ // -----------------------------------------------------------------------
+
+ public function test_json_serialize_includes_class_key_and_all_fields(): void
+ {
+ $msg = SessionMessage::make('error', 'something went wrong', ['closeButton' => true]);
+ $data = $msg->jsonSerialize();
+
+ $this->assertSame('Dcat\Admin\Support\SessionMessage', $data['__dcat_class']);
+ $this->assertSame('error', $data['title']);
+ $this->assertSame('something went wrong', $data['message']);
+ $this->assertSame(['closeButton' => true], $data['options']);
+ }
+
+ public function test_json_encode_roundtrip(): void
+ {
+ $msg = SessionMessage::make('warning', 'be careful', ['positionClass' => 'toast-top-right']);
+ $decoded = json_decode(json_encode($msg), true);
+
+ $this->assertSame('warning', $decoded['title']);
+ $this->assertSame('be careful', $decoded['message']);
+ $this->assertSame(['positionClass' => 'toast-top-right'], $decoded['options']);
+ }
+
+ // -----------------------------------------------------------------------
+ // tryFrom — PHP 序列化路径
+ // -----------------------------------------------------------------------
+
+ public function test_try_from_accepts_existing_instance(): void
+ {
+ $original = SessionMessage::make('success', 'done');
+ $result = SessionMessage::tryFrom($original);
+
+ $this->assertSame($original, $result);
+ }
+
+ public function test_try_from_returns_null_for_plain_object(): void
+ {
+ $this->assertNull(SessionMessage::tryFrom(new \stdClass()));
+ }
+
+ public function test_try_from_returns_null_for_null(): void
+ {
+ $this->assertNull(SessionMessage::tryFrom(null));
+ }
+
+ public function test_try_from_returns_null_for_string(): void
+ {
+ $this->assertNull(SessionMessage::tryFrom('success'));
+ }
+
+ // -----------------------------------------------------------------------
+ // tryFrom — JSON 序列化路径
+ // -----------------------------------------------------------------------
+
+ public function test_try_from_reconstructs_from_json_array(): void
+ {
+ $original = SessionMessage::make('info', 'hello', ['timeOut' => 5000]);
+ $array = json_decode(json_encode($original), true);
+
+ $restored = SessionMessage::tryFrom($array);
+
+ $this->assertNotNull($restored);
+ $this->assertSame('info', $restored->getTitle());
+ $this->assertSame('hello', $restored->getMessage());
+ $this->assertSame(['timeOut' => 5000], $restored->getOptions());
+ }
+
+ public function test_try_from_returns_null_for_array_without_class_key(): void
+ {
+ $this->assertNull(SessionMessage::tryFrom(['title' => 'info', 'message' => 'hi']));
+ }
+
+ public function test_try_from_returns_null_for_array_with_wrong_class_key(): void
+ {
+ $this->assertNull(SessionMessage::tryFrom([
+ '__dcat_class' => 'Some\Other\Class',
+ 'title' => 'info',
+ 'message' => 'hi',
+ ]));
+ }
+
+ public function test_try_from_tolerates_missing_fields_in_json_array(): void
+ {
+ $restored = SessionMessage::tryFrom([
+ '__dcat_class' => 'Dcat\Admin\Support\SessionMessage',
+ ]);
+
+ $this->assertNotNull($restored);
+ $this->assertSame('', $restored->getTitle());
+ $this->assertSame('', $restored->getMessage());
+ $this->assertSame([], $restored->getOptions());
+ }
+
+ public function test_try_from_ignores_non_string_title_and_message(): void
+ {
+ $restored = SessionMessage::tryFrom([
+ '__dcat_class' => 'Dcat\Admin\Support\SessionMessage',
+ 'title' => ['injected'],
+ 'message' => 42,
+ 'options' => ['timeOut' => 3000],
+ ]);
+
+ $this->assertNotNull($restored);
+ $this->assertSame('', $restored->getTitle());
+ $this->assertSame('', $restored->getMessage());
+ $this->assertSame(['timeOut' => 3000], $restored->getOptions());
+ }
+
+ public function test_try_from_ignores_non_array_options(): void
+ {
+ $restored = SessionMessage::tryFrom([
+ '__dcat_class' => 'Dcat\Admin\Support\SessionMessage',
+ 'title' => 'info',
+ 'message' => 'hello',
+ 'options' => 'not-an-array',
+ ]);
+
+ $this->assertNotNull($restored);
+ $this->assertSame('info', $restored->getTitle());
+ $this->assertSame('hello', $restored->getMessage());
+ $this->assertSame([], $restored->getOptions());
+ }
+}
\ No newline at end of file