drodata / yii2-utility Goto Github PK
View Code? Open in Web Editor NEWAwesome utilities I use frequentyly in my projects.
Home Page: https://github.com/drodata/yii2-utility/blob/master/docs/SUMMARY.md
Awesome utilities I use frequentyly in my projects.
Home Page: https://github.com/drodata/yii2-utility/blob/master/docs/SUMMARY.md
/**
* @inheritdoc
*/
public function fields()
{
$customFields = [
'readableName' => function () {
return $this->readableName;
},
];
return ArrayHelper::merge(
parent::fields(), $customFields
);
}
抽象:在 Html::a()
内加入此特性,避免代码重复。当链接同时满足下列两个条件时,自动添加 wrapper:
.disabled
title
值不为空;actionLink()
中的路由目前仅支持使用 'id' 作为主键的模型,像 Rate
这种使用符合主键的模型,还要手动修改路由,这里改进为自动生成。
用于将相近的 tabs 归类,最好能做成一个开关。另外一个开关是,是否为 index, view 两个 actions 生成专门适合手机的 views.
从月结账单功能中发现使用 wkhtmltopdf 将打印内容输出成 PDF 有诸多好处,可以把里面很多公用的东西抽象出来,整合进来,满足打印下载这两个常见的需求。要点:
print
, download
, 前者在 server 上完成 HTML 到 pdf 文件的转换;后者是供用户下载的入口;模型操作分两类:一类是跟具体模型有关,例如修改删除等,路由后都带有主键参数锁定具体模型;另一种跟具体模型无关,例如新建操作。
新建操作按钮.
Lookup::route('cost-create-prepay')
视图中的变量应该仅来自以下两个地方:
$view->params
内的数组变量;除此之外,不应该在视图内声明任何变量,MVC 中视图只负责数据展示,不应该含有组装数据的代码。
之前使用 Gii 一直没涉及自定义 generator. 现在需要做一个 api controler 的 genetator, api 下的控制器和 web 控制器区别很大,例如没有视图文件等。因此需要重新定义 generator. 现在的目录结构导致自定义的 generator 的命名空间只能是 drodata\gii-templates\api\controller
, 这个横线 -
导致类无法被识别。例如,假设一个名为 foo\bar-a\MyWidget
, 在视图中进行输入时:
<? = foo\bar-a\MyWidget::widget() ?>
提示 Undefined constant 'foo\bar'
, 换句话说,PHP 把命名空间中的 -
解析成了减号,foo\bar
被理解成了变量。
关于这个限制,暂时没有在官方手册中找到相关说明。以后禁止使用横线,目录尽量用一个单词表示,非要用多个单词了,也要使用 camelCase
.
从财务待办想到的,目标:只需要传入必要的配置即可。
借助改进后的 ActiveField, 我们可以通过在 AR model 内声明 getAttributesHints()
的方式,快速在 Form element 新建 popover.
这种简易的 popover helper 在其它地方也有用到,每次都要重复写类似下面的内容有些费劲,
return Html::icon('question-circle-o', [
'class' => 'text-info',
'data' => [
'toggle' => 'popover',
'trigger' => 'hover',
'placement' => 'auto top',
'content' => '此价格尚未包含运费',
],
]);
不如在 Html
内声明一个简易的方法。
新增的 appendMd5Hash
属性有个不足:仅支持 web 可直接访问的 asset, 不支持类似 AdminLTE custom.css 这样的 source assets. custom.css 也属于经常变动的 asset, 但由于浏览器缓存的原因,即便 composer update 后,asset url 仍然没有变动,导致 asset 仍不是最新的。
Model template 增加一下内容:
public function getIsCreated()
{
return $this->status == self::STATUS_CREATED;
}
...
public function getCreator()
{
return $this->hasOne(User::className(), ['id' => 'created_by']);
}
crud 模板方便:
_grid.php
内创建时间列增加 width:170px;min-width:170px;
样式;_field-tabular.php
: 表头列增加 table-layout:fixed
属性,用最简单的办法确保 tabular row 的各个列的宽度;主要对诸如 Trial 和 TrialItem 这样含有子条目的模型详情页面在下屏幕上进行了优化。
_detail-view
内明细页面合并成 _div-items
, 在后者进行大小屏区分显示;_div-items
大屏幕上显示 _grid-parent
; 小屏幕上显示 _list-ol
. 这两个视图都进行了命名调整,更加直观;_list-item-span
视图,用于在小屏上以 <li>
形式显示子条目内容;相对应的,默认的 _list-item
是 block 形式;在 StackOverflow 上提问:Is it possible to design a generic many-to-many junction table?
这表示之前在 d1bcb8d 中直接修改代码的方法(d1bcb8d#diff-e2f921f5dd2a68c6ddebaa9892625cacL41 )是错误的
类似 label()
, icon()
用来生成一个带有 tooltip 的 icon, 主要是为了解决 label 过宽的问题,类似 GridView ActionColumn 中的 action link.
便于维护代码:
详见: https://gitlab.com/drodata/eims/-/issues/116 中过滤回款的方法。
不利于排查错误。
code : 0
message : "服务器内部错误。"
name : "Internal Server Error"
status : 500
----
file : "/path/to/Model.php"
line : 300
message : "Call to a member function validate() on null"
name : "PHP Fatal Error"
stack-trace : Array[2]
type : "yii\base\ErrorException"
yii2-utility/gii/backend/model/default/model.php
Lines 186 to 191 in 2d1b5ff
现在配置模型链接都在 getActionOptions()
内完成,actionLink()
仅负责调用,内容不会再变动,可以直接放进 drodata\db\ActiveRecord
内。
以 TimestampBehavior 为例,在某些场景下,当写入一条新纪录时,我们不想插入当前的时间戳,这时就需要使用 detachBehavior()
临时解绑,此方法的参数表示行为名称,这也意味着对应的行为必须是 named behavior 而不是 annoymous behavior. 因此修改 Gii 模板,在 behaviors()
内统一使用 named behavior 来声明。
if ($invalid) {
Yii::$app->session->setFlash('warning', 'invalid');
$this->redirect(Yii::$app->request->referrer);
}
类似这样的常用操作,仿照 goHome()
添加一个 jumpBack()
shortcut 以减少输入。如此命名是因为 goBack()
已经存在。
In grid/detail view widget, one could display human-readable timestamp via custom <attribute>:date
format, that feature doesn't work in custom detail view file, because of this fact, we should declare the follow method in nearly every model:
public functon getReadableCreatedAt()
{
return Yii::$app->formatter->asDatetime($this->created_at);
}
To avoid of duplicate coding, declaring a common method in TimestampBehavior may be a good choice.
实践证明此法行不通,自己的设想是能向方法传递列名(如 'created_at'),但是在方法内部是不能使用 没看代码,已经提供 $this->created_at
的,模型具有 Timestamp behavior 不代表时间戳行为能直接使用模型中的属性。$createdAtAttribute
属性。
关键:$this->owner
可以访问到 AR 对象。
if (empty($code) || empty($color)) {
上面的 empty() 不好。设置的值还不能是 0, 实际中 code 设置为 0 很常见,且看上去很直接。
InventoryAction 有一个不再使用(Picking), 将 visible 设置为 0 后, lookup()
直接不显示内容,应该显示出来,是否可见。
Update: 这里设计的很不好,像这种通用的写入操作,更多的是通过事件触发来完成,此方法应该写成 event handler 的形式就好了!
解决:向 \drodata\db\ActiveRecord
内追加 handleWrite()
handler, 专门用于事件触发
public static function handleWrite($event)
{
static::write($event->data)
};
想通 #15 后觉得没必要。
在数据库设计中,通常会存在几个多个模型共用的表格,例如可以创建一个 map 表,专门存储各种多对多关系,结构大致如下:
id
type
from_id
to_id
假设有下面两个多对多关系,依靠上面的 map 表关联:
那么在实现新建产品或包裹操作时,都要在代码中存储 map 记录,例如:
# code-example-1
$map = new Map();
$map->type = ITEM2IMG;
$map->from_id = 3; // item id
$map->to_id = 5; // image id
$map->save();
每次都要完整地写出上面 5 行代码,很麻烦。其实类似 map 表有两个特点:表格结构简单;大多出现在事务中,很少单独存储。拿上面的新建产品为例,事务中要分别向三个表格中写入记录:产品表、图片表和 map 表。根据这两个特点,我们可以考虑把上面的代码抽象成一个静态方法,如下:
// in Map.php
public static function write($attrs)
{
$map = new Map();
$map->attributes = $attrs;
if (!$map->save()) {
throw new \yii\db\Exception("Failed to insert into Map");
}
}
这样以来, code-example-1 处的代码就可简化为:
Map::write([
'type' => ITEM2IMG,
'from_id' => 3,
'to_id' => 5,
]);
像 map 表这样的表还有很多,难道在每个 model 内都声明一个 write
方法吗? 更好的办法是创建一个继承自 yii\db\ActiveRecord
的子类,把通用的 write()
放在这个子类中,然后使用 Gii 生成 Model 类文件时,自动让类继承自自定义的子类即可。更改后的方法如下:
public static function write($attrs)
{
$name = static::className();
$model = new $name();
$model->attributes = $attrs;
if (!$model->save()) {
throw new \yii\db\Exception(
"Failed to insert into " . static::tableName()
);
}
}
Getting unknown property: drodata\adminlte\Tabs::dropdownClass
dropdownClass
在 2.0.7 中被引入,而 2.0.7 尚未 relase. 备注一下,暂时不能使用 dropdown.
其实每个表单都应该显性添加一个取消按钮,给用户一个反悔的选择。尽管有其它变相的返回办法(比如点击浏览器的返回前一页按钮),但是并不是所有用户都知道,一定要在醒目的地方给用户留一个出口。
所谓“取消”,就是返回前一页 (Yii::$app->request->referrer
).
借助 Bootstrap 的 .table-responsive
类,可以让 GridView table 在手机上实现自适应——当表格宽度大于手机屏幕宽度时,可以通过左右滑动,显示表格中不可见的内容。这种方式在虽能达到一定程度上的自适应,但实际体验并不理想,尤其是当用户需要频繁在手机上进行相关操作时。用户需要先向左滑动表格,使得表格最右侧的 action column 显示出来,然后点击对应的按钮。另外一个体验不好的地方是 grid view 中的 filter, 也需要借助滑动屏幕才能进行。
自适应如果能达到这样一种效果就好了:当用户在手机上访问时,GridView 自动变成 ListView, 页面顶部是一个搜索框,下方是满足条件的 list items, 每个 list item 使用 DetailView widget 显示,DetailView 默认用一个两列的表格显示,在手机上显示效果很好。经过简单搜索,Bootstrap 提供了相关的类, 借助下面两个类,就能实现想要的效果:
.visible-xs-block
: 具有该类的 div 仅会在手机上显示,其它尺寸的设备上都会显示;.hidden-xs
: 具有该类的 div 仅会在手机上隐藏,其它尺寸的设备上都会显示;我们在模型的管理页面(例如 /user/index
)内同时放置 GridView 和 ListView 两个 widgets, 大致结构如下:
<?php
// in views/user/index.php
?>
<div class="row">
<div class="col-xs-12 hidden-xs">
<?= GridView::widget([]) ?>
</div>
<div class="col-xs-12 visible-xs-block">
<?= $this->render('_search', ['model' => $searchModel]) ?>
<?= ListView::widget([]) ?>
</div>
</div>
在上面的结构下,根据个人的需要进行自定义设置即可。后期还可以把它单独作为一个 Gii 模板,提高开发速度。这里有我自己的 CRUD 模板,供大家参考。
在 GridView 的 action column 内,根据记录的状态的不同,操作按钮经常需要显示成禁用状态。例如,当订单状态为“已发出”时,禁止删除订单,按钮如下:
'buttons' => [
'delete' => function ($url, $model, $key) {
if ($model->status == SENT) {
return Html::icon('trash', [
'title' => '已发出的订单暂不支持删除',
'class' => 'text-muted',
'data' => [
'toggle' => 'tooltip',
],
]);
} else {
return Html::a(
Html::icon('trash'),
['update', 'id' => $model->id],
[
'title' => '删除'
]
);
}
},
],
针对这个还算普遍的需求,上面生成禁用按钮的方法太过繁琐,有必要加一个 shorthand method tooltopIcon()
.
对应的 popoverHelper()
同时做如下变动:
popoverIcon
和 tooltipIcon
保持一致;behviors()
内 TimestampBehavior 改用完整形式,因为有很多表不需要 updated_at,与其每一次手动添加,不如直接在 widget 内自动添加。
以 Modal 形式查看详情体验更好。问题是每增加一个功能,都要做类似的工作:
如何设计才能省掉这些重复工作?
可以看到显示的 option 值没有上下居中,有点偏上。此效果在 AdminLTE.css 内设置:
.select2-container .select2-selection--single .select2-selection__rendered {
padding-left: 0;
padding-right: 0;
height: auto;
margin-top: -4px; /* !*/
}
https://github.com/almasaeed2010/AdminLTE/blob/master/dist/css/AdminLTE.css#L4239
原因未知。
get
前缀,容易和 Yii2 的 getter/setter 混淆。统一更改为 disabledHint()
更合适;Html::actionLink()
中跟逻辑密切相关的选项有三个:visible, disabled 和 confirm. visible 相对简单。禁止执行某个动作的条件和执行某个动作前的确认信息比较复杂。之前在 Gii model 模板中已先后添加 actionLink()
和 getConfirmText($action)
, 后者的目的就是将这个复杂的逻辑剥离出来而新建的方法。
今天在设置合同评审禁用状态时发现可能性较多,也适合剥离出来。getDisabledHint()
返回值类型有两个:string 或 null. 允许执行返回 null, 否则返回禁止操作提示信息,例如下面:
public function getDisabledHint($action)
{
switch ($action) {
case 'audit':
if ($this->isAudited) {
return '已评审';
}
// 已提交初次评审
if ($this->isUnaudited && $this->hasContractAudit) {
$auditor = $this->contractAudit->currentAuditor;
return empty($auditor) ? false : "等待 $auditor 审核";
}
return null;
break;
}
}
代码内部使用了 guard conditions: 自上而下一次列出所有需要禁止操作的可能性并提前返回值,在所有可能性都列出后,自然就是可以操作了。
必须使用动态绑定的一种情况
假设有两个表:order 和 activity, 分别表示订单和日志。订单的每一次操作(创建、修改等)都写入日志。日志表有一个 reference_id 列用来标识 order id. 问题:新建操作比较麻烦,无法向其它操作一样,使用内置的 EVENT_AFTER_INSERT 或 EVENT_AFTER_UPDATE 来完成,因为 on() 第三个参数需要带上 order id, 而绑定时 order id 尚未产生(修改操作没有这个问题)。
额外的开销就是,需要特地新建一个类似 EVENT_AFTER_CREATED 的事件。我在想是否有必要像官方的 beforeSave() 那样,在 drodata\db\ActiveRecord 内预定义一个 afterCreated(), 方法内除了触发 EVENT_AFTER_CREATE 外,什么也不做。
这种情况适合所有设计多表存储的情形,其它的例子包括:新建订单时, order
和 item
两个表,item.order_id
是外键。
yii2-utility/migrations/sku/m180224_082756_init_sku_module.php
Lines 137 to 141 in 03236d8
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.