澳门新浦京娱乐游戏Laravel 关联模型由于名称一致性导致的问题

1. 定义关联模型

在Laravel里面,我们可以通过定义以下Model来完成关联查询。

class MyPost extends Eloquent {
    public function myPostInfo () {
        return $this->hasOne('MyPostInfo');
    }
}

class MyPostInfo extends Eloquent {}

  Laravel作为在国内国外都颇为流行的PHP框架,风格优雅,其拥有自己的一些特点,且也发布长期支持版(LTS)。

适用于Laravel >= 5.3

2. 使用关联模型

这里myPostInfo()用的是Camel命名规则,但是我们在读取某一个PostInfo的时候可以用Snake规则。如下面代码都是可行的:

$post = MyPost::find(1);
$post_info = $post->myPostInfo; // example 1
$post_info = $post->my_post_info; // example 2

Laravel允许上述两种方法,但是没有合理的处理使用两种命名造成的冲突。

 

在Laravel 5.6/5.5/5.4/5.3版本中,路由文件默认放在了 routes 目录下。

3. 缓存失效

如果我们同时使用了上述两个例子,就会使其中一个缓存失效。在Model的relations变量中,缓存了已经读取过的关联Model,但是当我们用不同规则的名字去读取的时候,却会使得前一个缓存失效。例如

$post_info = $post->myPostInfo; 
// $post->relations = [‘myPostInfo’ => ..];

$post_info = $post->my_post_info;
// $post->relations = [‘myPostInfo’ => …, ‘my_post_info’ => …];

所以如果不希望缓存失效,得在项目中只使用一种命名方法去读取关系模型。Laravel推荐的是Camel
Case.

****. 请求周期

在官方的定义里,http路由有 2 个文件

4. toArray() 方法失效

如果同时使用了两者,另外一个问题就是导致Model::toArray()失效。因为toArray()方法首先去relations中查找Snake
Case命名的关联模型,没有的话才去看Camel Case的。

所以如果用到了toArray()方法来转换Model,切忌同时使用两者。

  Laravel
采用了单一入口模式,应用的所有请求入口都是 public/index.php
文件。

  • 一个是 routes 目录下的 web.php 文件,
    它定义了web界面的路由,被分配了web中间件组,以提供session和csrf防护等功能
  • 另一个是 routes 目录下的 api.php
    文件,文件中的路由则是无状态的,被分配了 api 中间件组。

5. 容易犯错的位置

最容易犯错的代码是这样的:

MyPost::with(‘myPostInfo’)->get();

在使用With去eagerLoad关联模型时,必须使用和定义方法同名的key去读取,那么这样读取出来的方法只能是Camel
Case的key。其他地方就只能用

$my_post->myPostInfo;

来保证不出问题。

  1. 注册类文件自动加载器:Laravel通过composer进行依赖管理,并在bootstrap/autoload.php中注册了Composer
    Auto Loader
    (PSR-4),应用中类的命名空间将被映射到类文件实际路径,不再需要开发者手动导入各种类文件,而由自动加载器自行导入。因此,Laravel允许你在应用中定义的类可以自由放置在Composer
    Auto
    Loader能自动加载的任何目录下,但大多数时候还是建议放置在app目录下或app的某个子目录下。
  2. 创建服务容器:从 bootstrap/app.php
    文件中取得 Laravel 应用实例 $app(服务容器)。
  3. 创建
    HTTP / Console 内核
    传入的请求在HTTP / Console
    内核中进行预处理。HTTP 内核继承自
    IlluminateFoundationHttpKernel 类,其中注入了 $app 和 $router 两个实例,内核是完成应用引导、请求处理(包括通过Router转发
    请求等)的场所。HTTP
    Kernel 定义了一个 bootstrappers
    数组,配置了环境变量加载、应用配置加载、错误处理,以及其他在请求被处理前需要完成的工作。
  4. 载入服务提供者至容器:在内核引导启动的过程中最重要的动作之一就是载入服务提供者到你的 $app (即所有的服务提供者都要挂载到服务容器下去执行),服务提供者负责引导启动框架的全部各种组件,例如数据库、队列、验证器以及路由组件。因为这些组件引导和配置了框架的各种功能,所以服务提供者是整个
    Laravel 启动过程中最为重要的部分,所有的服务提供者都配置在
    config/app.php 文件中的 providers 数组中。首先,所有提供者的
    register 方法会被调用;一旦所有提供者注册完成,接下来,boot
    方法将会被调用。
  5. 分发请求:一旦应用完成引导和所有服务提供者都注册完成,Request
    将会移交给Router进行分发。在通过Router转发请求时,所有请求都必须先经过全局HTTP中间件栈的处理,再调度到Router并获得其回调,然后执行该回调。

    关于中间件:
    (1)中间件好比一个过滤层,多个中间件就是多个过滤层,且它们有先后顺序。
    (2)全局中间件可分发前执行,也可分发后执行;中间件组使用组key(如’web’、’api’)来调用middleware(key)执行,中间件组仅仅是为了使一次将多个中间件指定给路由变得更加方便;路由中间件在(自定义)路由分发中或分发后执行,也是通过key(如’auth’)来调用middleware(key)执行的。
    (3)你也可以自定义前置或后置中间件,它们的差别在于在请求执行前还是执行后执行自定义动作。

    namespace AppHttpMiddleware;
    
    use Closure;
    
    class AfterMiddleware
    {
        public function handle($request, Closure $next)
        {
            $response = $next($request);
    
            // Perform action
    
            return $response;
        }
    }
    

     

    (4)如果你想在内核 handle 和 内核 terminate
    时使用同一个中间件实例,可使用容器的 singleton
    方法向容器注册中间件。

  6. 发送响应并结束:由Response发送响应,然后由内核发出terminate,包括调用可终止的中间件(定义了terminate方法的全局HTTP中间件和路由中间件)、 $app 服务容器终止。

(关于路由的更多解释请自行参见Laravel文档的路由部分)

     

无论是使用web中间件的路由还是api中间件的路由,随着项目越来越大,需要的定义的路由越来越多,如果几百上千个路由都定义在一个文件中,我相信将很难进行维护和管理。

二. 服务容器和服务提供者

因此,为了更方便的管理路由,我们需要对路由进行分割,模块化管理。

  服务容器是
Laravel 管理类依赖和运行依赖注入的有力工具,在类中可通过 $this->app 来访问容器,在类之外通过 $app 来访问容器;服务提供者是
Laravel
应用程序引导启动的中心,关系到服务提供者自身、事件监听器、路由的启动运行。因为应用程序中注册的路由通过RouteServiceProvider实例来加载,而事件监听器在EventServiceProvider类中进行注册。在新创建的应用中,AppServiceProvider
文件中方法实现都是空的,这个提供者是你添加应用专属的引导和服务的最佳位置,当然,对于大型应用你可能希望创建几个服务提供者,每个都具有粒度更精细的引导。服务提供者在
config/app.php 配置文件中的providers数组中进行注册

接下来我们细细道来:

<?php

namespace AppProviders;

use RiakConnection;
use IlluminateSupportServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * 在容器中注册绑定
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

app/Providers/RouteServiceProvider.php 这个文件负责处理 routes
目录下的所有 http 路由。

  

源代码片段:

三. 依赖注入

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    //
}

protected function mapWebRoutes()
{
    Route::middleware('web')
         ->namespace($this->namespace)
         ->group(base_path('routes/web.php'));
}

protected function mapApiRoutes()
{
    Route::prefix('api')
         ->middleware('api')
         ->namespace($this->namespace)
         ->group(base_path('routes/api.php'));
}

  Laravel
实现依赖注入方式有两种:自动注入和主动注册。自动注入通过参数类型提示由服务容器自动注入实现;主动注册则需开发人员通过绑定机制来实现,即绑定服务提供者或类(参考:  )。

从代码中可以看出,map()
方法是实际搜集全部路由的方法,为了代码职责/结构更加清晰,从而定义了
mapApiRoutes()mapApiRoutes() 两个具体的方法,并在 map()
中引用。

  1. 绑定服务提供者或类:这种方式对依赖注入的实现可以非常灵活多样

    use IlluminateSupportFacadesStorage;
    use AppHttpControllersPhotoController;
    use AppHttpControllersVideoController;
    use IlluminateContractsFilesystemFilesystem;
    
    $this->app->when(PhotoController::class)
              ->needs(Filesystem::class)
              ->give(function () {
                  return Storage::disk('local');
              });
    
    $this->app->when(VideoController::class)
              ->needs(Filesystem::class)
              ->give(function () {
                  return Storage::disk('s3');
              });
    
  2. 参数类型声明:通过对类的构造器参数类型、类的方法参数类型、闭包的参数类型给出提示来实现

    <?php
    
    namespace AppHttpControllers;
    
    use AppUsersRepository as UserRepository;
    
    class UserController extends Controller
    {
        /**
         * user repository 实例。
         */
        protected $users;
    
        /**
         * 控制器构造方法。
         *
         * @param  UserRepository  $users
         * @return void
         */
        public function __construct(UserRepository $users)
        {
            $this->users = $users;
        }
    
        /**
         * 储存一个新用户。
         *
         * @param  Request  $request
         * @return Response
         */
        public function store(Request $request)
        {
            $name = $request->input('name');
    
            //
        }
    }
    
  3. 路由参数依赖:下边的示例使用 IlluminateHttpRequest
    类型提示的同时还获取到路由参数id

    你的路由可能是这样定义的:
    Route::put('user/{id}', 'UserController@update');
    
    而控制器对路由参数id的依赖却可能是这样实现的:
    <?php
    
    namespace AppHttpControllers;
    
    use IlluminateHttpRequest;
    
    class UserController extends Controller
    {
        /**
         * 更新指定的用户。
         *
         * @param  Request  $request
         * @param  string  $id
         * @return Response
         */
        public function update(Request $request, $id)
        {
            //
        }
    }
    

     

换句话说,可以在 app/Providers/RouteServiceProvider.php
文件中新增方法去定义自己的路由!

**四. **Artisan Console 

public function map()
{
    $this->mapApiRoutes();
    $this->mapWebRoutes();

    $this->myRoutes();//自定义路由方法
}

protected function myRoutes()//自定义路由方法
{
    Route::prefix('xxx')//前缀
         ->middleware('xxx')//中间件
         ->namespace($this->namespace)
         ->group('path');//path为自定义路由文件绝对路径
}

......

  Laravel利用PHP的CLI构建了强大的Console工具artisan,artisan几乎能够创建任何你想要的模板类以及管理配置你的应用,在开发和运维管理中扮演着极其重要的角色,artisan是Laravel开发不可或缺的工具。在Laravel根目录下运行:PHP
artisan
list可查看所有命令列表。用好artisan能极大地简化开发工作,并减少错误发生;另外,还可以编写自己的命令。下面列举部分比较常用的命令:

可以料想到,如果路由文件很多的话,就得定义很多很多方法了,那上面这个方法就显得相当臃肿了!
(╯﹏╰)

  • 启用维护模式:php artisan down –message=’Upgrading Database’
    –retry=60
  • 关闭维护模式:php artisan up
  • 生成路由缓存:php artisan route:cache
  • 清除路由缓存:php artisan route:clear
  • 数据库迁移 Migrations:php artisan make:migration
    create_users_table –create=users
  • 创建资源控制器:php artisan make:controller PhotoController
    –resource –model=Photo
  • 创建模型及迁移:php artisan make:model User -m

下面介绍一种优雅的方式~

 

我们需要对 app/Providers/RouteServiceProvider.php 这个文件动一下手脚。

**五. 表单验证机制

  • 首先,在文件 app/Providers/RouteServiceProvider.php 中的
    RouteServiceProvider 类里面添加一个方法,姑且命名为
    loadRoutesFile($path), 这个方法的作用是递归遍历 routes
    目录下的全部文件:

    protected function loadRoutesFile($path){
        $allRoutesFilePath = array();
        foreach(glob($path) as $file){
            if(is_dir($file)){
                $allRoutesFilePath = array_merge($allRoutesFilePath, $this->loadRoutesFile($file.'/*'));
            }else{
                $allRoutesFilePath[] = $file;
            }
        }
        return $allRoutesFilePath;
    }
    
  • 修改 map() 方法为如下

    public function map()
    {
        $allRoutesFilePath = $this->loadRoutesFile(base_path('routes'));
    
        $this->mapApiRoutes($allRoutesFilePath);
    
        $this->mapWebRoutes($allRoutesFilePath);
    
        //
    }
    
  • 接着修改 mapWebRoutes()mapApiRoutes() 两个方法如下

    • mapWebRoutes() 方法

      protected function mapWebRoutes($allRoutesFilePath)
      {
          foreach ($allRoutesFilePath as $routesFilePath){
              if(ends_with($routesFilePath, 'web.php')){//匹配需要分配web中间的文件
                  Route::middleware('web')
                      ->namespace($this->namespace)
                      ->group($routesFilePath);
              }
          }
      }
      

      web中间件会被分配到 routes 目录下所有已 web.php
      结尾的文件
      。无论你如何命名,只要路径结尾包含 web.php
      就会被分配到web中间件。当然,这个匹配规则也可以根据你的自身需求修改。在
      if 判断语句处修改你的匹配规则即可。

      例如:

      routes
         |-- web.php
         |-- api.php
         |-- channels.php
         |-- console.php
         |-- welcome_web.php
         |-- Userweb.php
         |-- Role
              |-- role_web.php
              |-- roleUserweb.php
      

  表单验证在web开发中是不可或缺的,其重要性也不言而喻,也算是每个web框架的标配部件了。Laravel表单验证拥有标准且庞大的规则集,通过规则调用来完成数据验证,多个规则组合调用须以“|”符号连接,一旦验证失败将自动回退并可自动绑定视图。

    `web.php`、`welcome_web.php`、`Userweb.php`、`Role/role_web.php`、`Role/roleUserweb.php`
    这几个路由文件会被分配 web中间件。

-   `mapApiRoutes()` 方法

        protected function mapApiRoutes($allRoutesFilePath)
        {
            foreach ($allRoutesFilePath as $routesFilePath){
                if(ends_with($routesFilePath, 'api.php')){//匹配需要分配api中间的文件
                    Route::prefix('api')
                        ->middleware('api')
                        ->namespace($this->namespace)
                        ->group($routesFilePath);
                }
            }
        }

    **api中间件会被分配到 routes 目录下所有已 `api.php`
    结尾的文件**。无论你如何命名,只要路径结尾包含 `api.php`
    就会被分配到api中间件。当然,这个匹配规则也可以根据你的自身需求修改。在
    `if` 判断语句处修改你的匹配规则即可。

    例如:

        routes
           |-- web.php
           |-- api.php
           |-- channels.php
           |-- console.php
           |-- welcome_api.php
           |-- Userapi.php
           |-- Role
                |-- role_api.php
                |-- roleUserapi.php


    `api.php`、`welcome_api.php`、`Userapi.php`、`Role/role_api.php`、`Role/roleUserapi.php`
    这几个路由文件会被分配 api中间件。

  下例中,附加bail规则至title属性,在第一次验证required失败后将立即停止验证;“.”语法符号在Laravel中通常表示嵌套包含关系,这个在其他语言或框架语法中也比较常见

最后的最后~ 在生产环境上。通过 Laravel 的命令:

$this->validate($request, [
    'title' => 'bail|required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);
php artisan route:cache

Laravel验证规则参考 http://d.laravel-china.org/docs/5.4/validation#可用的验证规则 ;另外,在Laravel开发中还可采用如下扩展规则:

上面这个命令会生成 bootstrap/cache/routes.php 文件。

  1. 自定义FormRequest (须继承自
    IlluminateFoundationHttpFormRequest )
  2. Validator::make()手动创建validator实例
  3. 创建validator实例验证后钩子
  4. 按条件增加规则
  5. 数组验证
  6. 自定义验证规则

需要注意的是,路由缓存并不会作用在基于闭包的路由。要使用路由缓存,必须将所有闭包路由转换为控制器类。

 

使用闭包路由,在生成缓存时,将报错!

**六. 事件机制**

生成路由缓存文件后,路由就只会读取缓存文件的路由规则啦!不必担心引用多个文件带来的IO性能影响等等。

  Laravel事件机制是一种很好的应用解耦方式,因为一个事件可以拥有多个互不依赖的监听器。事件类
(Event) 类通常保存在 app/Events 目录下,而它们的监听类 (Listener)
类被保存在 app/Listeners 目录下,使用 Artisan
命令来生成事件和监听器时他们会被自动创建。

另外如果添加了任何新的路由,都必须生成新的路由缓存噢!

  1. 注册事件和监听器:EventServiceProvider的 listen
    属性数组用于事件(键)到对应的监听器(值)的注册,然后运行 php
    artisan
    event:generate将自动生成EventServiceProvider中所注册的事件(类)模板和监听器模板,然后在此基础之上进行修改来实现完整事件和监听器定义;另外,你也可以在
    EventServiceProvider 类的 boot 方法中通过注册闭包事件来实现
  2. 定义事件(类):事件(类)就是一个包含与事件相关信息数据的容器,不包含其它逻辑

     1 <?php
     2 
     3 namespace AppEvents;
     4 
     5 use AppOrder;
     6 use IlluminateQueueSerializesModels;
     7 
     8 class OrderShipped
     9 {
    10     use SerializesModels;
    11 
    12     public $order;
    13 
    14     /**
    15      * 创建一个事件实例。
    16      *
    17      * @param  Order  $order
    18      * @return void
    19      */
    20     public function __construct(Order $order)
    21     {
    22         $this->order = $order;
    23     }
    24 }
    
  3. 定义监听器:事件监听器在 handle 方法中接受了事件实例作为参数

     1 <?php
     2 
     3 namespace AppListeners;
     4 
     5 use AppEventsOrderShipped;
     6 
     7 class SendShipmentNotification
     8 {
     9     /**
    10      * 创建事件监听器。
    11      *
    12      * @return void
    13      */
    14     public function __construct()
    15     {
    16         //
    17     }
    18 
    19     /**
    20      * 处理事件
    21      *
    22      * @param  OrderShipped  $event
    23      * @return void
    24      */
    25     public function handle(OrderShipped $event)
    26     {
    27         // 使用 $event->order 来访问 order ...
    28     }
    29 }
    
  4. 停止事件传播:在监听器的 handle 方法中返回 false 来停止事件传播到其他的监听器

  5. 触发事件:调用 event
    辅助函数可触发事件,事件将被分发到它所有已经注册的监听器上

     1 <?php
     2 
     3 namespace AppHttpControllers;
     4 
     5 use AppOrder;
     6 use AppEventsOrderShipped;
     7 use AppHttpControllersController;
     8 
     9 class OrderController extends Controller
    10 {
    11     /**
    12      * 将传递过来的订单发货。
    13      *
    14      * @param  int  $orderId
    15      * @return Response
    16      */
    17     public function ship($orderId)
    18     {
    19         $order = Order::findOrFail($orderId);
    20 
    21         // 订单的发货逻辑...
    22 
    23         event(new OrderShipped($order));
    24     }
    25 }
    
  6. 队列化事件监听器:如果监听器中需要实现一些耗时的任务,比如发送邮件或者进行
    HTTP
    请求,那把它放到队列中处理是非常有用的。在使用队列化监听器,须在服务器或者本地环境中配置队列并开启一个队列监听器,还要增加
    ShouldQueue
    接口到你的监听器类;如果你想要自定义队列的连接和名称,你可以在监听器类中定义 $connection 和 $queue 属性;如果队列监听器任务执行次数超过在工作队列中定义的最大尝试次数,监听器的
    failed 方法将会被自动调用

     1 <?php
     2 
     3 namespace AppListeners;
     4 
     5 use AppEventsOrderShipped;
     6 use IlluminateContractsQueueShouldQueue;
     7 
     8 class SendShipmentNotification implements ShouldQueue
     9 {
    10     /**
    11      * 队列化任务使用的连接名称。
    12      *
    13      * @var string|null
    14      */
    15     public $connection = 'sqs';
    16 
    17     /**
    18      * 队列化任务使用的队列名称。
    19      *
    20      * @var string|null
    21      */
    22     public $queue = 'listeners';
    23 
    24     public function failed(OrderShipped $event, $exception)
    25     {
    26         //
    27     }       
    28 }
    
  7. 事件订阅者:事件订阅者允许在单个类中定义多个事件处理器,还应该定义一个
    subscribe
    方法,这个方法接受一个事件分发器的实例,通过调用事件分发器的 listen
    方法来注册事件监听器,然后在 EventServiceProvider 类的 $subscribe
    属性中注册订阅者

     1 <?php
     2 
     3 namespace AppListeners;
     4 
     5 class UserEventSubscriber
     6 {
     7     /**
     8      * 处理用户登录事件。
     9      */
    10     public function onUserLogin($event) {}
    11 
    12     /**
    13      * 处理用户注销事件。
    14      */
    15     public function onUserLogout($event) {}
    16 
    17     /**
    18      * 为订阅者注册监听器。
    19      *
    20      * @param  IlluminateEventsDispatcher  $events
    21      */
    22     public function subscribe($events)
    23     {
    24         $events->listen(
    25             'IlluminateAuthEventsLogin',
    26             'AppListenersUserEventSubscriber@onUserLogin'
    27         );
    28 
    29         $events->listen(
    30             'IlluminateAuthEventsLogout',
    31             'AppListenersUserEventSubscriber@onUserLogout'
    32         );
    33     }
    34 
    35 }
    

如果想要移除缓存路由文件的话,可以使用命令:

 

php artisan route:clear

七. Eloquent 模型

  Eloquent ORM
以ActiveRecord形式来和数据库进行交互,拥有全部的数据表操作定义,单个模型实例对应数据表中的一行

1 $flights = AppFlight::where('active', 1)
2                ->orderBy('name', 'desc')
3                ->take(10)
4                ->get(); 

  config/database.php中包含了模型的相关配置项。Eloquent
模型约定:

  1. 数据表名:模型以单数形式命名(CamelCase),对应的数据表为蛇形复数名(snake_cases),模型的$table属性也可用来指定自定义的数据表名称
  2. 主键:模型默认以id为主键且假定id是一个递增的整数值,也可以通过$primaryKey来自定义;如果主键非递增数字值,应设置$incrementing
    = false
  3. 时间戳:模型会默认在你的数据库表有 created_at 和 updated_at
    字段,设置$timestamps =
    false可关闭模型自动维护这两个字段;$dateFormat
    属性用于在模型中设置自己的时间戳格式
  4. 数据库连接:模型默认会使用应用程序中配置的数据库连接,如果你想为模型指定不同的连接,可以使用
    $connection 属性自定义
  5. 批量赋值:当用户通过 HTTP 请求传入了非预期的参数,并借助这些参数
    create
    方法更改了数据库中你并不打算要更改的字段,这时就会出现批量赋值(Mass-Assignment)漏洞,所以你需要先在模型上定义一个
    $fillable(白名单,允许批量赋值字段名数组) 或
    $guarded(黑名单,禁止批量赋值字段名数组)

    1 // 用属性取回航班,当结果不存在时创建它...
    2 $flight = AppFlight::firstOrCreate(['name' => 'Flight 10']);
    3 
    4 // 用属性取回航班,当结果不存在时实例化一个新实例...
    5 $flight = AppFlight::firstOrNew(['name' => 'Flight 10']);
    
  6. 模型软删除:如果模型有一个非空值
    deleted_at,代表模型已经被软删除了。要在模型上启动软删除,则必须在模型上使用IlluminateDatabaseEloquentSoftDeletes
    trait 并添加 deleted_at 字段到你的模型 $dates
    属性上和数据表中,通过调用trashed方法可查询模型是否被软删除

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 use IlluminateDatabaseEloquentSoftDeletes;
     7 
     8 class Flight extends Model
     9 {
    10     use SoftDeletes;
    11 
    12     /**
    13      * 需要被转换成日期的属性。
    14      *
    15      * @var array
    16      */
    17     protected $dates = ['deleted_at'];
    18 }
    
  7. 查询作用域:Laravel允许对模型设定全局作用域和本地作用域(包括动态范围),全局作用域允许我们为模型的所有查询添加条件约束(定义一个实现
    IlluminateDatabaseEloquentScope
    接口的类),而本地作用域允许我们在模型中定义通用的约束集合(模型方法前加上一个 scope 前缀)。作用域总是返回查询构建器

     1 全局作用域定义:
     2 <?php
     3 
     4 namespace AppScopes;
     5 
     6 use IlluminateDatabaseEloquentScope;
     7 use IlluminateDatabaseEloquentModel;
     8 use IlluminateDatabaseEloquentBuilder;
     9 
    10 class AgeScope implements Scope
    11 {
    12     /**
    13      * 应用作用域
    14      *
    15      * @param  IlluminateDatabaseEloquentBuilder  $builder
    16      * @param  IlluminateDatabaseEloquentModel  $model
    17      * @return void
    18      */
    19     public function apply(Builder $builder, Model $model)
    20     {
    21         return $builder->where('age', '>', 200);
    22     }
    23 }
    24 
    25 本地作用域:
    26 <?php
    27 
    28 namespace App;
    29 
    30 use IlluminateDatabaseEloquentModel;
    31 
    32 class User extends Model
    33 {
    34     /**
    35      * 限制查询只包括受欢迎的用户。
    36      *
    37      * @return IlluminateDatabaseEloquentBuilder
    38      */
    39     public function scopePopular($query)
    40     {
    41         return $query->where('votes', '>', 100);
    42     }
    43 
    44     /**
    45      * 限制查询只包括活跃的用户。
    46      *
    47      * @return IlluminateDatabaseEloquentBuilder
    48      */
    49     public function scopeActive($query)
    50     {
    51         return $query->where('active', 1);
    52     }
    53 }
    54 
    55 动态范围:
    56 <?php
    57 
    58 namespace App;
    59 
    60 use IlluminateDatabaseEloquentModel;
    61 
    62 class User extends Model
    63 {
    64     /**
    65      * 限制查询只包括指定类型的用户。
    66      *
    67      * @return IlluminateDatabaseEloquentBuilder
    68      */
    69     public function scopeOfType($query, $type)
    70     {
    71         return $query->where('type', $type);
    72     }
    73 }
    
  8. 隐藏和显示属性:模型 $hidden 属性用于隐藏属性和关联的输出,
    $visible
    属性用于显示属性和关联的输出,另外makeVisible()还可用来临时修改可见性。当你要对关联进行隐藏时,需使用关联的方法名称,而不是它的动态属性名称

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 在数组中可见的属性。
    11      *
    12      * @var array
    13      */
    14     protected $visible = ['first_name', 'last_name'];
    15 }
    16 ?>
    17 
    18 //makeVisible()用来临时修改可见性
    19 return $user->makeVisible('attribute')->toArray();
    
  9. 访问器和修改器:访问器(getFooAttribute)和修改器(setFooAttribute)可以让你修改
    Eloquent 模型中的属性或者设置它们的值,比如你想要使用 Laravel
    加密器来加密一个被保存在数据库中的值,当你从 Eloquent
    模型访问该属性时该值将被自动解密。访问器和修改器要遵循cameCase命名规范,修改器会设置值到
    Eloquent 模型内部的 $attributes 属性上

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 获取用户的名字。
    11      *
    12      * @param  string  $value
    13      * @return string
    14      */
    15     public function getFirstNameAttribute($value)
    16     {
    17         return ucfirst($value);
    18     }
    19 
    20      /**
    21      * 设定用户的名字。
    22      *
    23      * @param  string  $value
    24      * @return void
    25      */
    26     public function setFirstNameAttribute($value)
    27     {
    28         $this->attributes['first_name'] = strtolower($value);
    29     }
    30 }
    

    而对于访问器与修改器的调用将是模型对象自动进行的

    1 $user = AppUser::find(1);
    2 $user->first_name = 'Sally';//将自动调用相应的修改器
    3 $firstName = $user->first_name;//将自动调用相应的访问器 
    
  10. 追加属性:在转换模型到数组或JSON时,你希望添加一个在数据库中没有对应字段的属性,首先你需要为这个值定义一个
    访问器,然后添加该属性到改模型的 appends 属性中

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class User extends Model
     8 {
     9      /**
    10      * 访问器被附加到模型数组的形式。
    11      *
    12      * @var array
    13      */
    14     protected $appends = ['is_admin'];
    15 
    16     /**
    17      * 为用户获取管理者的标记。
    18      *
    19      * @return bool
    20      */
    21     public function getIsAdminAttribute()
    22     {
    23         return $this->attributes['admin'] == 'yes';
    24     }
    25 }
    
  11. 属性类型转换:$casts
    属性数组在模型中提供了将属性转换为常见的数据类型的方法,且键是那些需要被转换的属性名称,值则是代表字段要转换的类型。支持的转换的类型有:integer、real、float、double、string、boolean、object、array、collection、date、datetime、timestamp

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 应该被转换成原生类型的属性。
    11      *
    12      * @var array
    13      */
    14     protected $casts = [
    15         'is_admin' => 'boolean',//is_admin 属性以整数(0 或 1)被保存在我们的数据库中,把它转换为布尔值
    16     ];
    17 }
    
  12. 序列化: Laravel模型及关联可递归序列化成数组或JSON

     1 //单个模型实例序列化成数组
     2 $user = AppUser::with('roles')->first();
     3 return $user->toArray();
     4 //集合序列化成数组
     5 $users = AppUser::all();
     6 return $users->toArray();
     7 
     8 //单个模型实例序列化成JSON
     9 $user = AppUser::find(1);
    10 return $user->toJson();
    11 //直接进行string转换会将模型或集合序列化成JSON
    12 $user = AppUser::find(1);
    13 return (string) $user;
    14 //因此你可以直接从应用程序的路由或者控制器中返回 Eloquent 对象
    15 Route::get('users', function () {
    16     return AppUser::all();
    17 });
    
  13. 关联(方法)与动态属性:在 Eloquent
    模型中,关联被定义成方法(methods),也可以作为强大的查询语句构造器

    1 $user->posts()->where('active', 1)->get();
    

    Eloquent
    模型支持多种类型的关联:一对一、一对多、多对多、远层一对多、多态关联、多态多对多关联

    举个例子,一个 User 模型会关联一个 Phone
    模型,一对一关联(hasOne)

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 获取与用户关联的电话号码
    11      */
    12     public function phone()
    13     {
    14         return $this->hasOne('AppPhone');
    15     }
    16 }
    

    动态属性允许你访问关联方法,使用
    Eloquent
    的动态属性来获取关联记录,如同他们是定义在模型中的属性

    1 $phone = User::find(1)->phone;
    

    Eloquent
    会假设对应关联的外键名称是基于模型名称的。在这个例子里,它会自动假设
    Phone 模型拥有 user_id
    外键。如果你想要重写这个约定,则可以传入第二个参数到 hasOne 方法里

    1 return $this->hasOne('AppPhone', 'foreign_key');
    

    如果你想让关联使用 id 以外的值,则可以传递第三个参数至 hasOne
    方法来指定你自定义的键

    1 return $this->hasOne('AppPhone', 'foreign_key', 'local_key');
    

    如果我们要在 Phone
    模型上定义一个反向关联,此关联能够让我们访问拥有此电话的 User
    模型。我们可以定义与 hasOne 关联相对应的 belongsTo 方法

     1 <?php
     2 
     3 namespace App;
     4 
     5 use IlluminateDatabaseEloquentModel;
     6 
     7 class Phone extends Model
     8 {
     9     /**
    10      * 获取拥有该电话的用户模型。
    11      */
    12     public function user()
    13     {
    14         return $this->belongsTo('AppUser');
    15     }
    16 } 
    
  14. 模型事件: Laravel为模型定义的事件包括creating, created,
    updating, updated, saving, saved, deleting, deleted, restoring,
    restored。 模型上定义一个 $events 属性

     1 <?php
     2 
     3 namespace App;
     4 
     5 use AppEventsUserSaved;
     6 use AppEventsUserDeleted;
     7 use IlluminateNotificationsNotifiable;
     8 use IlluminateFoundationAuthUser as Authenticatable;
     9 
    10 class User extends Authenticatable
    11 {
    12     use Notifiable;
    13 
    14     /**
    15      * 模型的时间映射。
    16      *
    17      * @var array
    18      */
    19     protected $events = [
    20         'saved' => UserSaved::class,
    21         'deleted' => UserDeleted::class,
    22     ];
    23 }
    

     如果你在一个给定的模型中监听许多事件,也可使用观察者将所有监听器变成一个类,类的一个方法就是一个事件监听器

     1 定义观察者:
     2 <?php
     3 
     4 namespace AppObservers;
     5 
     6 use AppUser;
     7 
     8 class UserObserver
     9 {
    10     /**
    11      * 监听用户创建的事件。
    12      *
    13      * @param  User  $user
    14      * @return void
    15      */
    16     public function created(User $user)
    17     {
    18         //
    19     }
    20 
    21     /**
    22      * 监听用户删除事件。
    23      *
    24      * @param  User  $user
    25      * @return void
    26      */
    27     public function deleting(User $user)
    28     {
    29         //
    30     }
    31 }
    32 
    33 注册观察者:
    34 <?php
    35 
    36 namespace AppProviders;
    37 
    38 use AppUser;
    39 use AppObserversUserObserver;
    40 use IlluminateSupportServiceProvider;
    41 
    42 class AppServiceProvider extends ServiceProvider
    43 {
    44     /**
    45      * 运行所有应用.
    46      *
    47      * @return void
    48      */
    49     public function boot()
    50     {
    51         User::observe(UserObserver::class);
    52     }
    53 
    54     /**
    55      * 注册服务提供.
    56      *
    57      * @return void
    58      */
    59     public function register()
    60     {
    61         //
    62     }
    63 }
    

 

**八.
Laravel的Restful风格**

  一般认为Restful风格的资源定义不包含操作,但是在Laravel中操作(动词)也可作为一种资源来定义。下图是对Laravel中资源控制器操作原理的描述,可以看到,create、edit就直接出现在了URI中,它们是一种合法的资源。对于create和edit这两种资源的访问都采用GET方法来实现,第一眼看到顿感奇怪,后来尝试通过artisan
console生成资源控制器,并注意到其对create、edit给出注释“ Show the form
for ”字样,方知它们只是用来展现表单而非提交表单的。

澳门新浦京娱乐游戏 1

 

九. 扩展开发

  我们知道,Laravel本身是基于Composer管理的一个包,遵循Composer的相关规范,可以通过Composer来添加所依赖的其他Composer包,因此在做应用的扩展开发时,可以开发Composer包然后引入项目中即可;另外也可开发基于Laravel的专属扩展包。下面所讲的就是Laravel的专属扩展开发,最好的方式是使用
contracts ,而不是 facades,因为你开发的包并不能访问所有 Laravel
提供的测试辅助函数,模拟 contracts 要比模拟 facade 简单很多。

  • 服务提供者:服务提供者是你的扩展包与
    Laravel
    连接的重点,须定义自己的服务提供者并继承自 IlluminateSupportServiceProvider
    基类
  • 路由:若要为你的扩展包定义路由,只需在包的服务提供者的
    boot 方法中传递 routes 文件路径到 loadRoutesFrom
    方法即可

    1 /**
    2  * 在注册后进行服务的启动。
    3  *
    4  * @return void
    5  */
    6 public function boot()
    7 {
    8     $this->loadRoutesFrom(__DIR__.'/path/to/routes.php');
    9 }
    
  • 配置文件:你可以选择性地将扩展包的配置文件发布(publishes)到应用程序本身的config目录上或者合并(mergeConfigFrom)到应用程序里的副本配置文件中,但不应在配置文件中定义闭包函数,当执行
    config:cache Artisan命令时,它们将不能正确地序列化

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * 用户使用 vendor:publish 命令可将扩展包的文件将会被复制到指定的位置上。
     5  *
     6  * @return void
     7  */
     8 public function boot()
     9 {
    10     $this->publishes([
    11         __DIR__.'/path/to/config/courier.php' => config_path('courier.php'),
    12     ]);
    13 }
    14 
    15 $value = config('courier.option');//只要你的配置文件被发布,就可以如其它配置文件一样被访问
    16 
    17 /**
    18  * 或者选择性在容器中注册绑定。
    19  *
    20  * 此方法仅合并配置数组的第一级。如果您的用户部分定义了多维配置数组,则不会合并缺失的选项
    21  *
    22  * @return void
    23  */
    24 public function register()
    25 {
    26     $this->mergeConfigFrom(
    27         __DIR__.'/path/to/config/courier.php', 'courier'
    28     );
    29 }
    
  • 数据库迁移:如果你的扩展包包含数据库迁移,需要使用
    loadMigrationsFrom 方法告知 Laravel 如何去加载它们。在运行 php
    artisan migrate
    命令时,它们就会自动被执行,不需要把它们导出到应用程序的
    database/migrations 目录

    1 /**
    2  * 在注册后进行服务的启动。
    3  *
    4  * @return void
    5  */
    6 public function boot()
    7 {
    8     $this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
    9 }
    
  • 语言包:如果你的扩展包里面包含了本地化,则可以使用
    loadTranslationsFrom 方法来告知 Laravel
    该如何加载它们。下例假设你的包名称为courier

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
     9 
    10     //如果不想发布语言包至应用程序的 resources/lang/vendor 目录,请注销对$this->publishes()调用。运行 Laravel 的 vendor:publish Artisan 命令可将扩展包的语言包复制到指定的位置上
    11     $this->publishes([
    12         __DIR__.'/path/to/translations' => resource_path('lang/vendor/courier'),
    13     ]);
    14 }
    15 
    16 echo trans('courier::messages.welcome');//扩展包翻译参照使用了双分号 package::file.line 语法
    

     

  • 视图:若要在 Laravel 中注册扩展包 视图,则必须告诉 Laravel
    你的视图位置,loadViewsFrom
    方法允许传递视图模板路径与扩展包名称两个参数。需要特别指出的是,当你使用 loadViewsFrom
    方法时,Laravel 实际上为你的视图注册了两个位置:一个是应用程序的
    resources/views/vendor 目录,另一个是你所指定的目录。Laravel会先检查
    resources/views/vendor
    目录是否存在待加载视图,如果不存在,才会从指定的目录去加载,这个方法可以让用户很方便的自定义或重写扩展包视图。

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
     9 
    10     //若要发布扩展包的视图至 resources/views/vendor 目录,则必须使用服务提供者的 publishes 方法。运行 Laravel 的 vendor:publish Artisan 命令时,扩展包的视图将会被复制到指定的位置上 
    11     $this->publishes([
    12         __DIR__.'/path/to/views' => resource_path('views/vendor/courier'),
    13     ]); 
    14 }
    15 
    16 //扩展包视图参照使用了双分号 package::view 语法
    17 Route::get('admin', function () {
    18     return view('courier::admin');
    19 });
    
  • 命令:使用 commands 方法给扩展包注册 Artisan
    命令,命令的定义要遵循Laravel Artisan 命令规范

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     if ($this->app->runningInConsole()) {
     9         $this->commands([
    10             FooCommand::class,
    11             BarCommand::class,
    12         ]);
    13     }
    14 }
    
  • 公用 Assets:你可以发布像 JavaScript、CSS
    和图片这些资源文件到应用程序的 public 目录上。当用户执行 vendor:publish 命令时,您的
    Assets
    将被复制到指定的发布位置。由于每次更新包时通常都需要覆盖资源,因此您可以使用 --force 标志:php
    artisan vendor:publish –tag=public –force

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->publishes([
     9         __DIR__.'/path/to/assets' => public_path('vendor/courier'),
    10     ], 'public');
    11 }
    

     

  • 发布群组文件:你可能想让用户不用发布扩展包的所有资源文件,只需要单独发布扩展包的配置文件即可,通过在调用 publishes 方法时使用标签来实现

     1 /**
     2  * 在注册后进行服务的启动。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->publishes([
     9         __DIR__.'/../config/package.php' => config_path('package.php')
    10     ], 'config');
    11 
    12     $this->publishes([
    13         __DIR__.'/../database/migrations/' => database_path('migrations')
    14     ], 'migrations');
    15 }
    

    对于上例运行命令 php artisan vendor:publish –tag=config
    时将忽略掉migrations部分

发表评论

电子邮件地址不会被公开。 必填项已用*标注