laravel核心知识学习

laravel 使用

  • 依赖注入,控制翻转,反射各个概念的理解和使用
<?php

// 定义写日志的接口规范
interface Log
{
    public function write();   
}

// 文件记录日志
class FileLog implements Log
{
    public function write(){
        echo 'file log write...';
    }   
}


// 数据库记录日志
class DatabaseLog implements Log
{
    public function write(){
        echo 'database log write...';
    }   
}

class User 
{
    protected $log;

    public function __construct(Log $log)
    {
        $this->log = $log;   
    }

    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }

}

$user = new User(new DatabaseLog());
$user->login();

用任何方式记录操作日志都不需要去修改 User 类了,只需要通过构造函数参数传递就可以实现,其实这就是“控制反转”。不需要自己内容修改,改成由外部传递。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。

那什么是依赖注入呢?,其实上面的例子也算是依赖注入,不是由自己内部 new 对象或者实例,通过构造函数,或者方法传入的都属于 依赖注入(DI)

反射的概念其实可以理解成根据类名返回该类的任何信息,比如该类有什么方法,参数,变量等等。我们先来学习下反射要用到的 api。拿 User 举例

<?php
// 获取User的reflectionClass对象
$reflector = new ReflectionClass(User::class);

// 拿到User的构造函数
$constructor = $reflector->getConstructor();

// 拿到User的构造函数的所有依赖参数
$dependencies = $constructor->getParameters();


// 创建user对象
$user = $reflector->newInstance();

// 创建user对象,需要传递参数的
$user = $reflector->newInstanceArgs($dependencies = []);

具体代码实现

<?php
// 注意我们这里需要修改一下User的构造函数,如果不去修改。反射是不能动态创建接口的,那如果非要用接口该怎么处理呢?下一节我们讲Ioc容器的时候会去解决。

class User 
{
    protected $log;

    public function __construct(FileLog $log)
    {
        $this->log = $log;   
    }

    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }

}

function make($concrete){
    
    $reflector = new ReflectionClass($concrete);
    $constructor = $reflector->getConstructor();
    // 为什么这样写的? 主要是递归。比如创建FileLog不需要传入参数。
    if(is_null($constructor)) {
        return $reflector->newInstance();
    }else {
        // 构造函数依赖的参数
        $dependencies = $constructor->getParameters();
        // 根据参数返回实例,如FileLog
        $instances = getDependencies($dependencies);
        return $reflector->newInstanceArgs($instances);
    }

}

function getDependencies($paramters) {
    $dependencies = [];
    foreach ($paramters as $paramter) {
        $dependencies[] = make($paramter->getClass()->name);
    }
    return $dependencies;
}

$user = make('User');
$user->login();
  • 如何实现Ioc容器和服务提供者是什么概念

提前把log,user都绑定到Ioc容器中。User的创建交给这个容器去做。比如下面这样的,你再任何地方使用login。都不需要关心是用什么记录日志了,哪怕后期需要修改只需要在ioc容器修改绑定其他记录方式日志就行了。

具体代码实现的思路 1.Ioc容器维护binding数组记录bind方法传入的键值对如:log=>FileLog, user=>User 2.在ioc->make('user')的时候,通过反射拿到User的构造函数,拿到构造函数的参数,发现参数是User的构造函数参数log,然后根据log得到FileLog。 3.这时候我们只需要通过make获取binding绑定的FileLog 4.通过newInstanceArgs然后再去创建new User($filelog); 核心的Ioc容器代码

<?php
interface Log
{
    public function write();
}

// 文件记录日志
class FileLog implements Log
{
    public function write(){
        echo 'file log write...';
    }
}

// 数据库记录日志
class DatabaseLog implements Log
{
    public function write(){
        echo 'database log write...';
    }
}

class User
{
    protected $log;
    public function __construct(Log $log)
    {
        $this->log = $log;
    }
    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }
}

class Ioc
{
    public $binding = [];

    public function bind($abstract, $concrete)
    {
        //这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建FileLog;
        $this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
            return $ioc->build($concrete);
        };

    }

    public function make($abstract)
    {
    	// 根据key获取binding的值
        $concrete = $this->binding[$abstract]['concrete'];
        return $concrete($this);
    }

    // 创建对象
    public function build($concrete) {
        $reflector = new ReflectionClass($concrete);
        $constructor = $reflector->getConstructor();
        if(is_null($constructor)) {
            return $reflector->newInstance();
        }else {
            $dependencies = $constructor->getParameters();
            $instances = $this->getDependencies($dependencies);
            return $reflector->newInstanceArgs($instances);
        }
    }

    // 获取参数的依赖
    protected function getDependencies($paramters) {
        $dependencies = [];
        foreach ($paramters as $paramter) {
            //根据名称获取以绑定的对象
            $dependencies[] = $this->make($paramter->getClass()->name);
        }
        return $dependencies;
    }

}

//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('Log','FileLog');
$ioc->bind('user','User');
$user = $ioc->make('user');
$user->login();

laravel中的服务容器和服务提供者是什么样子呢? 可以在config目录找到app.php中providers,这个数组定义的都是已经写好的服务提供者

<?php
$providers = [
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    Illuminate\Bus\BusServiceProvider::class,
    Illuminate\Cache\CacheServiceProvider::class,
    ...
]
...

// 随便打开一个类比如CacheServiceProvider,这个服务提供者都是通过调用register方法注册到ioc容器中,其中的app就是Ioc容器。singleton可以理解成我们的上面例子中的bind方法。只不过这里singleton指的是单例模式。

class CacheServiceProvider{
    public function register()
    {
        $this->app->singleton('cache', function ($app) {
            return new CacheManager($app);
        });

        $this->app->singleton('cache.store', function ($app) {
            return $app['cache']->driver();
        });

        $this->app->singleton('memcached.connector', function () {
            return new MemcachedConnector;
        });
    }
}
  • acades外观模式背后实现原理

上一节我们讲到需要 $ioc->make(‘user’) 才能拿到User的实例,再去使用 $user->login(); 那能不能更方便点,比如下面的用法,是不是很方便。 UserFacade::login();

Facade工作原理 1.Facade 核心实现原理就是在 UserFacade 提前注入Ioc容器。 2.定义一个服务提供者的外观类,在该类定义一个类的变量,跟ioc容器绑定的key一样, 3.通过静态魔术方法__callStatic可以得到当前想要调用的login 4.使用 static::$ioc->make('user');

<?php
class UserFacade
{
    // 维护Ioc容器
    protected static $ioc;

    public static function setFacadeIoc($ioc)
    {
        static::$ioc = $ioc;
    }

    // 返回User在Ioc中的bind的key
    protected static function getFacadeAccessor()
    {
        return 'user';
    }

    // php 魔术方法,在静态上下文中调用一个不可访问方法时会被触发
    public static function __callStatic($method, $args)
    {
        $instance = static::$ioc->make(static::getFacadeAccessor());
        return $instance->$method(...$args);
    }

}

//实例化IoC容器

$ioc = new Ioc();
$ioc->bind('log','FileLog');
$ioc->bind('user','User');

UserFacade::setFacadeIoc($ioc);

UserFacade::login();
  • 中间件实现
<?php
    interface Milldeware {
        public static function handle(Closure $next);
    }

    class VerfiyCsrfToekn implements Milldeware {

        public static function handle(Closure $next)
        {
            echo '验证csrf Token <br>';
            $next();
        }
    }

    class VerfiyAuth implements Milldeware {

        public static function handle(Closure $next)
        {
            echo '验证是否登录 <br>';
            $next();
        }
    }

    class SetCookie implements Milldeware {
        public static function handle(Closure $next)
        {
            $next();
            echo '设置cookie信息!';
        }
    }

    $handle = function() {
        echo '当前要执行的程序!';
    };

    $pipe_arr = [
        'VerfiyCsrfToekn',
        'VerfiyAuth',
        'SetCookie',
    ];

    //array_reduce 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
    $callback = array_reduce($pipe_arr, function($stack, $pipe) {
        return function() use ($stack, $pipe){
            return $pipe::handle($stack);
        };
    },$handle);
    
    //第一次迭代时,$stack:null,$pipe = VerfiyCsrfToekn,返回一个 use 了 VerfiyCsrfToekn 的闭包
    //第二次迭代时,$stack:use 了 VerfiyCsrfToekn 的闭包,$pipe = VerfiyAuth,返回一个 use 了 VerfiyAuth 的闭包
    // 第三次迭代时,$stack:use 了 VerfiyAuth 的闭包,$pipe = SetCookie,返回一个 use 了 SetCookie 的闭包
    

    //调用函数
    call_user_func($callback);
  • Laravel 入口文件说明
<?php
// 定义了laravel一个请求的开始时间
define('LARAVEL_START', microtime(true));

// composer自动加载机制
require __DIR__.'/../vendor/autoload.php';

// 这句话你就可以理解laravel,在最开始引入了一个ioc容器。
$app = require_once __DIR__.'/../bootstrap/app.php';

// 打开__DIR__.'/../bootstrap/app.php';你会发现这段代码,绑定了Illuminate\Contracts\Http\Kernel::class,
// 这个你可以理解成之前我们所说的$ioc->bind();方法。
// $app->singleton(
// 	Illuminate\Contracts\Http\Kernel::class,
//	App\Http\Kernel::class
// );

// 这个相当于我们创建了Kernel::class的服务提供者
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 获取一个 Request ,返回一个 Response。以把该内核想象作一个代表整个应用的大黑盒子,输入 HTTP 请求,返回 HTTP 响应。
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

// 就是把我们服务器的结果返回给浏览器。
$response->send(); 

// 这个就是执行我们比较耗时的请求,
$kernel->terminate($request, $response);
  • trait在Laravel中的应用

具体的案例 如我们定义类的时候,很多都要做成单例模式,因此我们只需要定一个trait在需要单例的时候use trait就可以了。下面看下代码

<?php
trait Singleton {
    protected static $_instance;

    final public static function getInstance() {
        if(!isset(self::$_instance)) {
            self::$_instance = new static();
        }

        return self::$_instance;
    }

    private function __construct() {
        $this->init();
    }

    protected function init() {}

}


class Db {
    use Singleton;
    protected function init() {

    }
}

trait在Laravel中的应用 AuthenticatesUsers 这个里面实现了根login的业务逻辑,但是呢?所有的可配置的参数,可重写改的方法你都可以在LoginController中就实现重写,这样代码不就简洁了嘛。我们随意找一个举例,我们重写login的验证加入极验验证。

<?php
namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{

    use AuthenticatesUsers;

    protected function redirectTo()
    {
        return '/';
    }

    protected $gee;

    public function __construct(GeeCaptchaController $gee)
    {
        $this->gee = $gee;
        $this->middleware('guest')->except('logout');
    }

    /**
     * 重写login验证逻辑,增加极验验证
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $this->validate($request, [
            $this->username() => 'required|string',
            'password' => 'required|string',
        ]);

        if (!$this->gee->verify()) {
            throw ValidationException::withMessages([
                'captcha' => '无效的验证码失效!',
            ]);
        }

    }
}
  • 负载均衡,分布式,集群的理解

集群 我们的项目如果跑在一台机器上,如果这台机器出现故障的话,或者用户请求量比较高,一台机器支撑不住的话。我们的网站可能就访问不了。那怎么解决呢?就需要使用多台机器,部署一样的程序,让几个机器同时的运行我们的网站。那怎么分发请求到我们的所有机器上?所以负载均衡的概念就出现了。

负载均衡 负载均衡是指基于反向代理能将现在所有的请求根据指定的策略算法,分发到不同的服务器上。常用实现负载均衡的可以用nginx,lvs。但是现在也有个问题,如果负载均衡服务器出现问题了怎么办?所以冗余的概念就出现了。

冗余 冗余其实就是两个或者多台服务器 一个主服务器,一个从服务器。 假设一个主服务器的负载均衡服务器出现了问题,从服务器能够替代主服务器来继续负载均衡。实现的方式就是使用keepalive来抢占虚拟主机。

分布式 分布式其实就是将一个大项目的拆分出来,单独运行。

举个上面的例子。假设我们的访问量特别大。我们就可以做成分布式,跟cdn一样的机制。在北京,杭州,深圳三个地方都搭建一个一模一样的集群。离北京近的用户就访问北京的集群,离深圳近的就访问深圳这边的集群。这样就将我们网站给拆分3个区域了,各自独立。

再举个例子比如我们redis分布式。redis分布式是将redis中的数据分布到不同的服务器上面,每台服务器存储不同的内容,而mysql集群是每台服务器都放着一样的数据。这也就理解了分布式和集群的概念。

mysql 主从 mysql master服务器会把sql操作日志写入到bin.log 日志里 slave服务器会去读master的bin.log 日志,然后执行sql语句。

主从有以下几个问题。

1.master服务器能写又能读,slave却只能读。

slave读取的数据还没有写入,这样该怎么解决呢?

1.假如缓存,从缓存中读取。 2.强制从master读取。 3.使用pxc集群,任何一个节点都是可读可写的,读写强一致性。

laravel如何解决数据不一致 在config/database.php mysql配置块中将sticky设置为true

sticky 是一个 可选值,它可用于立即读取在当前请求周期内已写入数据库的记录。若 sticky 选项被启用,并且当前请求周期内执行过 「写」 操作,那么任何 「读」 操作都将使用 「写」 连接。这样可确保同一个请求周期内写入的数据可以被立即读取到,从而避免主从延迟导致数据不一致的问题。不过是否启用它,取决于应用程序的需求。