强制转换值意味着将其更改为(或确保已经)特定类型。
您可能熟悉的某些类型是Integer或Boolean。
简而言之,类型转换是一种将实体从一种数据类型更改为另一种数据类型的方法。
我为什么要关心类型转换:
因此,当您将数字存储在数据库中时-默认情况下,它们将以字符串形式返回。
但是类型转换允许您将它们转换为整数,实数,浮点数或布尔值,可以将数据库中的1和0转换为true和false
我们开始做吧
因此,在您的模型中,您可以编写:
protected $casts = [
'status' => 'boolena',
]
它将status(1,0)转换为true或false。
因此,当您从表格中获取任何数据时,它将状态列转换为true或false。
您不需要编写任何类似这样的逻辑:
if($post-> status == 1){
//显示状态为活动
}else{
/显示状态为无效
}
数组和JSON转换
此外,如果表包含存储序列化JSON字符串的列,并且您希望在Eloquent模型上访问该特定列时将该属性自动反序列化为PHP数组,则应使用数组强制转换。
您可以这样做:
<?php
namespace App\Model\Post;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ['title'];
protected $casts = [
'tags' => 'array',
];
}
因此,您可以将JSON标签数据存储到post表中,但是在获取帖子时,您可以将其自动转换为PHP数组,因此现在您可以避免创建tags表。
结论
如您所见,雄辩的属性转换具有将我们从不必要的重复逻辑中解放出来的巨大潜力,并且还使我们可以轻松地将数据存储在JSON中。
转:https://dev.to/ajayyadavexpo/type-casting-in-laravel-4npm
观察者模式描述:
Laravel 的 Event 事件系统提供了一个简单的观察者模式实现,能够订阅和监听应用中发生的各种事件,在 PHP 的标准库 (SPL) 里甚至提供了三个接口 SplSubject, SplObserver, SplObjectStorage 来让开发者更容易地实现观察者模式,不过我还是想脱离 SPL 提供的接口和特定编程语言来说一下如何通过面向对象程序设计来实现观察者模式,示例是 PHP 代码不过用其他面向对象语言实现起来也是一样的。
模式定义:
1.观察者模式 (Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
2.观察者模式又叫做发布 - 订阅(Publish/Subscribe)模式、模型 - 视图(Model/View)模式、源 - 监听器(Source/Listener)模式或从属者(Dependents)模式。
3.观察者模式的核心在于 Subject 和 Observer 接口,Subject (主题目标) 包含一个给定的状态,观察者 “订阅” 这个主题,将主题的当前状态通知观察者,每次给定状态改变时所有观察者都会得到通知。
4.发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
模式结构说明:
Subject 目标抽象类
ConcreteSubject 具体目标
Observer 观察者抽象类
ConcreteObserver 具体观察者
应用举例:
比如在设置用户 (主题) 的状态后要分别发送当前的状态描述信息给用户的邮箱和手机,我们可以使用两个观察者订阅用户的状态,一旦设置状态后主题就会通知的订阅了状态改变的观察者,在两个观察者里面我们可以分别来实现发送邮件信息和短信信息的功能。
1.抽象目标类
abstract class Subject
{
protected $stateNow;
protected $observers = [];
public function attach(Observer $observer)
{
array_push($this->observers, $observer);
}
public function detach(Observer $ob)
{
$pos = 0;
foreach ($this->observers as $viewer) {
if ($viewer == $ob) {
array_splice($this->observers, $pos, 1);
}
$pos++;
}
}
public function notify()
{
foreach ($this->observers as $viewer) {
$viewer->update($this);
}
}
}
在抽象类中 attach detach 和 notify 都是具体方法,这些是继承才能使用的方法,将由 Subject 的子类使用。
2.具体目标类
class ConcreteSubject extends Subject
{
public function setState($state)
{
$this->stateNow = $state;
$this->notify();
}
public function getState()
{
return $this->stateNow;
}
}
3.抽象观察者
abstract class Observer
{
abstract public function update(Subject $subject);
}
在抽象观察者中,抽象方法 update 等待子类为它提供一个特定的实现。
4.具体观察者
class ConcreteObserverDT extends Observer
{
private $currentState;
public function update(Subject $subject)
{
$this->currentState = $subject->getState();
echo '<div style="font-size:10px;">'. $this->currentState .'</div>';
}
}
class ConcreteObserverPhone extends Observer
{
private $currentState;
public function update(Subject $subject)
{
$this->currentState = $subject->getState();
echo '<div style="font-size:20px;">'. $this->currentState .'</div>';
}
}
在例子中为了理解起来简单,我们只是根据不同的客户端设置了不同的内容样式,实际应用中可以真正的调用邮件和短信服务来发送信息。
5.使用观察者模式
class Client
{
public function __construct()
{
$sub = new ConcreteSubject();
$obDT = new ConcreteObserverDT();
$obPhone = new ConcreteObserverPhone();
$sub->attach($obDT);
$sub->attach($obPhone);
$sub->setState('Hello World');
$sub->notify();
}
}
$worker = new Client();
何时使用观察者模式:
1.一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
2.一个对象必须通知其他对象,而并不知道这些对象是谁。
3.基于事件触发机制来解耦复杂逻辑时,从整个逻辑的不同关键点抽象出不同的事件,主流程只需要关心最核心的逻辑并能正确地触发事件 (Subject),其余相关功能实现由观察者或者叫订阅者来完成。
总结:
1.观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
2.模式包含四个角色:目标又称为主题,它是指被观察的对象;具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者将对观察目标的改变做出反应;在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
3.观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
转:https://learnku.com/docs/laravel-kernel/design-mode-observer-mode/6919
装饰模式描述:
装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象添加功能。通常有两种方式可以实现给一个类或对象增加行为:
1.继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
2.组合机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器 (Decorator)。
显然,为了扩展对象功能频繁修改父类或者派生子类这种方式并不可取。在面向对象的设计中,我们应该尽量使用对象组合,而不是对象继承来扩展和复用功能。装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能。装饰器模式的本质就是动态组合。动态是手段,组合才是目的。总之,装饰模式是通过把复杂的功能简单化,分散化,然后在运行期间,根据需要来动态组合的这样一个模式。
装饰模式定义:
装饰模式 (Decorator Pattern) :动态地给一个对象增加一些额外的职责 (Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器 (Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为 “油漆工模式”,它是一种对象结构型模式。
装饰模式的优点:
1.装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
2.可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
3.通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
模式结构和说明:
1.聚合关系用一条带空心菱形箭头的直线表示,上图表示 Component 聚合到 Decorator 上,或者说 Decorator 由 Component 组成。
2.继承关系用一条带空心箭头的直接表示
3.看懂 UML 类图请看这个文档:https://designpatterns.readthedocs.io/zh_CN/latest/read_uml.html
Component:
组件对象的接口,可以给这些对象动态的添加职责;
ConcreteComponent:
具体的组件对象,实现了组件接口。该对象通常就是被装饰器装饰的原始对象,可以给这个对象添加职责;
Decorator:
所有装饰器的父类,需要定义一个与 Component 接口一致的接口 (主要是为了实现装饰器功能的复用,即具体的装饰器 A 可以装饰另外一个具体的装饰器 B,因为装饰器类也是一个 Component),并持有一个 Component 对象,该对象其实就是被装饰的对象。如果不继承 Component 接口类,则只能为某个组件添加单一的功能,即装饰器对象不能再装饰其他的装饰器对象。
ConcreteDecorator:
具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另外一个具体的装饰器对象。
装饰器的示例代码:
1.Component 抽象类,可以给这些对象动态的添加职责
abstract class Component
{
abstract public function operation();
}
2.Component 的实现类
class ConcreteComponent extends Component
{
public function operation()
{
echo __CLASS__ . '|' . __METHOD__ . "\r\n";
}
}
3. 装饰器的抽象类,维持一个指向组件对象的接口对象, 并定义一个与组件接口一致的接口
abstract class Decorator extends Component
{
/**
* 持有Component的对象
*/
protected $component;
/**
* 构造方法传入
*/
public function __construct(Component $component)
{
$this->component = $component;
}
abstract public function operation();
}
4. 装饰器的具体实现类,向组件对象添加职责,beforeOperation (),afterOperation () 为前后添加的职责。
class ConcreteDecoratorA extends Decorator
{
//在调用父类的operation方法的前置操作
public function beforeOperation()
{
echo __CLASS__ . '|' . __METHOD__ . "\r\n";
}
//在调用父类的operation方法的后置操作
public function afterOperation()
{
echo __CLASS__ . '|' . __METHOD__ . "\r\n";
}
public function operation()
{
$this->beforeOperation();
$this->component->operation();//这里可以选择性的调用父类的方法,如果不调用则相当于完全改写了方法,实现了新的功能
$this->afterOperation();
}
}
class ConcreteDecoratorB extends Decorator
{
//在调用父类的operation方法的前置操作
public function beforeOperation()
{
echo __CLASS__ . '|' . __METHOD__ . "\r\n";
}
//在调用父类的operation方法的后置操作
public function afterOperation()
{
echo __CLASS__ . '|' . __METHOD__ . "\r\n";
}
public function operation()
{
$this->beforeOperation();
$this->component->operation();//这里可以选择性的调用父类的方法,如果不调用则相当于完全改写了方法,实现了新的功能
$this->afterOperation();
}
}
5. 客户端使用装饰器
class Client
{
public function main()
{
$component = new ConcreteComponent();
$decoratorA = new ConcreteDecoratorA($component);
$decoratorB = new ConcreteDecoratorB($decoratorA);
$decoratorB->operation();
}
}
$client = new Client();
$client->main();
6. 运行结果
oncreteDecoratorB|ConcreteDecoratorB::beforeOperation
ConcreteDecoratorA|ConcreteDecoratorA::beforeOperation
ConcreteComponent|ConcreteComponent::operation
ConcreteDecoratorA|ConcreteDecoratorA::afterOperation
ConcreteDecoratorB|ConcreteDecoratorB::afterOperation
装饰模式需要注意的问题:
1.一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
2.尽量保持具体组件类 ConcreteComponent 的轻量,不要把主逻辑之外的辅助逻辑和状态放在具体组件类中,可以通过装饰类对其进行扩展。 如果只有一个具体组件类而没有抽象组件类,那么抽象装饰类可以作为具体组件类的直接子类。
适用环境:
1.需要在不影响组件对象的情况下,以动态、透明的方式给对象添加职责。
2.当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时可以考虑使用装饰类。
转:https://learnku.com/docs/laravel-kernel/design-mode-decoration-mode/6918
外观模式描述:
外观模式 (Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
Laravel 中我们常用到的 Route、Redis、Auth 这些 Facade 就是外观模式的具体实现, 在 Laravel 中设计了多个外观类,每个外观类继承自统一的抽象外观类,在抽象外观类里提供了通过外观类访问其背后子系统的基础方法。
对于新的业务需求,不要修改原有外观类,而应该增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
下面是一个简单的外观模式的例子,并没有引入抽象外观类,在介绍 Laravel Facade 的文章中我们会看到 Laravel 里提供了一个抽象外观类从而让我们能够方便的根据需要增加新子系统的外观类,并让外观类能够正确代理到其对应的子系统 (或者叫服务)。
模式结构:
外观模式包含如下角色:
1.Facade 外观角色
2.SubSystem 子系统角色
代码示例
<?php
class Client
{
public function main()
{
(new Facade)->operation();
}
}
class Facade
{
private $systemA;
private $systemB;
public function __construct()
{
$this->systemA = new SystemA;
$this->systemB = new SystemB;
}
public function operation()
{
$this->systemA->operationA();
$this->systemB->operationB();
}
}
class SystemA
{
public function operationA()
{
//
}
}
class SystemB
{
public function operationB()
{
//
}
}
模式分析:
根据 “单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。 - 外观模式也是 “迪米特法则” 的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。 - 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。 - 外观模式的目的在于降低系统的复杂程度。 - 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
外观模式的缺点:
不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了 “开闭原则”。
模式扩展:
1.一个系统有多个外观类
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。
2.不要试图通过外观类为子系统增加新行为
不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。
3.抽象外观类的引入
外观模式最大的缺点在于违背了 “开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
总结:
1.在外观模式中,外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
2.外观模式包含两个角色:外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的 (一个或者多个) 子系统的功能和责任,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理;在软件系统中可以同时有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能。
3.外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
4.外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程;其缺点在于不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了 “开闭原则”。
5.外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。
转:https://learnku.com/docs/laravel-kernel/design-mode-appearance-mode/6917
Laravel的这些用法,你用的上没?
1. 在 find 方法中指定属性
User::find(1, ['name', 'email']);
User::findOrFail(1, ['name', 'email']);
2. Clone 一个 Model
用 replicate 方法可以克隆一个 Model
$user = User::find(1);
$newUser = $user->replicate();
$newUser->save();
3. 判断两个 Model 是否相同
检查两个 Model 的 ID 是否相同用 is 方法
$user = User::find(1);
$sameUser = User::find(1);
$diffUser = User::find(2);
$user->is($sameUser); // true
$user->is($diffUser); // false;
4. 重新加载一个 Model
$user = User::find(1);
$user->name; // 'Peter'
// 如果 name 更新过,比如由 peter 更新为 John
$user->refresh();
$user->name; // John
5. 加载新的 Model
$user = App\User::first();
$user->name; // John
//
$updatedUser = $user->fresh();
$updatedUser->name; // Peter
$user->name; // John
6. 更新带关联的 Model
在更新关联的时候,使用 push 方法可以更新所有 Model
class User extends Model
{
public function phone()
{
return $this->hasOne('App\Phone');
}
}
$user = User::first();
$user->name = "Peter";
$user->phone->number = '1234567890';
$user->save(); // 只更新 User Model
$user->push(); // 更新 User 和 Phone Model
7. 自定义软删除字段
Laravel 默认使用 deleted_at 作为软删除字段,我们通过以下方式将 deleted_at 改成 is_deleted
class User extends Model
{
use SoftDeletes;
* deleted_at 字段.
*
* @var string
*/
const DELETED_AT = 'is_deleted';
}
或者使用访问器
class User extends Model
{
use SoftDeletes;
public function getDeletedAtColumn(){
return 'is_deleted';
}
}
8. 查询 Model 更改的属性
$user = User::first();
$user->name; // John
$user->name = 'Peter';
$user->save();
dd($user->getChanges());
// 输出:
[
'name' => 'John',
'updated_at' => '...'
]
9. 查询 Model 是否已更改
$user = User::first();
$user->name; // John
$user->isDirty(); // false
$user->name = 'Peter';
$user->isDirty(); // true
$user->getDirty(); // ['name' => 'Peter']
$user->save();
$user->isDirty(); // false
getChanges () 与 getDirty () 的区别
getChanges () 方法用在 save () 方法之后输出结果集
getDirty () 方法用在 save () 方法之前输出结果集
10. 查询修改前的 Model 信息
$user = App\User::first();
$user->name; //John
$user->name = "Peter"; //Peter
$user->getOriginal('name'); //John
$user->getOriginal(); //Original $user record
转:https://learnku.com/articles/53700
因为要用Elasticsearch测试一下数据,所以在windows7电脑上搭建一回,顺便记录一下增加一篇博文
开始准备必要条件:
1.elasticsearch-7.9.3-windows-x86_64.zip,我在官网下载最新的,几k每秒捶胸口,最后在是在群里问群友要的;
2.laravel8,已经装好,phpStudy环境已跑起,用的是博客laravel8仿栏目的环境,有兴趣的看一下我之前的博文有记录
一.把Elasticsearch跑起来,解压直接运行文件:D:\elasticsearch-7.9.3\bin\elasticsearch.bat
cmd.exe
...
}, term: 5, version: 51, reason: Publication{term=5, version=51}
[2020-11-27T13:51:08,003][INFO ][o.e.h.AbstractHttpServerTransport] [WD2VNW3O8RK
H2QI] publish_address {127.0.0.1:9200}, bound_addresses {127.0.0.1:9200}, {[::1]
:9200}
[2020-11-27T13:51:08,004][INFO ][o.e.n.Node ] [WD2VNW3O8RKH2QI] st
arted
[2020-11-27T13:51:08,155][INFO ][o.e.l.LicenseService ] [WD2VNW3O8RKH2QI] li
cense [09bedffd-1635-497c-889b-0efb6071476b] mode [basic] - valid
[2020-11-27T13:51:08,158][INFO ][o.e.x.s.s.SecurityStatusChangeListener] [WD2VNW
3O8RKH2QI] Active license is now [BASIC]; Security is disabled
[2020-11-27T13:51:08,167][INFO ][o.e.g.GatewayService ] [WD2VNW3O8RKH2QI] re
covered [2] indices into cluster_state
[2020-11-27T13:51:09,216][INFO ][o.e.c.r.a.AllocationService] [WD2VNW3O8RKH2QI]
Cluster health status changed from [RED] to [YELLOW] (reason: [shards started [[
name][0]]]).
启动完:
二.安装elasticsearch扩展,我这直接安装最新的,注意composer的时候记得转阿里云镜像源,不然要卡死
composer require elasticsearch/elasticsearch
三.laravel8中配置文件 .env、config/database.php
1. .env 在底部添加
ES_HOSTS=127.0.0.1:9200
2. config/database.php 也在底部添加
'elasticsearch' => [
// Elasticsearch 支持多台服务器负载均衡,因此这里是一个数组
'hosts' => explode(',', env('ES_HOSTS')),
]
四.将elasticsearch对象注入到laravel容器中 在laravel项目文件
App\Provider\AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; //add fixed sql
use Elasticsearch\ClientBuilder as ESClientBuilder;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
* @return void
*/
public function register()
{
// 注册一个名为 es 的单例
$this->app->singleton('es', function () {
// 从配置文件读取 Elasticsearch 服务器列表
$builder = ESClientBuilder::create()->setHosts(config('database.elasticsearch.hosts'));
// 如果是开发环境
if (app()->environment() === 'local') {
// 配置日志,Elasticsearch 的请求和返回数据将打印到日志文件中,方便我们调试
$builder->setLogger(app('log')->driver());
}
return $builder->build();
});
}
/**
* Bootstrap any application services.
* @return void
*/
public function boot()
{
//
Schema::defaultStringLength(191); //add fixed sql
}
}
五.laravel项目中使用elasticsearch
1.routes\api.php文件中添加 (路由功能描述在控制器里面)
//测试路由 Elasticsearch
use App\Http\Controllers\API\TestController;
Route::get('test', [TestController::class, 'test']);
Route::get('testinsert', [TestController::class, 'testinsert']);
Route::get('testmysqllist', [TestController::class, 'testmysqllist']);
2.添加测试控制器文件 app\Http\Constrollers\API\TestController.php
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use Elasticsearch\ClientBuilder;
class TestController extends Controller
{
public $client = null;
public function __construct() {
$this-> client = ClientBuilder::create()->build();
}
//根据索引名(表名),跟id取出数据
public function test()
{
$a = app('es')->get(['index' => 'name', 'id' => 2]);
dd($a);
}
//从数据库表中取出数据 对应库表 存入Elasticsearch中(当然我这是测试就没有对应库表)
public function testinsert()
{
$id = 2;
$info = User::where(['id' => 2])->first()->toArray();
$params = [
'index' => 'name',
'type' => 'title',
'id' => $id,
'body' => ['testField' => $info]
];
$response = $this->client->index($params);
dd($response);
}
//打印Elasticsearch中所有索引数据
public function testmysqllist()
{
$client = ClientBuilder::create()->build();
$data = [
'type' => 'title'
];
$response = $client->search($data);
dd($response['hits']['hits']);
return $response['hits']['hits'];
}
}
3.看看 testmysqllist方法的 效果
array:4 [▼
0 => array:5 [▼
"_index" => "name"
"_type" => "title"
"_id" => "3"
"_score" => 1.0
"_source" => array:1 [▼
"testField" => array:10 [▼
"id" => 3
"name" => "站长二号"
"email" => "5142245@qq.com"
"email_verified_at" => null
"current_team_id" => null
"profile_photo_path" => null
"created_at" => "2020-09-30T06:00:25.000000Z"
"updated_at" => "2020-09-30T06:00:25.000000Z"
"subscribe" => 0
"profile_photo_url" => "https://ui-avatars.com/api/?name=%E7%AB%99%E9%95%BF%E4%BA%8C%E5%8F%B7&color=7F9CF5&background=EBF4FF"
]
]
]
1 => array:5 [▼
"_index" => "name"
"_type" => "title"
"_id" => "2"
"_score" => 1.0
"_source" => array:1 [▼
"testField" => array:10 [▼
"id" => 2
"name" => "站长小号"
"email" => "houtizong@dingtalk.com"
"email_verified_at" => null
"current_team_id" => null
"profile_photo_path" => null
"created_at" => "2020-09-30T06:00:25.000000Z"
"updated_at" => "2020-09-30T06:00:25.000000Z"
"subscribe" => 1
"profile_photo_url" => "https://ui-avatars.com/api/?name=%E7%AB%99%E9%95%BF%E5%B0%8F%E5%8F%B7&color=7F9CF5&background=EBF4FF"
]
]
]
2 => array:5 [▼
"_index" => "name1"
"_type" => "title"
"_id" => "1"
"_score" => 1.0
"_source" => array:1 [▶]
]
]
六.cmd.exe中测试
D:\phpStudy\WWW\larabg>php artisan tinker
Psy Shell v0.10.4 (PHP 7.3.22 — cli) by Justin Hileman
>>> app('es')->info();
=> [
"name" => "WD2VNW3O8RKH2QI",
"cluster_name" => "elasticsearch",
"cluster_uuid" => "dupYVfwPSPiDMFoDg3fWlg",
"version" => [
"number" => "7.9.3",
"build_flavor" => "default",
"build_type" => "zip",
"build_hash" => "c4138e51121ef06a6404866cddc601906fe5c868",
"build_date" => "2020-10-16T10:36:16.141335Z",
"build_snapshot" => false,
"lucene_version" => "8.6.2",
"minimum_wire_compatibility_version" => "6.8.0",
"minimum_index_compatibility_version" => "6.0.0-beta1",
],
"tagline" => "You Know, for Search",
]
>>> exit;
Exit: Goodbye
>>> app('es')->get(['index' => 'name', 'id' => 1])
Elasticsearch/Common/Exceptions/Missing404Exception with message '{"_index":"nam
e","_type":"_doc","_id":"1","found":false}'
>>> app('es')->get(['index' => 'name', 'id' => 2])
=> [
"_index" => "name",
"_type" => "_doc",
"_id" => "2",
"_version" => 2,
"_seq_no" => 9,
"_primary_term" => 2,
"found" => true,
"_source" => [
"testField" => [
"id" => 2,
"name" => "站长å°å·",
"email" => "houtizong@dingtalk.com",
"email_verified_at" => null,
"current_team_id" => null,
"profile_photo_path" => null,
"created_at" => "2020-09-30T06:00:25.000000Z",
"updated_at" => "2020-09-30T06:00:25.000000Z",
"subscribe" => 1,
"profile_photo_url" => "https://ui-avatars.com/api/?name=%E7%AB%99%E9%9
5%BF%E5%B0%8F%E5%8F%B7&color=7F9CF5&background=EBF4FF",
],
],
]
一、运行原理描述
laravel的入口文件 index.php
1、引入自动加载 autoload.php
2、创建应用实例,并同时完成了:
基本绑定($this、容器类Container等等)、
基本服务提供者的注册(Event、log、routing)、
核心类别名的注册(比如db、auth、config、router等)
3、开始Http请求的处理
make方法从容器中解析指定的值为实际的类,比如$app->make(Illuminate\Contracts\Http\Kernel::class) 解析出 App\Http\Http.php
handle方法对http请求进行处理
实际上是handle中的sendRequestThroughRouter处理的http请求
首先,将request绑定到共享实例
然后执行bootstarp方法,运行给定的引导类数组$bootstrappers,这里很关键,包括了加载配置文件、环境变量、服务提供者(config/app.php中的providers)、门面、异常处理、引导提供者
之后,进入管道模式,经过中间件的处理过滤后,再进行用户请求的分发
在请求分发时,首先,查找与给定请求匹配的路由,然后执行runRoute方法,实际处理请求的是runRoute 方法中的runRouteWithinStack
然后,经过runRouteWithinStack中的run方法,将请求分配到实际的控制器中,并得到响应结果
4、将处理结果返回
二、详细源码分析
1、注册自动加载器,实现文件的自动加载
require __dir__.'/../vendor/autoload.php';
2、创建应用容器实例Application(该实例继承自容器类Container),并绑定核心(web、命令行、异常),以便在需要时解析它们
$app = require_once __DIR__.'/../bootstrap/app.php';
#app.php
<?php
// 创建Laravel实例 【3】
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// 绑定Web端kernel
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// 绑定命令行kernel
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
// 绑定异常处理kernel
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
// 返回应用实例
return $app;
3、在创建应用实例(Application.php)的构造函数中,将基本绑定注册到容器中,并注册了所有的基本服务提供者,以及在容器中注册核心类别名
Application.php
public function __construct($basePath = null)
{
// 将基本绑定注册到容器中【3.1】
$this->registerBaseBindings();
// 注册所有基本服务提供者【3.2】
$this->registerBaseServiceProviders();
// 在容器中注册核心类别名【3.3】
$this->registerCoreContainerAliases();
}
3.1、将基本绑定注册到容器中
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
# 注:instance方法为将...注册为共享实例,singleton方法为将...注册为共享绑定
3.2、注册所有基本服务提供者(事件、日志、路由)
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
3.3、在容器中注册核心类别名
4、上面完成了类的自动加载、服务提供者注册、核心类的绑定、以及基本注册的绑定
5、开始解析http请求
index.php
// 5.1
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 5.2
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
5.1 make方法是从容器解析给定值
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
中的Illuminate\Contracts\Http\Kernel::class 是在index.php 中的$app = require_once __DIR__.'/../bootstrap/app.php';这里面进行绑定的,实际指向的就是App\Http\Kernel::class这个类
5.2 这里对http请求进行处理
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
进入$kernel所代表的类App\Http\Kernel.php中,我们可以看到其实里面只是定义了一些中间件相关的内容,并没有handle方法
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces the listed middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}
因此,我们再到它的父类use Illuminate\Foundation\Http\Kernel as HttpKernel;中找handle方法,可以看到handle方法是这样的
public function handle($request)
{
try {
// 方法欺骗,不用关注这里
$request->enableHttpMethodParameterOverride();
// 最核心的处理http请求的地方【6】
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
6、处理http请求(将request绑定到共享实例,并使用管道模式处理用户请求)
vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php的handle方法
// 最核心的处理http请求的地方
$response = $this->sendRequestThroughRouter($request);
进入sendRequestThroughRouter方法,
protected function sendRequestThroughRouter($request)
{
// 将请求$request绑定到共享实例
$this->app->instance('request', $request);
// 将请求request从已解析的门面实例中清除(因为已经绑定到共享实例中了,没必要再浪费资源了)
Facade::clearResolvedInstance('request');
// 引导应用程序进行HTTP请求
$this->bootstrap();【7、8】
// 进入管道模式,经过中间件,然后处理用户的请求【9、10】
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
7、在bootstrap方法中,运行给定的引导类数组$bootstrappers,加载配置文件、环境变量、服务提供者、门面、异常处理、引导提供者,非常重要的一步
位置在vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
/**
* Bootstrap the application for HTTP requests.
*
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
/**
* 运行给定的引导类数组
*
* @param string[] $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
/**
* Get the bootstrap classes for the application.
*
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}
/**
* 应用程序的引导类
*
* @var array
*/
protected $bootstrappers = [
// 加载环境变量
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// 加载config配置文件【重点】
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
// 加载异常处理
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
// 加载门面注册
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
// 加载在config/app.php中的providers数组里所定义的服务【8 重点】
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
// 记载引导提供者
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
8、加载config/app.php中的providers数组里所定义的服务
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
/**
* 自己添加的服务提供者
*/
\App\Providers\HelperServiceProvider::class,
可以看到,关于常用的 Redis、session、queue、auth、database、Route 等服务都是在这里进行加载的
9、使用管道模式处理用户请求,先经过中间件进行处理
return (new Pipeline($this->app))
->send($request)
// 如果没有为程序禁用中间件,则加载中间件(位置在app/Http/Kernel.php的$middleware属性)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
app/Http/Kernel.php
/**
* 应用程序的全局HTTP中间件
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
10、经过中间件处理后,再进行请求分发(包括查找匹配路由)
10.1 通过中间件/路由器发送给定的请求
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
...
return (new Pipeline($this->app))
...
// 进行请求分发
->then($this->dispatchToRouter());
}
10.2 获取路由调度程序回调
/**
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
// 将请求发送到应用程序
return $this->router->dispatch($request);
};
}
10.3 将请求发送到应用程序
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
10.4 将请求分派到路由并返回响应【重点在runRoute方法】
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatchToRoute(Request $request)
{
//
return $this->runRoute($request, $this->findRoute($request));
}
10.5 查找与给定请求匹配的路由
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
10.6 查找与给定请求匹配的第一条路由
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function match(Request $request)
{
// 获取用户的请求类型(get、post、delete、put),然后根据请求类型选择对应的路由
$routes = $this->get($request->getMethod());
// 匹配路由
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) {
return $route->bind($request);
}
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) {
return $this->getRouteForMethods($request, $others);
}
throw new NotFoundHttpException;
}
到现在,已经找到与请求相匹配的路由了,之后将运行了,也就是10.4 中的runRoute 方法
10.4 将请求分派到路由并返回响应
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
10.7 返回给定路线的响应
/**
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
10.8 在栈中运行路由
/**
* Run the given route within a Stack "onion" instance.
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
11、运行路由并返回响应[重点]
可以看到,10.7 中有一个方法是prepareResponse,该方法是从给定值创建响应实例,而 runRouteWithinStack 方法则是在栈中运行路由,也就是说,http的请求和响应都将在这里完成。
随着应用程序的扩展,使用 Laravel Eloquent 处理大量数据库记录可能变得越来越困难。 导致内存不足异常并总体上降低应用程序速度。 这是为什么?
从数据库中获取结果时,您又将数据拉到内存中。 以这个代码片段为例
Post::all()->each(function ($post) {
// ...
});
这将导致以下查询,将 posts 表中的所有记录加载到内存中
select * from posts;
通常对于具有少量记录的表,这是绝对可以接受的。 但是,随着成千上万的帖子的积累,您最终将开始遇到 Web 服务器的内存资源限制。
分块
Laravel 中常见的方法是使用 Eloquent 的 (通过 BuildsQuery) chunk() 一种方法,该方法获取固定数量的记录,将较大的集合分解为更多的可消耗块。
Post::chunk(1000, function ($post) {
// ...
});
尽管这看起来不错,但有很多改进和要注意的地方。
首先,设想以下情形:您正在从数据库中获取 Post 记录以更新也在 where 子句中使用的属性
Post::where('published_at', '<', now())->chunk(1000, function ($post) {
$post->update('published_at', now());
});
尽管是人为的,但它例证了一个非常现实的问题,其中这样的查询将导致无限循环,因为在下一次执行查询时,published_at 属性将始终小于 now()(假设精度为秒 使用 MySQL 的 timestamp 列类型或类似名称)。
其次,存在查询性能及其对数据库服务器的影响的问题。 上面的代码将导致类似于以下内容的查询
select * from posts order by posts.id asc limit 1000 offset 9000
由于删除的记录和附加的查询约束,MySQL 无法直接转到偏移量,因此,此查询必须有效地选择前 10,000 条记录,以仅返回最后选择的 1,000 条记录。 可以想象,这无法很好地扩展到成千上万行。 这将导致数据库服务器使用不必要的资源,从而降低了应用程序中所有其他查询的速度。
大块… 但是更好!
为了防止无法预料的陷阱并提高数据库服务器性能,我们可以使用 Eloquent chunkById 的方法
Post::where('published_at', '<', now())->chunkById(1000, function ($post) {
$post->update('published_at', now());
});
上面的代码段将导致类似于以下内容的查询
select * from posts where published_at < '2019-09-11 12:00:00' and id > 9000 order by id asc limit 1000
为什么将此方法视为 “更好”?
a)它允许 MySQL 完全跳过前 9000 条记录(假设是顺序记录)
b)由于 where 子句中的 id 约束,我们将不再重新选择已经更新的记录
深入探讨 BuildsQueries 特性的 chunkById 方法,我们 [看到](github.com/laravel/framework/blob/...) 存储的最后一条记录的 ID(请记住,我们按 ID 升序排列)被存储并在下一个要运行的查询中用作参数。
转:https://dev.to/ryancco/faster-eloquent-chunking-3lc0
集合通过 Illuminate\Support\Collection 进行实例,Laravel的内核大部分的参数传递都用到了集合,但这并不代表集合就是好的。Laravel作为快捷并优雅的开发框架,是有他一定的道理所在的,并非因他的路由、DB、监听器等等。当你需要处理一组数组时,你可能就需要它帮助你快捷的解决实际问题。
实现代码
1.创建集合
$collection = collect([1,2,3]);
显而易见,这是一部非常简单的操作,请打住你想说“这种操作很复杂”的话,它更类似与早起PHP5.x的版本的声明方式。
例如:$collection = array(1,3);
laravel对于collection也没有做任何复杂的事情,想了解更多的laravel集合知识请搜索:Laravel源码解析之集合
2.如果你想将集合转换为数据,其使用方法也非常的简单
collect([1,3])->all();
效果:[1,3]
在不过与考虑性能的情况下,可以使用Laravel集合,毕竟它将帮你完成数组操作的百分之九十的工作。
例如:我们需要通过一个水平线切分数组,将其分为2个及以上的数组个数。看集合的操作
$collection = collect([1,3,4,5,6,7]);
$chunks = $collection->chunk(4);
$chunks->toArray();
效果:[[1,4],[5,7]]
并且有些还根据sql语句的查询方式来设计的方法,可用方法:
所有方法都可以通过链式访问的形式优雅的操作数组。而且,几乎所有的方法都会返回一个新的 Collection 实例,允许你在必要时保存集合的原始副本
all
average
avg
chunk
collapse
combine
concat
contains
containsStrict
count
crossJoin
dd
diff
diffAssoc
diffKeys
dump
each
eachSpread
every
except
filter
first
firstWhere
flatMap
flatten
flip
forget
forPage
get
groupBy
has
implode
intersect
intersectByKeys
isEmpty
isNotEmpty
keyBy
keys
last
macro
make
map
mapInto
mapSpread
mapToGroups
mapWithKeys
max
median
merge
min
mode
nth
only
pad
partition
pipe
pluck
pop
prepend
pull
push
put
random
reduce
reject
reverse
search
shift
shuffle
slice
some
sort
sortBy
sortByDesc
sortKeys
sortKeysDesc
splice
split
sum
take
tap
times
toArray
toJson
transform
union
unique
uniqueStrict
unless
unlessEmpty
unlessNotEmpty
unwrap
values
when
whenEmpty
whenNotEmpty
where
whereStrict
whereBetween
whereIn
whereInStrict
whereInstanceOf
whereNotBetween
whereNotIn
whereNotInStrict
wrap
zip
想了解更多laravel集合用法请自行搜索或跳转至:https://learnku.com/docs/laravel/5.8/collections/3916#method-all
原因:
看源码得知,这个问题一直存在,因为分页先会执行一遍:select * 获取总数,所以你分页上去重并不会影响 他获取总数
/**
* Paginate the given query.
*
* @param int $perPage
* @param array $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*
* @throws \InvalidArgumentException
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$perPage = $perPage ?: $this->model->getPerPage();
$results = ($total = $this->toBase()->getCountForPagination())
? $this->forPage($page, $perPage)->get($columns)
: $this->model->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
案例demo:
1.distinct去重
$res = DB::table('90_cbb_palmlife')
->select('id','tid','title','address','tel','lat','lng',
DB::raw("round(st_distance_sphere(point(lng,lat),point({$where['lng']},{$where['lat']}))/1000,2) as space"
)
)->distinct()
->join('92_cbb_palmlife_coupon_relation', '90_cbb_palmlife.tid', '=', '92_cbb_palmlife_coupon_relation.palmlife_id')
->whereIn('92_cbb_palmlife_coupon_relation.palmlife_coupon_id',$where['cids'])
->orderBy('space','asc')
->paginate(10);
效果图:
2.groupBy去重
$res = DB::table('90_cbb_palmlife')
->select('id','tid','title','address','tel','lat','lng',
DB::raw("round(st_distance_sphere(point(lng,lat),point({$where['lng']},{$where['lat']}))/1000,2) as space"
)
)
->join('92_cbb_palmlife_coupon_relation', '90_cbb_palmlife.tid', '=', '92_cbb_palmlife_coupon_relation.palmlife_id')
->whereIn('92_cbb_palmlife_coupon_relation.palmlife_coupon_id',$where['cids'])
->groupBy('id')
->orderBy('space','asc')
->paginate(10);
效果图:
3.自定义分页 LengthAwarePaginator (这里我就不举例了)
需求:
1.每天时间与温度对应的一个有一个数组;(已有)
['2020-11-09 00:00:00' => 0,
'2020-11-09 01:00:00' => 0,
'2020-11-09 02:00:00' => -1,
'2020-11-09 03:00:00' => -1,
'2020-11-09 04:00:00' => -2,
'2020-11-09 05:00:00' => -2,
...]
2.用户自定义的时间段 比如 4点到8点/8点到11点等等;
3.求出每个不同时间段,平均温度值!(注意:优雅);
代码:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class TestController extends Controller
{
public function __invoke(Request $request)
{
$dates = [
['2020-11-09 04:00:00', '2020-11-09 08:00:00'],
['2020-11-09 08:00:00', '2020-11-09 11:00:00'],
['2020-11-09 11:00:00', '2020-11-09 15:00:00'],
['2020-11-09 15:00:00', '2020-11-09 22:00:00'],
['2020-11-09 22:00:00', '2020-11-10 04:00:00'],
['2020-11-10 04:00:00', '2020-11-10 08:00:00'],
['2020-11-10 08:00:00', '2020-11-12 11:00:00'],
['2020-11-10 11:00:00', '2020-11-12 15:00:00'],
];
return Collection::make($dates)->map(function ($val) {
$avg = $this->getAvgItem($val[0], $val[1])->avg();
return [
'start' => $val[0],
'end' => $val[1],
'avg' => $avg
];
});
}
protected function getAvgItem(string $start, string $end)
{
$start_date = Carbon::parse($start);
$end_date = Carbon::parse($end);
$data = $this->getTemperature();
return Collection::make($data)->filter(function ($key, $val) use ($start_date, $end_date) {
$now_date = Carbon::parse($val);
return $now_date->gte($start_date) && $now_date->lte($end_date);
});
}
protected function getTemperature()
{
return [
'2020-11-09 00:00:00' => 0,
'2020-11-09 01:00:00' => 0,
'2020-11-09 02:00:00' => -1,
'2020-11-09 03:00:00' => -1,
'2020-11-09 04:00:00' => -2,
'2020-11-09 05:00:00' => -2,
'2020-11-09 06:00:00' => -2,
'2020-11-09 07:00:00' => -3,
'2020-11-09 08:00:00' => -3,
'2020-11-09 09:00:00' => 0,
'2020-11-09 10:00:00' => 3,
'2020-11-09 11:00:00' => 6,
'2020-11-09 12:00:00' => 8,
'2020-11-09 13:00:00' => 11,
'2020-11-09 14:00:00' => 13,
'2020-11-09 15:00:00' => 13,
'2020-11-09 16:00:00' => 12,
'2020-11-09 17:00:00' => 12,
'2020-11-09 18:00:00' => 9,
'2020-11-09 19:00:00' => 7,
'2020-11-09 20:00:00' => 6,
'2020-11-09 21:00:00' => 6,
'2020-11-09 22:00:00' => 6,
'2020-11-09 23:00:00' => 4,
'2020-11-10 00:00:00' => 2,
'2020-11-10 01:00:00' => 2,
'2020-11-10 02:00:00' => 1,
'2020-11-10 03:00:00' => 1,
'2020-11-10 04:00:00' => 0,
'2020-11-10 05:00:00' => 0,
'2020-11-10 06:00:00' => 0,
'2020-11-10 07:00:00' => 0,
'2020-11-10 08:00:00' => 0,
'2020-11-10 09:00:00' => 8,
'2020-11-10 10:00:00' => 7,
'2020-11-10 11:00:00' => 10,
'2020-11-10 12:00:00' => 11,
'2020-11-10 13:00:00' => 12,
'2020-11-10 14:00:00' => 13,
'2020-11-10 15:00:00' => 13,
];
}
}
总结:基于laravel集合极其优雅的显示出来
描述:laravel链式操作虽然方便那是针对普通业务,稍微复杂点的我感觉还是原生sql来的实在
话不多说分享一下一个案例:
多条件查询,定位经纬度计算距离附近排序,分页
$where = request()->input();
if (!isset($where['lng']) || !isset($where['lat'])) {return '未传经纬度';}
try {
$sql = "SELECT
lara_90_cbb_palmlife.id,
lara_90_cbb_palmlife.tid,
lara_90_cbb_palmlife.city,
lara_90_cbb_palmlife.area,
lara_90_cbb_palmlife.business,
lara_90_cbb_palmlife.type_name,
lara_90_cbb_palmlife.img,
lara_90_cbb_palmlife.title,
lara_90_cbb_palmlife.lat,
lara_90_cbb_palmlife.lng,
lara_90_cbb_palmlife.youhui,
round(st_distance_sphere(point(lara_90_cbb_palmlife.lng,lara_90_cbb_palmlife.lat),point({$where['lng']},{$where['lat']}))/1000,2) as space
FROM
`lara_90_cbb_palmlife`
LEFT JOIN `lara_92_cbb_palmlife_coupon_relation` ON `lara_90_cbb_palmlife`.`tid` = `lara_92_cbb_palmlife_coupon_relation`.`palmlife_id`
LEFT JOIN `lara_91_cbb_palmlife_coupon` ON `lara_91_cbb_palmlife_coupon`.`cid` = `lara_92_cbb_palmlife_coupon_relation`.`palmlife_coupon_id`
WHERE ";
if (isset($where['city'])) {
$sql .= "city = '{$where['city']}'";
}
if (isset($where['area'])) {
$sql .= " and area = '{$where['area']}'";
}
if (isset($where['business'])) {
$sql .= " and business = '{$where['business']}'";
}
if (isset($where['type'])) {
$sql .= " and type_name = '{$where['type']}'";
}
if (isset($where['bank'])) {
$sql .= " and lara_90_cbb_palmlife.bank = {$where['bank']}";
}
if (isset($where['bank'])) {//查优惠表
$sql .= " and (lara_91_cbb_palmlife_coupon.bank = {$where['bank']} or lara_91_cbb_palmlife_coupon.bank is NULL)";
}
if (isset($where['week'])) {//查优惠表
$sql .= " and (youhui LIKE '%周一至周日%' OR lara_91_cbb_palmlife_coupon.title LIKE '%{$where['week']}%' )";
}
if (isset($where['kw'])) {
$sql .= " and lara_90_cbb_palmlife.title like '%{$where['kw']}%'";
}
$sql .= "GROUP BY lara_90_cbb_palmlife.tid";
if (isset($where['space'])) {//1000千米内距离排序 根据定位跟经纬度计算距离
$sql .= " HAVING space < {$where['space']} ORDER BY space ASC , lara_90_cbb_palmlife.id" ;
}else{
$sql .= " ORDER BY space ASC , lara_90_cbb_palmlife.id";
}
//$sql .= " LIMIT 100";
//$res = DB::select($sql);
$sql = '('.$sql.') cc';//定义临时表
$res = DB::table(DB::raw($sql))->paginate(10);
return response()->json(array('status' => 200, 'msg' => '成功', 'res' => $res));
} catch (Exception $e) {
return response()->json(array('status' => 201, 'msg' => '失败'));
}
laravel框架使用到阿里云短信服务发送消息,介绍一下该扩展:
https://github.com/MissMyCat/aliyun-sms
1.安装:
composer require mrgoon/aliyun-sms dev-master
2.加载:
在config/app的providers中添加 Mrgoon\AliSms\ServiceProvider::class3.运行:
php artisan vendor:publish
生成配置文件aliyunsms.php
<?php
return [
'access_key' => env('ALIYUN_SMS_AK'),// accessKey
'access_secret' => env('ALIYUN_SMS_AS'),// accessSecret
'sign_name' => env('ALIYUN_SMS_SIGN_NAME'),// 签名
];
4.根据新增的aliyunsms.php 文件,在.env文件中添加环境变量:
ALIYUN_SMS_AK=your access key
ALIYUN_SMS_AS=your secret key
ALIYUN_SMS_SIGN_NAME=sign name
6.为了能够方便的发送短信,我们可以在 app 目录下,创建一个Services目录,并添加 AliyunSms.php 文件。
可以用artisan命令生成
<?php
namespace App\Services;
use Mrgoon\AliSms\AliSms;
/**
* 阿里云短信类
*/
class AliyunSms
{
//验证码
const VERIFICATION_CODE = 'verification_code';
//模板CODE
public static $templateCodes = [
self::VERIFICATION_CODE => 'SMS_XXXXXXXXXX',];
/**
* 发送短信
*/
public static function sendSms($mobile,$scene,$params = [])
{
if (empty($mobile)) {
throw new \Exception('手机号不能为空');
}
if (empty($scene)) {
throw new \Exception('场景不能为空');
}
if (!isset(self::$templateCodes[$scene])) {
throw new \Exception('请配置场景的模板CODE');
}
$template_code = self::$templateCodes[$scene];
try {
$ali_sms = new AliSms();
$response = $ali_sms->sendSms($mobile,$template_code,$params);
if ($response->Code == 'OK') {
return true;
}
throw new \Exception($response->Message);
} catch (\Throwable $e) {
throw new \Exception($e->getMessage());
}
}
}
7.为了能够记录每次短信发送的状态,我们可以创建一个 sms_logs 表。
CREATE TABLE `sms_logs` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '类型(0:短信验证码,1:语音验证码,2:短信消息通知)',
`mobile` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号',
`code` varchar(12) NOT NULL DEFAULT '' COMMENT '验证码',
`checked` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否验证(0:未验证,1:已验证)',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态(0:未发送,1:已发送,2:发送失败)',
`reason` varchar(255) NOT NULL DEFAULT '' COMMENT '失败原因',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`operator_id` int(11) NOT NULL DEFAULT '0' COMMENT '操作人ID',
`ip` varchar(16) NOT NULL DEFAULT '' COMMENT '操作IP',
`created` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
`updated` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信表';
8.创建一个 SmsLog 模型来管理
<?php
namespace App\Models;
use App\Services\AliyunSms;
class SmsLog extends Model
{
protected $fillable = [
'type','mobile','code','checked','status','reason','remark','operator_id','ip',];
//类型(0:短信验证码,2:短信消息通知)
const TYPE_CODE = 0;
const TYPE_VOICE = 1;
const TYPE_MESSAGE = 2;
//是否验证(0:未验证,1:已验证)
const CHECKED_UNVERIFIED = 0;
const CHECKED_VERIFIED = 1;
//状态(0:未发送,2:发送失败)
const STATUS_NO_SEND = 0;
const STATUS_SEND = 1;
const STATUS_FAIL = 2;
//短信发送间隔时间,默认60秒
const SEND_INTERVAL_TIME = 60;
/**
* 检测短信验证码
*/
protected function checkCode($mobile,$code)
{
if (!$mobile) {
throw new \Exception('手机号不能为空');
}
if (!checkMobile($mobile)) {
throw new \Exception('手机号不正确');
}
if (!$code) {
throw new \Exception('验证码不能为空');
}
$sms_log = $this->where([
['type',self::TYPE_CODE],['mobile',$mobile],['status',self::STATUS_SEND],['checked',self::CHECKED_UNVERIFIED],])->orderBy('created','desc')->first();
if (!$sms_log) {
throw new \Exception('验证码不存在,请重新获取');
}
if ($code != $sms_log->code) {
throw new \Exception('验证码错误');
}
$sms_log->checked = self::CHECKED_VERIFIED;
$sms_log->save();
return true;
}
/**
* 检测短信频率
*/
protected function checkRate($mobile)
{
if (!$mobile) {
throw new \Exception('手机号不能为空');
}
$sms_log = $this->where([
['mobile','desc')->first();
$now = time();
if ($sms_log) {
if (($now - strtotime($sms_log->created)) < self::SEND_INTERVAL_TIME) {
throw new \Exception('短信发送太频繁,请稍后再试');
}
}
return true;
}
/**
* 发送短信验证码
*/
protected function sendVerifyCode($mobile)
{
self::checkRate($mobile);
$code = mt_rand(1000,9999);
$sms_log = $this->create([
'type' => self::TYPE_CODE,'mobile' => $mobile,'code' => $code,'checked' => self::CHECKED_UNVERIFIED,'status' => self::STATUS_NO_SEND,'ip' => getRealIp(),]);
try {
AliyunSms::sendSms($mobile,AliyunSms::VERIFICATION_CODE,['code' => $code]);
$sms_log->status = self::STATUS_SEND;
$sms_log->save();
return true;
} catch (\Exception $e) {
$sms_log->status = self::STATUS_FAIL;
$sms_log->reason = $e->getMessage();
$sms_log->save();
throw new \Exception($e->getMessage());
}
}
}
9.在 app/Helpers 的 functions.php 中添加getRealIp() 和 checkMobile() 公共方法
/**
* 获取真实IP地址
*/
function getRealIp()
{
$ip = false;
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"),"unknown")) {
$ip = getenv("HTTP_CLIENT_IP");
} else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"),"unknown")) {
$ips = explode(",",getenv("HTTP_X_FORWARDED_FOR"));
if ($ip) {
array_unshift($ips,$ip);
$ip = false;
}
$ipscount = count($ips);
for ($i = 0; $i < $ipscount; $i++) {
if (!preg_match("/^(10|172\.16|192\.168)\./i",$ips[$i])) {
$ip = $ips[$i];
break;
}
}
} else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"),"unknown")) {
$ip = getenv("REMOTE_ADDR");
} else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'],"unknown")) {
$ip = $_SERVER['REMOTE_ADDR'];
} else {
$ip = "unknown";
}
return isIp($ip) ? $ip : "unknown";
}
/**
* 检查是否是合法的IP
*/
function isIp($ip)
{
if (preg_match('/^((\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)(?:\.(\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)){3})$/',$ip)) {
return true;
} else {
return false;
}
}
/**
* 验证手机号
*/
function checkMobile($mobile)
{
return preg_match('/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/i',$mobile);
}
10.使用
SmsLog::sendVerifyCode(); //发送短信
laravel5.5+对自定义二维数组进行分页,demo:定位经纬度距离远近排序分页
功能:
根据定位当前经纬度跟店面经纬度计算出距离 比如0.1km
把距离值循环存入每条数据里面增加元素space
根据space值由低到高排序并分页显示
废话不多说直接贴代码:
//列表数组 自定义分页
public function plistdata()
{
$where = request()->input();
if (!isset($where['city'])) {return null;}
try {
$res = Palmlife::select('id','city', 'area', 'business','type_name','title','lat','lng')->where('city', $where['city'])->where(function ($query) use($where) {
if (isset($where['area'])) {
$query->where('area',$where['area']);
}
if (isset($where['business'])) {
$query->where('business',$where['business']);
}
if (isset($where['kw'])) {
$query->where('title','like',"%{$where['kw']}%");
}
});
$res = $res->get()->toArray();
//循环为每天数据添加距离值
foreach ($res as &$v){
//定位经纬度与店面的距离(两经纬度计算) 0.1km
$space = SelectOption::getlatlngspac($where['lng'],$where['lat'],$v['lng'],$v['lat'],2,2);
$v['space'] = $space;
}
//排序 根据与店面距离从近到远排序
$a = array_column($res,'space');
array_multisort($a,SORT_ASC,$res);//SORT_DESC ASC
//对数组 重组后分页
$page = $where['page'] ?? 1; //当前页数 默认1
$perPage = 10; //每页的条数
$offset = ($page * $perPage) - $perPage;//计算每页分页的初始位置
//实例化LengthAwarePaginator类,并传入对应的参数
$data = new LengthAwarePaginator(array_slice($res, $offset, $perPage, true), count($res), $perPage, $page, ['path' => request()->url(), 'query' => request()->query()]);
return response()->json(
array(
'status' => 200,
'msg' => '成功',
'res' => $data)
);
} catch (Exception $e) {
return response()->json(
array(
'status' => 201,
'msg' => '失败')
);
}
}
错误提示:
SQLSTATE [HY000] [1045]访问被拒绝用户'用户名'@'localhost'(使用密码:是)
select * from sessions id = 'xxx' limit 1 .......
这也算是一个不是坑的坑,遇到一次就好了,麻烦的就是在第一次遇到
原因:
基本可以确定你是数据库密码中有#等C语言的注释字符了
描述:
.env文件中,
数据库密码不能使用#,
否则解析的时候#后面的部分会被当做注释从而导致密码解析错误,
链接不上数据库
为防止再次出现这种类型错误,
建议数据库密码不要使用# //等C语言的注释符号
解决方式:
1.修改数据库密码 去掉#等C语言注释符
2.如果一定要使用C的注释符作为密码,必须使用单引号或者双引号将密码包裹住:
比如:DB_PASSWORD='pwd#123456'
功能:订阅网站后通过邮箱网站会定期或不定期推送一篇或几篇网站博文。
驱动程序说明和先决条件:数据库,Beanstalkd,Amazon SQS,Redis,和一个同步驱动程序(供本地使用),laravel可以用这些驱动实现队列,我这里就用database。
我通过数据库驱动队列发送
1.要使用 database 队列驱动程序,你需要一个数据库表来保存任务。要生成创建此表的迁移,请运行 queue:table Artisan 命令。一旦迁移已经创建,你可以使用 migrate 命令迁移你的数据库:
php artisan queue:table
php artisan migrate
2. .env 中改成数据库
QUEUE_CONNECTION=database
3. 创建任务类
php artisan make:job SendEmail
该命令会在App\Jobs目录下生成该文件
<?php
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 App\Mail\subscribe;
use Illuminate\Support\Facades\Mail;
class SendEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $name;
protected $email;
/**
* Create a new job instance.
* @return void
*/
public function __construct($name, $email)
{
//
$this->name = $name;
$this->email = $email;
}
/**
* Execute the job.
* @return void
*/
public function handle()
{
// 如果参试大于三次
if ($this->attempts() > 3) {
return;
}else{
// 每次进来休息3秒钟
sleep(3);
Mail::to($this->email)->send(new subscribe());
echo $this->name;
}
}
}
4.添加路由 创建添加队列方法的路由 (位置自行安排)
5.创建添加队列方法 把已经订阅的邮箱添加入队列里面 定时发送邮件
use App\Jobs\SendEmail;
//需要发邮件的订阅 插入队列
public function addqueuesubscribe()
{
$user = DB::table('users')->where('subscribe',1)->get();
//dd($user);
foreach($user as $data){
dispatch(new SendEmail($data->name,$data->email));
echo $data->name.'<br>';
}
}
6.测试看看效果
1.执行添加队列路由 :http://larabg.com/addqueuesubscribe
添加完队列会在jobs表生成发送队列信息 队列执行成功会删除 队列执行失败也会删除同时会在failed_jobs表生成队列失败信息
2.运行队列处理器:监听队列表 有数据就执行
D:\phpStudy\WWW\larabg>php artisan queue:listen
admin[2020-10-16 03:49:12][15] Processed: App\Jobs\SendEmail
[2020-10-16 03:49:12][16] Processing: App\Jobs\SendEmail
站长å°å[2020-10-16 03:49:16][16] Processed: App\Jobs\SendEmail
我用两个账号订阅网站一个是QQ邮箱,一个是钉钉邮箱
qq邮箱:
钉钉邮箱:
官网一段文字 (网上很多这种文章,后面有时间会写一遍守护进程的文章)
技巧:为了让 queue:work 进程永久地在后台运行,您应该使用一个进程监视器,如 Supervisor,以确保队列 worker 不会停止运行。
官方描述:
Laravel 基于热门的 SwiftMailer 函数库提供了一套干净、简洁的 API ,可以为 SMTP、Mailgun、Postmark、Amazon SES 和 sendmail 提供驱动,让你可以快速从本地或云端服务自由地发送邮件。
1.
必须安装Guzzle HTTP 函数库,因为我用laravel8所以默认已安装直接用就行
2.配置.env (因为我没有配置ssl所以用25端口如果是线上自行配置465/994)
MAIL_MAILER=smtp
MAIL_HOST=smtp.163.com
MAIL_PORT=25
MAIL_USERNAME=houtizong@163.com
MAIL_PASSWORD=xxx
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=houtizong@163.com
MAIL_FROM_NAME="${APP_NAME}"
3.生成邮件类,在 Laravel 中,应用发送的每种邮件都被表示为 mailable 类。这些类存储于 app/Mail 目录中。如果您的应用中没有该目录,别慌,当您使用 make:mail 命令生成您的首个 mailable 类时,应用将会自动创建它
php artisan make:mail subscribe
4.配置发送邮件的内容,所有的 mailable 类的配置都在 build 方法中完成。您可以通过调用诸如 from , subject , view 和 attach 这样的各种各样的方法来配置邮件的内容及其发送。
public function build()
{
return $this->view('home.email.subscribe')
->with([
'orderName' => 123,
'orderPrice' => 456,
]);
}
5.配置邮件视图:我的路径\resources\views\home\email\subscribe.blade.php
<div>
Price: {{ $orderPrice }}
</div>
6.创建发送邮件方法路由 (位置可以自行选择)
7.创建发送邮件方法
use App\Mail\subscribe;
use Illuminate\Support\Facades\Mail;
//发订阅邮件
public function sendsubscribeemail(Request $request, $id=1)
{
$user = User::findOrFail($id);
//dd($request->user());
//发邮件
Mail::to('houtizong@dingtalk.com')->send(new subscribe($user));
}
8.最后发送邮件看效果咯 (发邮箱还是设置成队列方式显得专业,后面有时间我会写一篇博文)
公共函数这种东西没有虽然不打紧,但是有的话会方便很多,最近打算用laravel8写个博客,所以把laravel8添加全局公共函数步骤记录一下,方便查阅
1.首先添加文件,app/Helpers.php ,我这里是这个名字因为习惯了,你也可以自己定义
<?php
if( !function_exists("format_date") ){
/**
*根据时间戳计算与当前时间的间距及格式化单位
*/
function format_date($time){
$t=time()-$time;
$f=array(
'31536000'=>'年',
'2592000'=>'个月',
'604800'=>'星期',
'86400'=>'天',
'3600'=>'小时',
'60'=>'分钟',
'1'=>'秒'
);
foreach ($f as $k=>$v) {
if (0 !=$c=floor($t/(int)$k)) {
return $c.$v.'前';
}
}
}
}
2.修改根目录下composer.json 作自动加载刚刚的文件
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
},
"files": [
"app/helpers.php"
]
},
3.修改完成后记得运行 composer dumpautoload 确保让修改生效
composer dumpautoload
完成了看看效果
{{format_date($item->pubtime)}}
安装完laraver8的前提下,开始标准的流水线作业。
来一段官方描述:
想要快速开始吗?在一个全新的 Laravel 应用程序中安装 laravel/jetstream Composer 包然后运行 php artisan jetstream:install livewire 或者 php artisan jetstream:install inertia 。 迁移数据库后,将浏览器导航到 http://your-app.test/register 或分配给应用程序的任何其他 URL。这些命令将负责构建整个认证系统!
1.安装
composer require laravel/jetstream
2.使用 Livewire 栈安装 Jetstream
php artisan jetstream:install livewire
3.标准迁移
php artisan migrate
备注:注意迁移错误是mysql低版本问题,修改App\Providers\AppServiceProvider.php即可
Syntax error or access violation: 1071 Specified key was too long;
好了,看看效果图 注册/登录
laravel8出来了所以我想测一下jetstream怎么个好法,就有了标题的问题
我安装的时候php版本是7.2+,后面一下laravel8也composer了下来
然后composer require laravel/jetstream 提示版本问题,低了,我一看还是原来的旧版本
laravel/jetstream at version has a php xxx
我在cmd上php -v 一看确实是7.2
我想起新的7.3环境变量没有加,赶紧添加上
右击电脑->属性->系统设置->高级->环境变量,找到path然后修改之前的php路径为7.3目录
比如我这里是:
D:\phpStudy\php\php-7.3.22-nts
把cmd关闭重新打开 php -v 正常了(注意:cmd一定要关闭重新打开不然环境变量不会生效)
好了正常了
解决方法:切换完新的php注意检查环境变量是否添加,不然就会指向原来添加过的旧版本
正在下载中....
@verbatim 指令
如果模板中一大部分需要显示 JavaScript 变量,就可以用 @verbatim 指令包裹住 HTML,这样就不用在每个 Blade echo 语句前加 @ 符
案例代码:
<ul id="news_box"></ul>
<div class="showMore" page="1" type="{{$row['pinyin']}}"><span>查看更多</span></div>
@verbatim<script type="text/html" id="tpl">
{{ each data as value index}}
<li>
<a href="{{value.url}}">{{ value.title }}</a>
<div class="look_over">{{ value.views }}</div>
</li>
{{ /each }}
</script>
@endverbatim
js:
$(".showMore").click(function(){
var page=parseInt($(this).attr("page"))+1;
var type=$(this).attr("type");
var htmlobj=$.ajax({url:"/api/cardnewsarticles/"+type+"?page="+page,async:false});
if(htmlobj!=null && htmlobj!=''){
var jsonObj =htmlobj.responseJSON;
if(jsonObj !=null){
var data =jsonObj.data;
if(data !=null && data.length>0){
var htmlStr = template( "tpl", jsonObj);
$("#news_box").append(htmlStr);
$(this).attr("page",page);
}else{
$(this).html("已加载完");
}
}
}
});
完事
ps:low是low了点,但是我想表达的是可以这么做
作为常规程序员,高级搜索是您不时遇到的事情。 我最近在Laravel中实现了它。 我要在这里解释。
先决条件-假设您确实具有Laravel Architecture及其功能(例如模型,控制器和路由的工作方式)的基础知识。
好的,我们确实有一个生物数据表,因为名称代表它保存了用户的生物数据。 这就是它的迁移方式
Migration
$table->id();
$table->enum('gender',['boy','girl']);
$table->string('name',255);
$table->json('biodata');
$table->timestamps();
如您所见,我们确实有一个表列生物数据,它是JSON类型。 这使我们可以将JSON保存在数据库中。
并且我们将使用属性强制转换在获取时将其自动转换为对象。 所以我们的生物数据模型看起来像这样
Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Biodata extends Model
{
protected $guarded = [];
protected $casts = [
'biodata' => object
];
protected $perPage = 10;
}
现在,在进一步介绍之前,先看看我们的生物数据对象是什么样子
Factory
return [
'gender' => $this->faker->randomElement($this->gender),
'name'=> $this->faker->name(),
'biodata' => [
'personal'=>[
'qualification' => $this->faker->randomElement($this->qualification),
'height'=>$this->faker->randomElement($this->height),
'income'=>$this->faker->numberBetween(100000,1000000),
'occupation' => $this->faker->randomElement($this->jobs),
],
'family'=>[
'father_gotra' => $this->faker->randomElement($this->gotra),
'mother_gotra' => $this->faker->randomElement($this->gotra),
],
'contact'=>[
'state' => $this->faker->randomElement($this->indianStates),
]
],
];
在所有这些字段上,我们将进行搜索。现在该讨论我们想从搜索功能中获得什么。 所以这是我们所有可选的搜索参数
Gender:单值 , 字段类型-ENUM
Qualification:可能是多个值, 字段类型-JSON密钥
Min Height:单值 , 字段类型-JSON密钥
Income:单值 , 字段类型-JSON密钥
Occupation:单值 , 字段类型-JSON密钥
Gotra:多重价值 , 字段类型-JSON密钥
现在,这对您来说似乎很简单,您只需要在where语句中使用,就可以了,但是请紧紧抓住,然后让我们先解决一些明显的问题
在JSON字段中搜索?
那么我们如何在JSON中搜索呢? 很显然,这对于MYSQL来说并不难,对于Laravel来说甚至更好,简而言之,这是您在JSON字段中进行搜索的方式,前提是它的密钥
return $query->where('biodata->personal->income', '>=', $minIncome);
检查上面的生物数据对象以了解。
多个值
现在,在进入编写搜索功能的代码之前,您可能要问的一个问题是如何在查询字符串中发送多个值? 很高兴您提出要求,我们只会将它们添加这样的逗号
http:://url.com/?qualification=MBA,BA&gender=boy
现在,我们可以简单地使用“,”将其爆炸,这将为我们提供该参数的值数组。
如何将多个查询添加到where语句?
所以这是Laravel文档声明的标准
DB::table('users')->where('votes', 100)->get();
或者 最好这么写
$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
您可以在此处向where语句添加多个参数,
但是,在实际跳至运行任何查询之前,我们需要进行某种验证。由于所有参数都是可选的,因此用户可能不会提供要添加到查询中的全部或任何输入。
因此,当我们在寻找地点和地点时,我们需要编写额外的代码来拯救我们。 这就是官方文件所说的
有时,您可能希望子句仅在其他情况成立时才应用于查询。 例如,如果传入请求中存在给定的输入值,则您可能只想应用where语句。 您可以使用when方法完成此操作:
这就是你写的方式
$users = DB::table('users')
->when($role, function ($query, $role) {
return $query->where('role_id', $role);
})
->get();
但是我们还有一个问题,我们如何为同一个参数添加多个值,那么when是函数的第二个参数,我们可以在该函数中执行任何操作:)继续阅读
因此,为了检查同一参数的多个值,我们将在其中使用循环,这就是它的样子。
->when($gotra, function ($query, $gotra) {
foreach($gotra as $g){
$query->where('biodata->family->father_gotra', '<>' ,$g);
}
return $query;
})
就是这样了。 我们介绍了编写搜索功能所需的所有基础知识。 编写的demo:
public function search(){
$gender = request('gender');
$state = request('state');
$minIncome = (int)request('minIncome');
$minHeight = (int)request('minHeight');
$qualifications = request('qualification') != '' ? explode(",", request('qualification') ) : false;
$gotra = request('gotra') != '' ? explode(",", request('gotra') ) : false;
$results = Biodata::
when($gender, function ($query, $gender) {
return $query->where('gender', $gender);
})
->when($state, function ($query, $state) {
return $query->where('biodata->contact->state', $state);
})
->when($qualifications, function ($query, $minQualification) {
foreach($qualifications as $qualification){
$query->where('biodata->personal->qualification', '=', $qualification);
}
return $query;
})
->when($minIncome, function ($query, $minIncome) {
return $query->where('biodata->personal->income', '>=', $minIncome);
})
->when($minHeight, function ($query, $minHeight) {
return $query->where('biodata->personal->height', '>=' , $minHeight);
})
->when($gotra, function ($query, $gotra) {
foreach($gotra as $g){
$query->where('biodata->family->father_gotra', '<>' ,$g);
}
return $query;
})
->paginate(10);
return response($results,200);
在开发此程序时,我遵循了TDD方法,因此我首先创建了测试,让我知道是否有人要检出它们。 给我发送消息,我将分享代码。
因此,今天我将把这个问题留给你们,让我知道您的想法。
转:https://dev.to/jiteshdhamaniya/advance-searching-in-laravel-for-json-column-type-and-when-method-3dp2
在该文中,我们将学习如何使用最新的Laravel 8版本中的JWT对REST API进行身份验证。您将学习如何使用具有JWT身份验证的Laravel 8创建rest API。
我们将了解如何在Laravel 8中设置JWT身份验证,以及如何使用tymon / jwt-auth包实施安全的REST API。
Laravel 8 JWT认证教程示例
在本教程中,我们将逐步介绍如何使用PHP和Laravel 8通过JWT令牌进行身份验证来实现REST API。
第1步-创建Laravel 8应用程序
让我们通过使用Composer创建一个Laravel 8应用程序开始本教程,该应用程序是PHP开发人员的依赖项管理工具。
转到新的命令行界面,然后运行以下命令:
$ composer create-project --prefer-dist laravel / laravel laravel8jwtapp
第2步-配置MySQL数据库
使用Composer创建Laravel 8应用程序之后,让我们在第二步中配置一个MySQL数据库。
打开位于Laravel 8'应用程序根目录中的.env文件。接下来,添加以下数据库配置信息:
DB_CONNECTION = mysql
DB_HOST = 127.0.0.1
DB_PORT = 3306
DB_DATABASE = <数据库名称>
DB_USERNAME = <数据库用户名>
DB_PASSWORD = <数据库密码>
步骤3 —安装jwt-laravel
现在,我们已经配置了MySQL数据库,并配置了Laravel 8应用程序,让我们开始通过安装jwt-auth软件包来实现JWT身份验证。
回到终端并从项目文件夹的根目录运行以下命令:
$ composer require tymon/jwt-auth
步骤4 —在Laravel 8中设置JWT身份验证
在这一步,我们有一个配置了MySQL的Laravel 8应用程序。在上一步中,我们还安装了jwt-auth库。现在,让我们在应用程序中设置JWT身份验证。
转到config / app.php文件,并添加JWT提供程序和别名,如下所示:
'providers' => [
….
'TymonJWTAuthProvidersJWTAuthServiceProvider',
],
'aliases' => [
….
'JWTAuth' => 'TymonJWTAuthFacadesJWTAuth',
'JWTFactory' => 'TymonJWTAuthFacadesJWTFactory',
],
接下来,返回您的终端并运行以下命令:
$ php artisan vendor:publish --provider="TymonJWTAuthProvidersJWTAuthServiceProvider"
步骤5 —生成JWT密钥
在我们的Laravel 8应用中配置JWT身份验证之后。在这一步中,我们需要生成一个JWT密钥。
转至您的终端并运行以下命令来生成JWT密钥:
$ php artisan jwt:generate
接下来,打开vendor / tymon / src / Commands / JWTGenerateCommand.php并进行如下更新:
public function handle() {$this->fire();}
第6步—在Laravel 8用户模型中实现JWT身份验证
在Laravel 8中配置JWT之后,在这一步中,我们将在User模型中实现它。
打开App / User.php文件,并进行如下更新:
<?php
namespace App;
use IlluminateNotificationsNotifiable;
use IlluminateFoundationAuthUser as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
步骤7 —为JWT身份验证实现REST API控制器
现在,我们在REST API应用程序中实现一个Laravel 8控制器来处理JWT身份验证。
回到您的终端并运行以下命令来生成控制器:
$ php artisan make:controller JwtAuthController
接下来,打开app / http / controllers / JwtAuthController.php文件,并添加以下方法:
<?php
namespace AppHttpControllers;
use JWTAuth;
use Validator;
use AppUser;
use IlluminateHttpRequest;
use AppHttpRequestsRegisterAuthRequest;
use TymonJWTAuthExceptionsJWTException;
use SymfonyComponentHttpFoundationResponse;
class JwtAuthController extends Controller
{
public $token = true;
public function register(Request $request)
{
$validator = Validator::make($request->all(),
[
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
'c_password' => 'required|same:password',
]);
if ($validator->fails()) {
return response()->json(['error'=>$validator->errors()], 401);
}
$user = new User();
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
if ($this->token) {
return $this->login($request);
}
return response()->json([
'success' => true,
'data' => $user
], Response::HTTP_OK);
}
public function login(Request $request)
{
$input = $request->only('email', 'password');
$jwt_token = null;
if (!$jwt_token = JWTAuth::attempt($input)) {
return response()->json([
'success' => false,
'message' => 'Invalid Email or Password',
], Response::HTTP_UNAUTHORIZED);
}
return response()->json([
'success' => true,
'token' => $jwt_token,
]);
}
public function logout(Request $request)
{
$this->validate($request, [
'token' => 'required'
]);
try {
JWTAuth::invalidate($request->token);
return response()->json([
'success' => true,
'message' => 'User logged out successfully'
]);
} catch (JWTException $exception) {
return response()->json([
'success' => false,
'message' => 'Sorry, the user cannot be logged out'
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function getUser(Request $request)
{
$this->validate($request, [
'token' => 'required'
]);
$user = JWTAuth::authenticate($request->token);
return response()->json(['user' => $user]);
}
}
步骤8 —添加Laravel 8 REST API路由
现在,我们已经在Laravel 8用户模型中实现了JWT身份验证。 在此步骤中,我们将继续创建REST API路由。
打开routes / api.php文件,并进行如下更新:
Route::post('login', 'JwtAuthController@login');
Route::post('register', 'JwtAuthController@register');
Route::group(['middleware' => 'auth.jwt'], function () {
Route::get('logout', 'JwtAuthController@logout');
Route::get('user-info', 'JwtAuthController@getUser');
});
步骤9 —为您的Laravel 8 REST API身份验证服务
在Laravel 8 REST API应用程序中实现JWT身份验证后,让我们使用以下命令运行本地开发服务器:
$ php artisan serve
结论
在本教程中,我们逐步介绍了如何实施JWT身份验证来保护和保护使用PHP和Laravel 8创建的REST API端点。
转:https://shabang.dev/laravel-8-rest-api-authentication-with-jwt-tutorial-by-example/
我们已经有很多时间需要获取执行的查询日志,
或者想要获取上次执行的查询,
或者如果您想从laravel查询生成器中查询sql查询,那么您可以这样做。
所以我有三个示例在Laravel 5中显示执行的查询。
当您当时在laravel应用程序上工作时,我认为您需要很多时间来打印上次运行的查询,或者您想直接从phpmyadmin sql框中检查,因此时间,您必须遵循以下三个示例。
因此,我们来看三个从Laravel Query Builder获取sql查询的示例。
$ user = User :: where('id',1)-> toSql();
print_r($ user);
在此示例中,我们可以使用laravel查询构建器的toSql()直接获取当前的sql查询。
在此示例中,您无需启用查询日志或可以直接查询的东西(例如laravel中的波纹管最后查询):
例
这是简单的调试示例。
DB :: enableQueryLog();
$ employees = DB :: select('从员工那里选择*,其中1');
$ query = DB :: getQueryLog();
$ lastQuery = end($ query);
dump($ lastQuery);
输出
Array ( [query] => select * from employee where 1 [bindings] => Array ( ) [time] => 0.41 )
转:https://dev.to/pakainfo/last-query-in-laravel-1n0i
RSA加密原理:非对称加密鼻祖
加密算法,RSA是绕不开的话题,因为RSA算法是目前最流行的公开密钥算法,既能用于加密,也能用户数字签名。不仅在加密货币领域使用,在传统互联网领域的应用也很广泛。从被提出到现在20多年,经历了各种考验,被普遍认为是目前最优秀的公钥方案之一。比特币所使用的Sha256算法,也是在其基础之上建立的。了解RSA算法,相信你会对区块链有更深的认识
今天公司拿到光大银行的一个信用卡申卡类的接口对接文档,拿过来一看是公钥+指定参数rsa加密合作商请求接口数据,银行那边通过私钥解密通过回调数据
一般银行基本都是这种方式,好了话不多说开始敲代码
测试公钥
$public_key = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqDu3ICsMGhMVCOmWp6PP
tUml0Spy8WEmIDEBfczJdwCdq7REdT9IyjpkxRU7qjeE8LHnZ0uoufjz03fGwHiP
ghrM7gimWoCjxSVV1C678pONYvsx0yZl6GnmlZmOHibv/RLL5IUZYueqOcAd5NvX
xaEnw8TEi8gFpgm9HMZy0yGZg8nmjv+KDXBhy2Pkpb29kK/OkMpLF8X0WJnTILOP
IbtPrWisFYFl5JxZeGeGbPtdH0VBly+iWxjuKBBfVTDnun41NE1stRj26npDrx1G
dBkpeoPLJu3a6OvHpybjQo5oWbCIeIlH9KzgHpknjbikN3IrV/iq4kv901dc8qwa
kQIDAQAB
-----END PUBLIC KEY-----';
//公钥跟指定参数rsa加密
$pu_key = openssl_pkey_get_public($public_key);//这个函数可用来判断公钥是否是可用的
print_r($pu_key);echo "\n";
//银行指定参数
$data = '{"biz_channel": "KK","cardId": "","asseinfo": "1","pro_code": "FHTG177739SD0072Sxxx"}';
$encrypted = "";
openssl_public_encrypt($data,$encrypted,$pu_key);//公钥加密
$encrypted = base64_encode($encrypted);
echo $encrypted,"\n";
//curl请求
echo 111,"\n";
$http = new \GuzzleHttp\Client;
$url = 'http://xxx/getLink?tpenstr=
'.$encrypted;
$response = $http->get($url);
$total = json_decode((string) $response->getBody(), true);
dd($total);
银行那边拿到你加密的json串用它自己的私钥解密,解密成功就提示下面的提示,解密失败就会有失败的code
{
"message":"成功",
"code":"0000",
"url":"https://xyk.cebbank.com/icip/icip-a
pplypage/info1?pro_code=1\u0026cardId=1\u0026corp_id=MQIA20\u0026coopinfo
=58ca7c0021e2df916c114ad071909e50"
}
博主 在
国内用什么翻墙使用谷歌?上外网神器Ghelper插件详解中评论@请教 小图...
请教 在
国内用什么翻墙使用谷歌?上外网神器Ghelper插件详解中评论你好,我也遇到了安装完右...
Test11 在
laravel查询构造器中whereNotKey,whereKey,firstWhere用法详解中评论<script&g...
博主 在
国内用什么翻墙使用谷歌?上外网神器Ghelper插件详解中评论@西瓜: 每一步都操作完...
西瓜 在
国内用什么翻墙使用谷歌?上外网神器Ghelper插件详解中评论你好 我安装完右上角没有...
博主 在
laravel-admin 添加Excel导入功能中评论这应该算是比较详细了吧,...
唐伯虎 在
laravel-admin 添加Excel导入功能中评论咋导入excel
...博主 在
国内用什么翻墙使用谷歌?上外网神器Ghelper插件详解中评论希望能帮到你 老铁
Auther ·HouTiZong© 2009-2020 zongscan.com 版权所有ICP证:
粤ICP备20027696号
也可以扫右边的二维码