php初心者, 记录自己工作和学习路上的感想和笔记

0%

get_include_path 开始

1
2
3
<?php
$path = get_include_path();
var_dump($path);

输出

1
2
root@c6c2fe3c9a93:/var/www/php_test# php test_get_include_path.php
string(20) ".:/usr/local/lib/php"

这个会输出我们配置的 include 目录,我当前的设置下,会有 php 的 lib 目录,和当前目录,让我们来测试一下。

首先测试,当前目录下的文件引入,下面这个是我们在项目目录创建的文件

1
2
3
4
5
<?php

function testCurrent() {
var_dump('This is current include Output');
}

然后我们在另一个文件引入

1
2
3
4
<?php
require 'test_include_current.php';

testCurrent();

会得到输出

1
2
root@c6c2fe3c9a93:/var/www/php_test# php test_get_include_path.php
string(30) "This is current include Output"

ok,当前文件夹没问题,我们在看看 lib 目录,我们在 lib 目录下,在我的实验环境中就是 /usr/local/lib/php 目录,放入了一个文件,然后在代码中引入,调用方案跟上面差不多,也就不贴代码了,都是一样的,依然可以得到输出。

这时候我们可以看到,在这两个目录里面的文件,都可以直接引入,引入文件的方法都可以正常使用。我们在随便找个目录写入个文件,看看能否引入呢?

我们随便找个目录写入了文件,然后 include 进来 ,include 是有返回值的,我们打印了一下,可以看到是 false,说明这个文件在 include_path 是没有找到的。所以引入失败了。

最后我们在测试个目录,我们就在当前文件夹下测试就好了,别的目录也是一致的。
结构是这样的,
在这里插入图片描述
代码是

1
2
3
4
<?php
$res = include 'include_folder/test_include_current.php';
var_dump($res);
testCurrent();

依然可以调用,完美。
最后,关于 include 要看php的文档,上面说的那么多都是文档里面写的。https://www.php.net/manual/zh/function.include.php

走进 set_include_path

这个方法是昨天在分析 typecho 的时候看到的,所以就引出了这篇文章。这方法是设置 include_path 用的,平时我们在写代码的时候不可能都在一个文件夹下写代码,那很难管理了,所以就会分成各种文件夹,但是这样,在 include 的时候又要写文件夹岂不是很麻烦,所以就可以调用这个方法,设置一下引入目录,以后引入的时候就可以不用写冗长的目录路径了。
但是这种会有一个什么问题呢,不同的文件夹下依然不能存在同名文件夹,否则可能会引入错误的文件。导致出现问题。

在进一步 __autoload

随着不断的进步,项目越来越大,文件起名是个问题,各种考虑名字不要冲突,还要时刻记得引入文件,这会让人很头大,大家都想要有没有什么办法,可以自动引入文件,这时候 面向对象__autoload 还有 namespace 让大家重新拾起了希望。

关于面向对象啥的大家还是自己看文档,这边我就直接从 __autoload 开始了。文档在这https://www.php.net/manual/en/function.autoload.php。当尝试加载一个未定义的类的时候会触发这个方法,我们的希望来了,先看实例代码

1
2
3
4
5
6
7
8
<?php

function __autoload($class)
{
var_dump($class);
}

$cls = new TestClass();

现在最上面写好 __autoload 方法,我们就输出一下 $class 的名称,当我们运行后,可以看到如下输出:

1
2
root@c6c2fe3c9a93:/var/www/php_test# php test_autoload.php
string(9) "TestClass"

可以看到我们这边成功输出了我们的类名,如果在这个文件里面有这个类会怎样呢,结果就是,不会触发这方法。那么利用这个特性,我们就可以搞事情了,我们就可以吧命名空间跟文件目录对应上,这样我们就可以用这个方法去引入文件了。来,看个例子,

1
2
3
4
5
6
7
8
9
10
// test_autoload.php
<?php
function __autoload($class)
{
var_dump($class);

require_once $class . '.php';
}

$cls = new TestClass();
1
2
3
4
5
6
7
8
9
10
11
// TestClass.php
<?php


class TestClass
{
public function __construct()
{
var_dump('TestClass is construct');
}
}

可以看到结果

1
2
3
root@c6c2fe3c9a93:/var/www/php_test# php test_autoload.php
string(9) "TestClass"
string(22) "TestClass is construct"

我们的两个输出都看到了,说明什么,文件成功引入,那么如何支持 namespace 呢,我直接上解决方案,大家可以自己试试看

1
2
3
4
5
6
7
function __autoload($class)
{
var_dump($class);
$cls = str_replace('\\', DIRECTORY_SEPARATOR, $class);
var_dump($cls);
require_once $cls . '.php';
}

这边只是进行了简单的调整,更多的可以看下面的东西。

升级 spl_autoload_register

这东西其实就是 __autoload 只不过 autoload 有的一些限制,被这个方法升级了。而且这个方法还有一个对应方法是 spl_autoload_unregister 加载完成后,直接卸载加载方法,进一步减少冲突的可能。

大步走向 composer

不得不说这个东西整的真好,定义了一系列加载规范,再配合起来 psr 标准,现在 php 的生态也逐步好起来了。
composer 这个东西,就是把你的配置文件,做了一份映射,然后用 spl_autoload_register 来加载文件的,我们直奔我们需要的东西去吧。进入 autoload_real.php 文件,如果你找不到这个文件,先去了解一下 composer 在继续哦,或者干脆不看下面的, 这个只不过就是花式用了 spl_autoload_register 而已。这个文件里面的 getLoader 方法其实就是加载的方法,不过这个方法里面最终调用的都是 ClassLoader 这个类里面的相关方法,把各种格式的文件帮助我们引入进来。其实就是这么简单,感觉他高级的原因就是我们只需要配置一下各种格式就好了,剩下的都帮我们搞定了。更多的东西大家可以自己看 classLoader 里面的代码哟。

结语

好了,这次的分析比上次满意多了,我们继续回归到 typecho 的分析中去。

@TOC

先说点前置的东西

从今天开始我要开始写源码分析的文章了,以前用 csdn 博客写过一些 android 和 java 的东西,后来脑袋抽筋被我删除了。所以这次等于是全新的开始,准备输出一些东西了。做了 5 年的开发,发现自己缺乏很多东西,所以这次从源码分析开始,输出东西以及补充自己的知识。

为什么选用 typecho ?

这个博客系统很经典,说是 cms 也不为过,毕竟他的对手 wordpress 也可以说是 cms 了,现在估计更多的企业都是用它来做展示站了。而不仅仅是博客。

也许会有朋友说,这么老的代码分析有何用?但是我想说虽然它没有用 composer,没有各种新奇的东西,而且还有很多 phphtml 混排的代码。但是他的代码我认为我可以分析的比较清楚,没有更多外部的引用,让我们可以全身心的投入到它本身的代码中去。不用考虑各种引用的代码,让我们在深入进去,比如如果分析 laravel,就要分析很多引入库了。另外,这个代码的注释写的太好了。当然了,后续我也会分析这种代码的。

我分析的流程

分析代码有很多种

  1. 一个文件分析完,在分析另一个
  2. 遇到一个方法或者流程的改变就进入到相应方法或者流程

我在这边选用第二种,这样更符合我的风格

正文开始

进入 index.php

1
2
3
4
5
/** 载入配置支持 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once 'config.inc.php') {
file_exists('./install.php') ? header('Location: install.php') : print('Missing Config File');
exit;
}

如果没有定义 __TYPECHO_ROOT_DIR__ 并且 引入 config.inc.php 失败就进入判断。
这时候检测 ./install.php 如果存在就转到 ./install.php 文件,否则就报错 Missing Config File

第一次进入这个文件的时候,我们肯定是没有安装的,所以我们就进入 install.php

来到 install.php

1
<?php if (!file_exists(dirname(__FILE__) . '/config.inc.php')): ?>

检测 install.php 同目录下是否存在 config.inc.php 文件 如果存在就到了第 35 行开始执行下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
require_once dirname(__FILE__) . '/config.inc.php';

//判断是否已经安装
$db = Typecho_Db::get();
try {
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));
if (empty($installed) || $installed['value'] == 1) {
Typecho_Response::setStatus(404);
exit;
}
} catch (Exception $e) {
// do nothing
}

引入 config.inc.php 配置文件,紧接着查询 table.options 表,是否包含 'name = installed 的行,如果没有没有检测到就报错 404,如果存在就继续执行。当然现在的我们是没有安装的,我们执行上面的代码,如果已经安装的流程,我们会在后面分析。现在让我们回到上面的部分看这段代码

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
<?php
/**
* Typecho Blog Platform
*
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @version $Id$
*/

/** 定义根目录 */
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));

/** 定义插件目录(相对路径) */
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');

/** 定义模板目录(相对路径) */
define('__TYPECHO_THEME_DIR__', '/usr/themes');

/** 后台路径(相对路径) */
define('__TYPECHO_ADMIN_DIR__', '/admin/');

/** 设置包含路径 */
@set_include_path(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);

/** 载入API支持 */
require_once 'Typecho/Common.php';

/** 程序初始化 */
Typecho_Common::init();

前面几个定义路径我们就不说了,注释写的很完美,我们说说 set_include_pathget_include_path 这两个方法可以看 https://www.jianshu.com/p/303feaaeded1 这篇文章,这块还得说下 PATH_SEPARATOR 这个东西,不仅是 PATH_SEPARATOR 还有 DIRECTORY_SEPARATOR 这两个分隔符,我们写代码的时候容易写死,但是实际上用这种定义好的常量,他会根据系统来决定用什么分隔符,这就很容易避免 windows 和 linux 系统不一致的区别,导致代码报错。set_include_pathget_include_path 配置好了系统引入路径和 typecho 自定义的引入路径,包括了 var 、插件目录,这两个字目录。以后再引入文件的时候,系统就会根据设置的这些目录,引入相关文件了。这块代码,再现在我们大量使用 namespace,以及 comopser 使用的概率很低了。关于 composer 的一些东西,我会在另一篇去说明。

先暂停一下

今天就先到这吧,我决定明天写完 composer 用的 spl_autoload_registerset_include_path 的相关对比后,在继续

PS

好久没写了,感觉状态不好,估计以后会慢慢提升的。

看着自己的博客接近两个月没得更新,自己很是难受,一直是想输出点东西,但是又不知道写啥,还想把公众号弄起来,博客都这样了,就不用提公众号了。

最近读了一些书,写点读书笔记吧,总结了几天发现没什么实际意义,发现自己的精炼能力几乎为 0。就像分不清重点一样,也包括读书应该读到的程度,什么章节应该精度,什么章节带过就好,这些都是需要提升的能力。

这些是不是就是我可以输出的东西呢,让我想一想。

距离上次写日志都已经过去了两个月,这两个月算是给自己放松了一下,从今年5月份开始就一直处于比较精神紧张的状态,进展不大,但是一直在思考东西,可是现在来看考虑的那些东西也没啥大用。

这两个月看了两本书,一点点视频教程,愈发觉得基础知识需要巩固,所以可能到20年前除了一些电影网站需要的东西还是会继续看书。

最近代码方面,没什么大的东西,电影网站优化了界面,自己修改了一些 dplayer ,不得不说这个播放器是真的好用,可惜跟预想的还是有一些差距的。

准备持续魔改播放器了,一直修改到自己想要的样子,好了,写个日志,证明我还存在。

这是一个计划原本2个月就能写完的小东西,事实证明在各种问题的情况下,两个月依然能够写完。为什么却活生生的拖了两年呢?其实是因为自己想要弄得东西越来越多,后来精简到了最初的规模后,很容易的就实现了。

其实还有一个原因是很多东西太久不写真的记不得了,用了太久的框架,太多的东西平时工作以为顺手拈来,但是当脱离了框架之后呢,全完了,就跟个刚入门的学生一样,很多东西都得去查才行了。

其实拖了两年,也让我有了更多的认识,在下一个阶段完成后,就要去把真正想实现的东西去实现出来了,其实也没啥大不了的东西,想弄个自己弄着好玩的cms出来,不过这个cms我想做成积木,因为我以前也没用过其他的cms(WordPress不算啊),所以呢,也不知道人家的功能怎么样,所以下个阶段搞定后先了解一下其他的cms是什么样的,然后在好好规划一下,实现时间再次定为2年吧。到那时候我都33了。浑身难受。

下个阶段呢,就是看书,把这两年感觉自己薄弱的环节好好的补一补。

东西写完了,也不惦记了,更踏实一些,好好看书,就这样

前几天我们自定义了模型的分页,但是在输出到模板的时候会发现生成模板也是个重复的操作,既然如此,我们就扩展个twig模板的方法,这样以后当我们要生成分页的时候就只需要调用这个方法就可以了,毕竟人懒,麻烦一次以后都舒服。上个图先


这里我们一共做了两个样式,一个是比较传统的样式,一个是 laravel 默认的样式,都挺好看,想用哪个用哪个。

参考文档 https://twig.symfony.com/doc/2.x/advanced.html

源码如下

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php


namespace App\Twig\Extension;

use Twig\Environment;
use Twig\Extension\AbstractExtension;

/**
* twig 模板分页扩展
* Class Paginate
* @package App\Twig\Extension
*/
class Paginate extends AbstractExtension
{
public function getFunctions()
{
return [
new \Twig\TwigFunction('links', [$this, 'links'], ['needs_environment' => true]),
new \Twig\TwigFunction('links2', [$this, 'links2'], ['needs_environment' => true]),
];
}

public function links(Environment $env, $data, $showTotal = true, $extraClass='', $eachSideCount = 2, $pageName = 'page', $template = 'paginate/links.html')
{
$links = $this->generateLinkItem($data['currentPage'], $data['totalPage'], $data['path'], $eachSideCount, $pageName);
$env->load($template)->display(compact('data', 'links', 'showTotal', 'extraClass'));
}

public function links2(Environment $env, $data, $showTotal = true, $extraClass='', $eachSideCount = 3, $pageName = 'page', $template = 'paginate/links.html')
{
$links = $this->generateLinkItem2($data['currentPage'], $data['totalPage'], $data['path'], $eachSideCount, $pageName);
$env->load($template)->display(compact('data', 'links', 'showTotal', 'extraClass'));
}

private function generateLinkItem($currentPage, $totalPage, $path, $eachSideCount = 2, $pageName = 'page')
{
$totalCount = $eachSideCount * 2 + 1;
$start = $currentPage - $eachSideCount;
$end = $currentPage + $eachSideCount;
if ($totalPage <= $totalCount) {
$start = 1;
$end = $totalPage;
} else {
if ($currentPage <= $eachSideCount) {
$start = $currentPage - abs($eachSideCount - $currentPage - 1);
$end = $currentPage + $eachSideCount + ($eachSideCount - $currentPage + 1);
}

if ($currentPage + $eachSideCount > $totalPage) {
$start = $currentPage - $eachSideCount - ($currentPage + $eachSideCount - $totalPage);
$end = $currentPage + $eachSideCount - ($currentPage + $eachSideCount - $totalPage);
}
}
$links = [];

if ($totalPage > $totalCount) {
if ($currentPage > $eachSideCount + 1) {
$links[] = $this->generateLinkData('首页', $path, 1, $currentPage, false, null, $pageName);
}

$links[] = $this->generateLinkData("«", $path, ($currentPage == 1 ? 1 : ($currentPage - 1)), $currentPage, ($currentPage == 1 ? true : false), false, $pageName);
}


for ($i = $start; $i <= $end; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}

if ($totalPage > $totalCount) {
$links[] = $this->generateLinkData("»", $path, ($currentPage == $totalPage ? $totalPage : ($currentPage + 1)), $currentPage, ($currentPage == $totalPage ? true : false), false, $pageName);

if ($totalPage - $currentPage > 2) {
$links[] = $this->generateLinkData('末页', $path, $totalPage, $currentPage, false, null, $pageName);
}
}

return $links;
}


private function generateLinkItem2($currentPage, $totalPage, $path, $eachSideCount = 3, $pageName = 'page')
{
$totalCount = $eachSideCount * 2 + 6;
$start = ($currentPage - $eachSideCount < 1) ? 1 : $currentPage - $eachSideCount;
$end = ($currentPage + $eachSideCount > $totalPage) ? $totalPage : $currentPage + $eachSideCount;

$links = [];
$links[] = $this->generateLinkData("«", $path, ($currentPage == 1 ? 1 : ($currentPage - 1)), $currentPage, ($currentPage == 1 ? true : false), false, $pageName);

if ($totalPage <= $totalCount) {
$start = 1;
$end = $totalPage;
for ($i = $start; $i <= $end; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
} else {

if ($currentPage < $eachSideCount * 2 + 2) {
for ($i = 1; $i <= $eachSideCount * 2 + 2; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
$links[] = $this->generateLinkData('...', false, '', $currentPage, true, null, $pageName);
for ($i = $totalPage - 1; $i <= $totalPage; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
} else if ($currentPage > $totalPage - ($eachSideCount * 2 + 2)) {
for ($i = 1; $i <= 2; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
$links[] = $this->generateLinkData('...', false, '', $currentPage, true, null, $pageName);
for ($i = $totalPage - ($eachSideCount * 2 + 2); $i <= $totalPage; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
} else {
for ($i = 1; $i <= 2; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
$links[] = $this->generateLinkData('...', false, '', $currentPage, true, null, $pageName);
for ($i = $start; $i <= $end; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
$links[] = $this->generateLinkData('...', false, '', $currentPage, true, null, $pageName);
for ($i = $totalPage - 1; $i <= $totalPage; $i++) {
$links[] = $this->generateLinkData($i, $path, $i, $currentPage, false, null, $pageName);
}
}

}

$links[] = $this->generateLinkData("»", $path, ($currentPage == $totalPage ? $totalPage : ($currentPage + 1)), $currentPage, ($currentPage == $totalPage ? true : false), false, $pageName);

return $links;
}

private function generateLinkData($name, $path, $index, $currentPage, $isDisable = false, $isActive = null, $pageName = 'page')
{
return [
'name' => $name,
'url' => $path . (stripos('?', $path) ? '&' : '?') . $pageName . '=' . $index,
'isDisabled' => $isDisable,
'isActive' => $isActive === null ? (($index == $currentPage) ? true : false) : $isActive,
'isLink' => $path === false ? false : true,
];
}
}

好了,说一下重要的部分 ['needs_environment' => true] 我们需要加上这个参数,原因是我们在代码中使用twig实例生成模板。剩下的也没啥难度了,就是单纯的生成链接节点了。

好了,我们在贴一下模板部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div>
<ul class="pagination pagination-sm m-0 {{extraClass}}">
{% if showTotal %}
<li class="page-item disabled"><a class="page-link" href="#">共 {{data['totalCount']}} 条 {{data['totalPage']}} 页</a></li>
{% endif %}
{% for link in links %}
<li class="page-item{% if link['isDisabled'] %} disabled{% endif %}{% if link['isActive'] %} active{% endif %}">
{% if link['isLink'] %}
<a class="page-link" href="{{link['url']}}">{{link['name']}}</a>
{% else %}
<span class="page-link">{{link['name']}}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>

看,模板就这么少,麻烦的就是生成节点的部分了,需要好好考虑。这里面很多代码都是参考了 laravel 的 pagination 部分。

对了,分页部分我们也加了一些参数,我也一起贴上来好了

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
34
35
class CustomBuilder extends Builder
{

public function myPaginate($perPage = 15, $columns = ['*'])
{
// 获取页码
$page = Paginate::getPageNum();
// 调用 builder 自带的方法获取总条数
$results = ($total = $this->toBase()->getCountForPagination())
? $this->forPage($page, $perPage)->get($columns)
: $this->model->newCollection();

$totalPage = $total ? ceil($total / $page) : 0;

$path = $basePath = Paginate::getBasePath();
$queryParams = Paginate::getQueryParams();
unset($queryParams['page']);

$queryStr = http_build_query($queryParams);
if ($queryStr) {
$path .= '?' . $queryStr;
}

return [
'currentPage' => $page,
'perPage' => $perPage,
'totalCount' => $total,
'totalPage' => $totalPage,
'data' => $results,
'basePath' => $basePath,
'queryStr' => $queryStr,
'path' => $path,
];
}
}

增加的部分仅仅是提供生成模板的时候用的。

好了,分页至此通过两篇文章就都搞定了。代码还得优化,自己看的都难受,等以后优化后会继续写的。

最近一年私下里写东西一直都是在使用 slim 框架,其实说是一直在写东西,可是到现在真的一点输出都没有,不过小技巧倒是学会了不少,自己写个小验证器啊什么的,并且对于框架的一些理解也加深了,最好的是工作中很多都把这些小技巧用到了。

好了,吐槽了这么多,我又要说句废话了,写了一年的东西才写到分页你敢信?不过这是真的,写了一年了,代码终于累计300行了,可以进入分页的过程了。

以前用 laravel 的时候觉得 Eloquent 麻烦的很,麻烦不是指使用,而是指看代码麻烦,不过用起来还是很爽的。所以当我在使用 slim 需要连接数据库的时候毫不犹豫就继续用了 Eloquent。

当我写到分页的时候,发现调用方法失败,那么就继续引入 illuminate/pagination 好了。这下查询有问题不能正确获取页码,还有一些其他的参数也都是不对的,而且到前端的时候由于使用的模板是 twig,不能像 blade 模板那样在代码用调用 model 的相关方法,想要调用就得扩展 twig,那又是一桩麻烦事,既然如此,我就在代码中调用好了,然后在输出到模板中去,但是当我想偷懒使用 links 输出分页部分的时候报错了,在看报错,发现又得引入 blade 等一系列的东西,那引入了这么多,还不如直接使用 laravel 算了。所以,在使用 slim 不变的情况下,我们就自己扩展一个分页方法吧。

最初我是想继承 model 然后在里面写方法的,后来发现不行,不行的原因是,model代用的大部分方法其实都是在 new 一个 builder 之后链式调用的,这里我又得多说一句了奥,laravel 中同一个类名,会在很多个包下都有的,自己查看的时候要小心别看错了。(其实,集成 model 也是可以的,就是太麻烦了,而且没法链式调用)

所以在继承 model 行不通之后,我们就从 builder 入手,通过 google 和看代码可以找到这个方法

1
2
3
4
5
6
7
8
9
10
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
}

这个方法就是 model 里面产生新 builder 用的,那我们就在我们的 model 中 覆盖这个方法就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php


namespace App\Model;

use App\Model\Builder\CustomBuilder;
use Illuminate\Database\Eloquent\Model;

class Base extends Model
{
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new CustomBuilder($query);
}
}

注意代码里面的 builder 引用,别引用错了哦。

好了,CustomBuilder 就是我们自定义的 builder 了,

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
<?php

namespace App\Model\Builder;

use App\Utils\Paginate;
use Illuminate\Database\Eloquent\Builder;

class CustomBuilder extends Builder
{

public function myPaginate($perPage = 15, $columns = ['*'])
{
// 获取页码
$page = Paginate::getPageNum();
// 获取 总数 和 条目
$results = ($total = $this->toBase()->getCountForPagination())
? $this->forPage($page, $perPage)->get($columns)
: $this->model->newCollection();
// 计算总页数
$totalPage = $total ? ceil($total / $page) : 0;
// 输出
return (object)[
'currentPage' => $page,
'perPage' => $perPage,
'totalCount' => $total,
'totalPage' => $totalPage,
'data' => $results,
];
}
}

可以看到我们定义了一个 myPaginate 方法实现我们的分页,其实这么做的目的只有一个覆盖自带分页在输出时候的一些操作,输出我们想要的各种数据。接下来我们就可以在代码和模板的任何地方使用我们输出的数据,哪怕想实现 laravel 中 links 的方法,只要我们扩展一下 twig 就ok了,具体怎么做呢,可以等着看下一篇文章了。

更换了域名,需要弄很多东西,累死了,没错这篇文章唯一的图片就是通过这篇文章编译的工具上传成功的

项目地址

https://github.com/crazyhl/upyun-electron

初始准备

参照文章

https://electronjs.org/docs/tutorial/first-app#installing-electron

  1. 创建项目文件夹 upyun-electron
  2. 初始化项目 npm init
阅读全文 »

最近在做一些自己的小东西,因为没用 laravel,所以对感觉很多东西都没有 laravel 那么顺手,很多东西都得自己搞定才行,不过也正是因为这样,很多东西弄起来,更符合自己的需求了。

以前写表单请求的时候没感觉一个验证器会让我写代码的时候轻松一些,但是因为最近写的表单更多,我越来越发现一个验证器会让我少些多少代码,所以,我自己实现了一个简易的验证器。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<?php


namespace App\Validator;

use Slim\Http\Request;

/**
* 验证器的根类
* Class AbstractValidator
* @package App\Validator
*/
abstract class AbstractValidator
{
/**
* @var Request
*/
protected $request;
/**
* @var array
*/
protected $extraParams;

public function __construct()
{
}

/**
* 保存条件的数组
* @var array
* rules 的 key 是验证的变量名称
* rules 的 value 是个数组
* value 可包含 type type是声明变量的类型,可包含 string | integer | float | double | array | regex 其他的需要用到的时候在添加
* 不同的类型会依赖后续不同的参数
* value 包含 require 这个类型是 true | false, 如果是 true 就是必填项,如果请求参数不包含这个就会返回 false,默认 false
* value 可包含 message 出错的提示信息,如果出错没有 message 的时候就考大家在 validation 里面自己实现默认的消息了,
* 如果包含 type 键,这个 message 就是 type 出错的 message,如果没有 type 这个就可以是 require 为 true 的错误消息
* value 可包含 requireMessage 这个是 require 为 true 时候的出错消息,如果这个没有,就返回默认的信息 'xxx必须填写'
*/
abstract public function rules();

public function validation($request, $extraParams)
{
$this->request = $request;
$this->extraParams = $extraParams;

return $this->doValidation();
}

/**
* 执行验证流程
*/
public function doValidation()
{
$rules = $this->rules();

// 获取请求方法
$requestMethod = $this->request->getMethod();
// 获取参数,如果是get就直接获取get参数,如果是其他请求方法
// 则优先获取post参数,如果不存在在获取get参数,如果依然不存在
// 就获取 extraParams
foreach ($rules as $paramName => $rule) {
$rule = (array)$rule;
$falseReturn = [false, array_key_exists('message', $rule) ? $rule['message'] : null];

$value = $this->request->getParsedBodyParam($paramName);

if (empty($value) && strtoupper($requestMethod) !== 'GET') {
$value = $this->request->getQueryParam($paramName);
}


if (array_key_exists($paramName, $this->extraParams)) {
$value = $this->extraParams[$paramName];
}

if ($rule['require'] == true && empty($value)) {
return [
false,
array_key_exists('requireMessage', $rule)
? $rule['requireMessage']
: (array_key_exists('message', $rule) ? $rule['message'] : $paramName . '必须填写')
];
}

// 如果什么都不存在就返回
if (empty($value)) {
continue;
}

if (!array_key_exists('requireMessage', $rule)) {
$rule['type'] = 'string';
}

switch ($rule['type']) {
case 'string':
if ($rule['length']) {
// 字符串情况下是长度必须相等
$length = $rule['length'];
if ($length !== mb_strlen($value)) {
return $falseReturn;
}
} else if ($rule['min'] || $rule['max']) {
// 字符串情况下是最大最小长度
if (array_key_exists('min', $rule) && mb_strlen($value) < $rule['min']) {
return $falseReturn;
}

if (array_key_exists('max', $rule) && mb_strlen($value) > $rule['max']) {
return $falseReturn;
}
}

break;
case 'integer':
if (!is_numeric($value) || ($value + 0) !== intval($value)) {
return $falseReturn;
}

$value = intval($value);

if ($rule['length']) {
// 数字的情况下比较数字是否相同
if ($value !== $rule['length']) {
return $falseReturn;
}
} else if ($rule['min'] || $rule['max']) {
// 这种情况下是区间
if (array_key_exists('min', $rule) && $value < $rule['min']) {
return $falseReturn;
}

if (array_key_exists('max', $rule) && $value > $rule['max']) {
return $falseReturn;
}
} else if ($rule['list'] && is_array($rule['list']) && !in_array($value, $rule['list'])) {
// list 代表一个数组,数字不在数组里面就报错
return $falseReturn;
}
break;
case 'float':
case 'double':
if (!is_numeric($value) || ($value + 0) !== floatval($value)) {
return $falseReturn;
}
$value = floatval($value);

if ($rule['length']) {
// 数字的情况下比较数字是否相同
if ($value !== $rule['length']) {
return $falseReturn;
}
} else if ($rule['min'] || $rule['max']) {
// 这种情况下是区间
if (array_key_exists('min', $rule) && $value < $rule['min']) {
return $falseReturn;
}

if (array_key_exists('max', $rule) && $value > $rule['max']) {
return $falseReturn;
}
} else if ($rule['list'] && is_array($rule['list']) && !in_array($value, $rule['list'])) {
// list 代表一个数组,数字不在数组里面就报错
return $falseReturn;
}
break;
case 'regex':
// 正则
if (!preg_match('~' . $rule['pattern'] . '~iu', $value)) {
return $falseReturn;
}
break;
case 'array':
// 数组
if (!is_array($value)) {
return $falseReturn;
}

if ($rule['length']) {
// 数组长度
if ($value !== $rule['length']) {
return $falseReturn;
}
} else if ($rule['min'] || $rule['max']) {
// 数组长度
if (array_key_exists('min', $rule) && $value < $rule['min']) {
return $falseReturn;
}

if (array_key_exists('max', $rule) && $value > $rule['max']) {
return $falseReturn;
}
}
break;
}
}

return [true, ''];
}
}

这次写代码用的是 slim 框架,所以一部分的类依赖我就使用了 slim 的相关类。弄好了这个之后写表单使用起来就简易多了,写一个类继承这个 AbstractValidator 类,只需实现一个方法 rules, 这个方法返回一个数组,一个需要验证的表单数组就ok了。

在验证的地方,调用 validation 方法就可以了,在返回失败的时候实现自己的返回失败逻辑就OK了。实例如下

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
<?php


namespace App\Validator;


class TestValidator extends AbstractValidator
{

/**
* 保存条件的数组
* @var array
* rules 的 key 是验证的变量名称
* rules 的 value 是个数组
* value 可包含 type type是声明变量的类型,可包含 string | integer | float | double | array | regex 其他的需要用到的时候在添加
* 不同的类型会依赖后续不同的参数
* value 包含 require 这个类型是 true | false, 如果是 true 就是必填项,如果请求参数不包含这个就会返回 false,默认 false
* value 可包含 message 出错的提示信息,如果出错没有 message 的时候就考大家在 validation 里面自己实现默认的消息了,
* 如果包含 type 键,这个 message 就是 type 出错的 message,如果没有 type 这个就可以是 require 为 true 的错误消息
* value 可包含 requireMessage 这个是 require 为 true 时候的出错消息,如果这个没有,就返回默认的信息 'xxx必须填写'
*/
public function rules()
{
// TODO: Implement rules() method.
return [
'field1' => [
'require' => true,
],
];
}
}

上面这个类就是我们自己的 validator, 下面就是我们的验证逻辑部分, 在 slim 中 只要实现一个 中间件就可以了,如下

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php
/**
* Created by PhpStorm.
* User: haoliang
* Date: 2019-03-13
* Time: 15:20
*/

namespace App\Middleware;


use App\Controller\User;
use App\Utils\JWT;
use App\Validator\AbstractValidator;
use Jose\Component\Core\Converter\StandardConverter;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
use App\Model\User as UserModel;

/**
* 检测菜单权限的中间件
* Class CheckUrl
* @package App\Middleware
*/
class ValidateMiddleware
{
/**
* @var AbstractValidator $validator ;
*/
private $validator;
/**
* @var Container $container ;
*/
private $container;

public function __construct(Container $container, AbstractValidator $validator)
{
$this->container = $container;
$this->validator = $validator;
}


/**
* @param Request $request
* @param Response $response
* @param $next
* @return Response
*/
public function __invoke(Request $request, Response $response, $next)
{
$routeParams = $request->getAttribute('routeInfo')[2];
$this->container->get('logger')->info(json_encode($routeParams));

list($result, $message) = $this->validator->validation($request, $routeParams);
$this->container->get('logger')->info($result . ' '. $message);

if ($result) {
$response = $next($request, $response);
} else {
$response = $response->withJson([
'status' => -19999,
'message' => $message,
'data' => '',
]);
}

return $response;
}
}

在 slim 中,就是在 __invoke 方法中实现验证逻辑就ok了。

这次实现的比较简单,我自己都觉得还有很大的优化余地,慢慢优化ing。

最近使用 vue 的场景比较多,所以体会也就更多了,以前用过但是没有很明确印象的,也就发现的更多了,所以现在决定都记录下来。以后找起来也方便,记忆也能加深。

这次动态路由用的比较多,所以在相同路由来回跳转的时候,url虽然跳转了,但是因为组件复用问题,没有调用vue的生命周期函数,导致数据没有刷新的问题也就明显的遇到的次数多了,vue-router 提供了两种解决方案,一种是 watch $router对象的变化,然后在变化的时候去刷新数据,另一种是调用 beforeRouteUpdate 设个导航守卫,昨天在弄这块的时候我还思考了一波 next 方法的调用时机,是先跳转在加载数据还是先加载数据在跳转呢?今天也算是想通了吧,在不同路由间跳转到的时候我们会因为用户体验问题,而去处理先加载数据在渲染页面,或者先渲染页面在加载数据。但是呢,这个 beforeRouteUpdate 是组件内的守卫方法,我们看下 vue-router 的说明:

beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 this
},

通过说明我们可以知道了,这个就是在自己组建跳转的时候才会触发,而next就是处理url变化的方法,所以在next前后处理就很随意了。想怎么整就怎么整。不过如果需要对跳转进行更多的控制的时候还是需要注意顺序的了。

阅读全文 »