drodata
- drodata 回答了问题 Yii2.0 操作事务多个数据库
事务支持嵌套: https://www.yiiframework.com/doc/guide/2.0/en/db-dao#nesting-transactions
如果先写入 A 表,就把 B 表的写入操作事务代码嵌套在 A 表的事务内。我还没遇到过你说的类似场景,你可以试一下。
- drodata 回答了问题 请教下关于请求与响应
框架简单概括就是处理请求,将请求结果发送到客户端。“自动完成”固然方便,如果还能明白自动完成的详细过程和原理,印象就会更深刻。你所说的“深入学习”具体指什么呢?“手动实现”能说具体些吗?
字符串比数字更易读。
我觉得使用ID关联更好
能具体说一下好在哪里吗?
你的思路有点问题。"ActiveRecord" 指的就是表格中的某一条记录,不能是多个表格中的某一条记录。既然你建立了两个表,就应该对应两个 AR 模型。
你描述的问题的根源在于你的表格设计得不好。试着只用一个 menu 表,里面包含原来的 menu 和 menu_frontend 的所有属性。在新的 Menu 模型内添加一个 'frontend' 场景,ActiveRecord 有个默认的
defult
场景,可作为你的 backend 场景:,rules()
内根据场景设定验证规则,例如:// in Menu.php const SCENARIO_FRONTEND = 'frontend'; public function rules() { return [ // 这些规则同时作用于 'default' (backend) 和 'frontend' [['type', 'name', 'url'], 'required'], // 这些规则仅作用于 'frontend' 场景 [['seo_title', 'seo_keyword'], 'required', 'on' => self::SCENARIO_FRONTEND], ]; }
通过上面的配置,后台代码可直接使用 menu 实例,前台代码将场景设置为 'frontend' 即可,例如:
// backend $menu = new Menu(); $menu->name = 'test'; $menu->save(); // backend $menu = new Menu(['scenario' => Menu::SCENARIO_FRONTEND]); $menu->name = 'test'; $menu->seo_title = 'seo-test'; $menu->save();
@clao 那就必须创建两个模型,只是可以放在不同的命名空间下。
假设两张表的结构如下:
其中 'menu' 表仅在后台使用,'menu' 和 'seo_menu' 同时在前台使用。后台只用到 'menu' 表,这里就不说了。下面简单演示一下前台如何同时向两张表内存储数据。
/** * 前台菜单模型 * * seo_menu 仅在前台用到,放在 `frontend\models` 命名空间下: */ namespace frontend\models; class Menu extends ActiveRecord { public static function tableName() { return '{{seo_menu}}'; } public function getMenu() { return $this->hasOne(\backend\models\Menu::className(), ['id' => 'menu_id']); } }
/** * 控制器 */ namespace frontend\controllers; // 这里需要同时引入前后台的 Menu 类,避免命名冲突,将前台 Menu 重命名 // 也可以跟个人习惯直接将前台菜单类名写成 SeoMenu use frontend\models\Menu as SeoMenu; use backend\models\Menu class MenuController extends \yii\web\ActiveRecord { public function actionCreate() { $model = new Menu(); $seoMenu= new SeoMenu(); if ( $model->load(Yii::$app->request->post()) && $model->validate() && $seoMenu->load(Yii::$app->request->post()) && $seoMenu->validate() ) { // 事件绑定,'menu' 表记录写入后,写入 'seo_menu' 记录 $menu->on(Menu::EVENT_AFTER_INSERT, [$menu, 'insertSeoMenu'], $seoMenu); $menu->save(false); } return $this->render('create', [ 'model' => $model, 'seoMenu' => $seoMenu, ]); } }
/** * 表单视图片段,同时搜集两个表的字段信息 */ <?= $form->field($menu, 'type')->textInput() ?> <?= $form->field($menu, 'url')->textInput() ?> <?= $form->field($seoMenu, 'keyword')->textInput() ?> <?= $form->field($seoMenu, 'description')->textInput() ?>
/** * 后台菜单模型 */ namespace backend\models; class Menu extends ActiveRecord { /** * 执行写入前台 menu 的 handler */ public function insertSeoMenu($event) { $seoMenu = $event->data; $seoMenu->menu_id = $this->id; if (!$seoMenu->save()) { throw new \yii\db\Exception('Failed to insert into seo_menu.'); } } }
中文文档、英文文档和源代码对照着看,多看几遍就明白了。Yii2 代码的作者主要是德国人和俄罗斯人,母语都不是英语,他们写的英文文档是将他们写的代码用非母语(英语)表达出来;Yii2 中文文档又是根据英文文档翻译的,又得经过一次语言转换,换句话说,如果你只看中文文档的话,你看到的是倒过两手的信息,“痛不欲生”是很正常的。由此可见看源码是理解框架的最直接有效的办法,或者说结合中英文文档来看源码。所谓的 "definitive guide" 无非是写代码的人写的关于代码的说明书。
- drodata 赞了回答
你怕是没有看过微信的文档
- drodata 回答了问题 如何一个模型关联多个数据表?
你的思路有点问题。"ActiveRecord" 指的就是表格中的某一条记录,不能是多个表格中的某一条记录。既然你建立了两个表,就应该对应两个 AR 模型。
你描述的问题的根源在于你的表格设计得不好。试着只用一个 menu 表,里面包含原来的 menu 和 menu_frontend 的所有属性。在新的 Menu 模型内添加一个 'frontend' 场景,ActiveRecord 有个默认的
defult
场景,可作为你的 backend 场景:,rules()
内根据场景设定验证规则,例如:// in Menu.php const SCENARIO_FRONTEND = 'frontend'; public function rules() { return [ // 这些规则同时作用于 'default' (backend) 和 'frontend' [['type', 'name', 'url'], 'required'], // 这些规则仅作用于 'frontend' 场景 [['seo_title', 'seo_keyword'], 'required', 'on' => self::SCENARIO_FRONTEND], ]; }
通过上面的配置,后台代码可直接使用 menu 实例,前台代码将场景设置为 'frontend' 即可,例如:
// backend $menu = new Menu(); $menu->name = 'test'; $menu->save(); // backend $menu = new Menu(['scenario' => Menu::SCENARIO_FRONTEND]); $menu->name = 'test'; $menu->seo_title = 'seo-test'; $menu->save();
你代码中的配置仅对正常的网页请求,ajax 请求的个性化错误信息可通过配置
response
组件实现。你遇到的 500 错误是因为 response 组件在遇到错误时,直接把错误传递到客户端并被 jQuery 捕获。可以借助 Response 的 beforeSend 事件改变这种默认的行为:在发送错误前将错误信息进行封装,
'components' => [ 'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; // 这里我们仅针对 ajax 请求 if ($response->format == \yii\web\Response::FORMAT_JSON) { if ($response->data !== null) { // 手动将抛出异常的状态码改为 200, 确保不被 jQuery 捕获 $response->statusCode = 200; // 自定义错误显示格式,把真正的响应数据存储在 'data' option 内 $response->data = [ 'success' => $response->isSuccessful, 'data' => $response->data, ]; } } }, ], ],
配置后,如果通过 ajax 访问下面的 action,
// in TestController.php public function actionAjaxRead() { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; // 这里将抛出异常 return 3/0; }
$.post("/test/ajax-read", function(response) { console.log(response) })
响应内容将输出:
参考: https://www.yiiframework.com/doc/guide/2.0/zh-cn/runtime-handling-errors#error-format
@小叮当的肚兜 上面的截图是在 prod 下测试的,dev 下显示的是堆栈信息