Laravel5: 学习笔记

Migration 应该是整个 laravel 框架的核心功能了。

#创建 table
php artisan make:migration create_table_requests --create="requests"

#然后让它会在目录下生成一个 migration 的文件:\database\migrations\

#运行 php artisan 让这个表生效
php artisan make:migration

#Rollback
php artisan migrate:rollback

#增加删除字段
php artisan make:migration modify_table_requests --table="requests"

#然后就会生成一个新的 migration 文件,如果可以为空,可以加一个->nullable(),默认是不可为空。
$table->string("submitter")->nullable()->default("Michelle");

#Rollback 里面
$table->dropColumn("submitter");

nullable

除了 nullable 之外,官网介绍里还提供了一些其他函数,真的很赞。

很神奇的是,强大如 PhpStorm,居然识别不出这个函数的来源,不过运行起来是 ok 的,后来查了下,要稍微修改一下一个文件,就可以识别了:

// copy to 'vendor\laravel\framework\src\Illuminate\Support\Fluent.php'

/**
 * Class Fluent
 * @package Illuminate\Support
 *
 * @see http://laravel.com/docs/master/schema
 *
 * @method Fluent unsigned()                    Set INTEGER to UNSIGNED
 * @method Fluent nullable()                    Designate that the column allows NULL values
 * @method Fluent default(mixed $value)         Declare a default value for a column
 *
 * @method Fluent references(string $key)       Specifies the name of the foreign key constraint
 * @method Fluent on(string $table)             Specifies the table on which the constraint applies to
 * @method Fluent onDelete(string $action)      Specifies the action to happen on a DELETE
 * @method Fluent onUpdate(string $action)      Specifies the action to happen on an UPDATE
 */

然后就可以操作数据库了,用的是 Eloquent Model,这是个很神奇的东西。

#首先用 make:model 创建一个与数据库同名的 model
php artisan make:model requests

#会有一个新的 App\requests.php,里面很空,不过已经可以操作了,它主要是拓展了一个叫 Model 的 class,这个 Class 及其强大,涵盖了大量数据库操作参数,比方 save(), find() 等。

#然后,调试的时候,你可能想往数据库里插入一些数据,这个可以通过 tinker 进行操作。

php artisan tinker

#创建一个对象
$request=new App\request;

#建立一个新的记录
$request->request_type=1;

#写入数据库,就是这么简单
$request->save();

#如果你想预览一下 $request 里面的东西:
$requests;
$requests->toArray();

#还有一个跟时间有关的函数叫 Carbon:
Carbon\Carbon::now();

#用的是 UTC 时间,后面再研究下怎么调时区。

#查看数据库里的记录
App\requests::All()->toArray();

#如果要更新某个点
$request->submitter="Tim";

$request->save();

#我猜之所以可以这样做,因为它把一个记录作为了一个对象。如果你想操作新的记录,就要建一个新的对象。

#通过主键进行查找,可以用 find 函数
$findrequest=App\requests::find(1)
$findrequest->submitter;

#但通常我们查找都不是用主键了,这时候用 where 函数
$whosubmit=App\requests::where('submitter','Tim')->get();

orderby

当然,还可以在 get() 之前再排序一下,然后取多少条,非常 elegant,Query Builder 里有更多的介绍。

然后视频介绍了 create() 方法,同时提到了这时候可能存在的安全问题,因为 POST 是可以模拟提交的,如果有人 POST 的时候自定义了 ID 或者自定义了 IS_ADMIN 这种字段,就会带来安全问题,所以这些字段如果被包含在 create() 里面,应该要返回一个失败页面。

相关资料:

  1. 从 Github 被 hack,谈 Rails 的安全性(mass-assignment)
  2. 网路安全
  3. 官方 Document 关于 Mass Assignment 的描述(翻译版)
  4. Laravel – Mass Assignment
  5. understanding-mass-assignment

看了一圈,大概理解 mass assignment 安全设定的重要性了。因为如果你要修改单个字段的值,你必定会手动通过一个查询语句把这条记录查出来,然后手动写一个 save() 命令把字段值更新进去。也就是说,你明确知道自己更新的是什么字段,也知道需要做什么安全方面的考虑。而如果你用 create() 方法,就会一次性把所有字段传到数据库里,这时候就容易被黑客 manipulate,因为你没有指定哪些点可以被更新,哪些点不能被更新,而是完全 trust 用户 input 的信息了。所以基于这点考虑,需要设定 $fillable 或者 $guard 来进行防范,比方 $id、$is_admin 这样的字段,不能让用户通过 post 表单的方式写进去,否则会带来很大的安全隐患,只能让 php 程序的内置函数生成这些值,而不是通过 post 方法传入。

以前因为都是手动写,所以没有想到过这个问题。

mass assignment

#定义在 mass assignment 中可以被 update 的字段
protected $fillable = [
        'submitter',
        'request_type'
];

设好之后我执行了很多次,都还是提示 mass assignment,后来才想到要退出 tinker 重新进一次,视频里也是这么做的。

$new = App\requests::create(['request_type'=>1,'submitter'=>'Tim']);

update

另外发现一个神奇的现象,用 find 方法无法后接 update,where 就可以,不过 find 确实不常用就是了,官方 document 也是用的 where。

试验了一下,update 方法不受 mass assignment 的管理,也可以理解,因为 update 那个字段是自己写的,自己要对自己负责。

最后就是把 MVC 连起来用,测试过程中发现要先做好 nginx 伪静态的设置:

location / {
        index  index.php index.html index.htm;
	try_files $uri $uri/ /index.php?$query_string;
}

找到 location / { } 这段,加入第三行,注意不能写两个 location / { },会出错,重启 nginx 就可以让 route 生效。

MVC 的知识点有点杂,不一一列举,这里记录一个很有用的 softdelete 的方法。巨灵在数据库建立的时候,一定要加一个字段叫 Isvalid,0 就已经置无效,1 就是有效。这是非常有必要的做法,数据记录如果真的删除了,恢复起来很麻烦,而如果只是变成 Isvalid=0 的记录,可以很方便地进行数据恢复。以前做批量清洗,很多就是批量设为 0,然后重新导入。

Laravel 里把这个较为软删除(softdelete),首先需要在表内加一个新的 column 叫 deleted_at:

php artisan softdelete_requests --table=requests

    public function up()
    {
        Schema::table('requests', function (Blueprint $table) {
            $table->softDeletes();
        });
    }

    public function down()
    {
        Schema::table('requests', function (Blueprint $table) {
            $table->dropSoftDeletes();
        });
    }

php artisan migrate

这就完成了准备,然后在调用 delete() method 的时候,它就不会真的删除整条记录,而是把 deleted_at 加上一个时间,正常情况下不查询 deleted_at not null 的记录,这比 isvalid 更好,有个 delete time 可以查询,当然,如果不对 deleted_at 做编辑,updated_at 其实就是 deleted_at。

如果想让查询结果包含被软删除的记录,可以这么做:

$r=App\requests::withTrashed()->find(1);

很简单明了,恢复为有效记录就是:

$r=App\requests::withTrashed()->find(1)->restore();

真的是简单到不用学 SQL,彻底删除是 forcedelete(),我应该是不会用到它的。

官方 Document

再然后是关于 hyperlink 的写法,有 3 种。

  1. 第一种比较直观,直接写
<a href="requests/{{$request->id}}">xxx</a>

2. 第二种是利用 controller

<a href="{{action ('RequestsController@show',$requests->id)}}">xxx</a>
public function show($id){

    $requests=Requests::where('id',$id)->get();
    return view('request.show',compact('requests'));

}

其实这么做反而麻烦了,当然如果地址比较复杂,也有一定好处,因为可以直接用 compact 把多个变量交给 controller 去处理。

3. 第三种是用 url 函数

<a href="{{url('/requests',$requests->id)}}">xxx</a>

比较有意思的是,url 函数会返回绝对路径,而前面两种是相对路径。

Route 里面是这么设置:

Route::get('requests/{id}','RequestsController@show');

获取地址里的,然后把 id 传入向 RequestsContorller 里的 show method。

当然,这么做不好的地方就是暴露了数据库 id,比方 2015-EU-OE,这样的地址,会更安全一些,缺点是加个字段,而且要人维护,还要保证 unique。

接下来是关于表单,视频里建议安装一个插件叫 illuminate\html:

cd /home/wwwroot/quality.mstar.top
composer require illuminate/html

然后修改配置:

#config\app.php
Illuminate\View\ViewServiceProvider::class,
Illuminate\Html\HtmlServiceProvider::class,

...

'View'      => Illuminate\Support\Facades\View::class,
'Form'      => Illuminate\Html\FormFacade::class,
'Html'      => Illuminate\Html\HtmlFacade::class,

这样就可以用了,Form 可以这样:

    {!! Form::open() !!}


    {!! Form::close() !!}

我想说,这样的事情我干过,我是这写的:

html::form("login_form","GET","index.php","FORM");

html::end("form");

殊途同归的感觉,其实自己造轮子确实也可以哈哈,说明我的思路是对滴,只是技术水平还不到家。

创建出来的 HTML 是这样的:

    <form method="POST" action="http://localhost/requests/create" accept-charset="UTF-8"><input name="_token" type="hidden" value="WUC9rRU0kAYr0NqciceyxOgAGPEOrOWYvRtBgMJu">

据说 token 是为了 security reason,据说是为了防止 CSRF 跨站攻击,具体我也不懂。比较神奇的是这个包在 4.2 是自带的,现在米有了。

接下来介绍了各种 input 的用法:

    {!! Form::open() !!}
	<legend>Create a new request</legend>
    <div class="form-group">
        {!! Form::label('title','Title:') !!}
        {!! Form::text('title',null,['class'=>'form-control','placeholder'=>'please input..']) !!}
	</div>
	<div class="form-group">
		{!! Form::label('body','Request Description:') !!}
		{!! Form::textarea('body',null,['class'=>'form-control','placeholder'=>'please input..']) !!}
    </div>

	<div class="form-group">
		{!! Form::submit('Add Request',['class'=>'btn btn-primary form-control']) !!}
	</div>

    {!! Form::close() !!}

上面这一坨都可以弄成 live template,挺好玩的。

然后用户输入完数据之后跳转到哪里呢,通常我们说比较安全的做法是用 post,form::open() 默认的 method 也是 POST,所以我们在 route 里面建一个针对 post 的跳转:

Route::post('requests','RequestsController@store');

然后,修改一下 FORM post 的地址,同样也是有 3 种方法,最高大上的似乎是 route 方法,视频里没介绍,action 相对比较好,url 方法最简单但后期如果地址改变也最麻烦:

{!! Form::open(['action'=>'RequestsController@store']) !!}
{!! Form::open(['url'=>'/requests']) !!}

然后回到 RequestsController,建立一个新的 method 叫 store,然后有个比较 tricky 的地方,视频说把开头的一个 class 改成这样子:

#use Illuminate\Http\Request;
use Request;

然后所有 input 的东西,都会传到 Request Model 里面,用下面这段,就可以看到我们刚才输入的东西:

    public function store(){

        $input=Request::all();

        return $input;

    }

挺神奇的,接着就是写入数据库,可以用 create 方法,但要注意 mass assignment 的问题,我觉得比较安全的还是手动建一个对象:

    public function store(){

        $input=Request::all();

        $r=new Requests;
        $r['title']=$input['title'];
        $r['body']=$input['body'];
        $r['request_type']=1;

        $r->save();

        return redirect('requests');

    }

OK,这样就写入数据库了,然后 redirect 回 requests。为了把最新的记录放在最前面,稍微改一下排序:

    public function index(){

        $requests=Requests::latest()->get();

        return view('request.index',compact('requests'));
    }

OK,下一节作者又卖了个关子,开始讲了一些周边的东西,其中包括 Model 的两种用法,不过掌握的不是很好,暂且不提,这篇笔记就写到这里。

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.