0%

这种情况只在通过 __get 获取对象属性的时候才会出现。之前也写过一篇类似的文章,但是那里阐述的报错原因没那么直观,本文指出了 laravel 里面出现这个报错的常见原因。

在 Laravel 中,有时候我们需要修改关联的某一个属性的值。这个时候我们一般会使用 $a->b->c = 1; 这种形式。

这种形式一般情况下是没有问题的,但是如果在关联的值是 null 的时候,也就是没有对应的关联的时候就会报错。

而这个报错往往让人摸不着头脑,事实上原因很简单,只是设置了 null 的属性。

报错信息

1
Indirect modification of overloaded property A::$b has no effect

测试源码(lumen)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

use Illuminate\Database\Eloquent\Model;

require_once __DIR__ . '/bootstrap/app.php';

app()->boot();

/**
* Class A
* @property-read B b
*/
class A extends Model
{
protected $table = 'as';

public function b()
{
return $this->hasOne(B::class);
}
}

class B extends Model
{
protected $table = 'bs';
}

/** @var A $a */
$a = A::query()->first();

dump($a->b); // null
$a->b->id = 2; // Indirect modification of overloaded property A::$b has no effect

解决方法

1、如果我们允许这个值为 null,并且需要在其不为 null 的时候设置其值,则可以用 optional 方法:

1
optional($a->b)->id = 2;

2、如果业务上是不会出现 null 的情况的,直接让它异常就好了,早点发现。

XML 实例文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="ISO-8859-1"?>

<bookstore>

<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>

<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>

</bookstore>

选取节点

XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。

下面列出了最有用的路径表达式:

表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

实例

在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠(/),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选取属于 bookstore 元素的后台的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性

谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点。

谓语被嵌在方括号中。

实例

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last() - 1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position() < 3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00

选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点
@* 匹配任何属性节点
node() 匹配任何类型的节点

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

选取若干路径

通过在路径表达式中使用 "|" 运算符,你可以选取若干个路径。

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
//book/title|//book/price 选取 book 元素的所有 title 元素和 price 元素。
//title|//price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title|//price 选取属于 bookstore 元素的 book 元素的所有 title 元素以及文档中所有的 price 元素。

在 Laravel 表单验证中我们常常会需要对一些非必填的字段进行校验,比如:string

但是如果如果 data 里面的对应字段是 null,则验证会失败,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$data = [
'num' => null
];

/** @var \Illuminate\Validation\Validator $validator */
$validator = app('validator')->make(
$data,
[
'num' => 'numeric'
]
);

if ($validator->fails()) {
dd($validator->messages()->toArray());
}

结果:

1
2
3
4
5
array:1 [
"num" => array:1 [
0 => "num字段值必须为数字"
]
]

如何让其通过验证?

如果我们的这个字段允许为 null 的话,可以加上 nullable 验证规则,比如:

1
2
3
$rules = [
'num' => 'nullable|numeric'
]

当然如果保存到数据库的时候不能为 null 的话,就不能这么写了。

dcat-admin 中的 Form, Show, Grid, Column 等内置类都 use 了 Dcat\Admin\Traits\HasBuilderEvents 这个 trait。

通过这个 trait,我们可以对 Form, Show, Grid, Column 的生命周期做一些类似勾子的操作。

resolving 主要用在统一自定义某些页面组件是否展示,composing 事件用于页面渲染时候做一些额外的操作。常用的主要是 resolving

resolving

1
2
3
4
5
6
7
// 默认禁用表单的一些 checkbox 以及 header
Form::resolving(function (Form $form) {
$form->disableHeader();
$form->disableViewCheck();
$form->disableEditingCheck();
$form->disableCreatingCheck();
});

composing

1
2
3
$form->composing(function ($form) {
static::addScript($form);
});
1
2
3
4
5
6
7
8
9
10
protected static function addScript(Form $form)
{
$confirm = json_encode($form->builder()->confirm);

Admin::script(
<<<JS
Dcat.FormConfirm = {$confirm};
JS
);
}

页面渲染的时候,添加一段自定义的 js 代码。

最近在使用 laravel/fortify 的过程中发现,在不同的环境中,一个环境 2fa 验证正常,但是另外一个环境验证一直失败,即使输入的是正确的验证码。

后来想起了 totp 算法相关的一个很关键的点,就是生成的验证码是基于时间的,这样一来,如果某一个环境上的时间是不对的(这里的不对是指不是那个环境时区上对应的准确时间),是不是和客户端生成的验证码不一样了。

后来经过验证发现,的确是这个原因导致的。

只有服务器的时间是正常的时间,UTC + 时区对应的小时数这样,验证都不会有问题。

另外,快速设置时间的方式:

1
date -s '20:09:41 2019-12-08'