Laravel5: 学习笔记
Migration 应该是整个 laravel 框架的核心功能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#创建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 之外,官网介绍里还提供了一些其他函数,真的很赞。
很神奇的是,强大如 PhpStorm,居然识别不出这个函数的来源,不过运行起来是 ok 的,后来查了下,要稍微修改一下一个文件,就可以识别了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 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,这是个很神奇的东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#首先用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(); |
当然,还可以在 get() 之前再排序一下,然后取多少条,非常 elegant,Query Builder 里有更多的介绍。
然后视频介绍了 create() 方法,同时提到了这时候可能存在的安全问题,因为 POST 是可以模拟提交的,如果有人 POST 的时候自定义了 ID 或者自定义了 IS_ADMIN 这种字段,就会带来安全问题,所以这些字段如果被包含在 create() 里面,应该要返回一个失败页面。
相关资料:
- 从 Github 被 hack,谈 Rails 的安全性(mass-assignment)
- 网路安全
- 官方 Document 关于 Mass Assignment 的描述(翻译版)
- Laravel – Mass Assignment
- understanding-mass-assignment
看了一圈,大概理解 mass assignment 安全设定的重要性了。因为如果你要修改单个字段的值,你必定会手动通过一个查询语句把这条记录查出来,然后手动写一个 save() 命令把字段值更新进去。也就是说,你明确知道自己更新的是什么字段,也知道需要做什么安全方面的考虑。而如果你用 create() 方法,就会一次性把所有字段传到数据库里,这时候就容易被黑客 manipulate,因为你没有指定哪些点可以被更新,哪些点不能被更新,而是完全 trust 用户 input 的信息了。所以基于这点考虑,需要设定 $fillable 或者 $guard 来进行防范,比方 $id、$is_admin 这样的字段,不能让用户通过 post 表单的方式写进去,否则会带来很大的安全隐患,只能让 php 程序的内置函数生成这些值,而不是通过 post 方法传入。
以前因为都是手动写,所以没有想到过这个问题。
1 2 3 4 5 |
#定义在mass assignment中可以被update的字段 protected $fillable = [ 'submitter', 'request_type' ]; |
设好之后我执行了很多次,都还是提示 mass assignment,后来才想到要退出 tinker 重新进一次,视频里也是这么做的。
1 |
$new = App\requests::create(['request_type'=>1,'submitter'=>'Tim']); |
另外发现一个神奇的现象,用 find 方法无法后接 update,where 就可以,不过 find 确实不常用就是了,官方 document 也是用的 where。
试验了一下,update 方法不受 mass assignment 的管理,也可以理解,因为 update 那个字段是自己写的,自己要对自己负责。
最后就是把 MVC 连起来用,测试过程中发现要先做好 nginx 伪静态的设置:
1 2 3 4 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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。
如果想让查询结果包含被软删除的记录,可以这么做:
1 |
$r=App\requests::withTrashed()->find(1); |
很简单明了,恢复为有效记录就是:
1 |
$r=App\requests::withTrashed()->find(1)->restore(); |
真的是简单到不用学 SQL,彻底删除是 forcedelete(),我应该是不会用到它的。
再然后是关于 hyperlink 的写法,有 3 种。
- 第一种比较直观,直接写
1 |
<a href="requests/{{$request->id}}">xxx</a> |
2. 第二种是利用 controller
1 |
<a href="{{action ('RequestsController@show',$requests->id)}}">xxx</a> |
1 2 3 4 5 6 |
public function show($id){ $requests=Requests::where('id',$id)->get(); return view('request.show',compact('requests')); } |
其实这么做反而麻烦了,当然如果地址比较复杂,也有一定好处,因为可以直接用 compact 把多个变量交给 controller 去处理。
3. 第三种是用 url 函数
1 |
<a href="{{url('/requests',$requests->id)}}">xxx</a> |
比较有意思的是,url 函数会返回绝对路径,而前面两种是相对路径。
Route 里面是这么设置:
1 |
Route::get('requests/{id}','RequestsController@show'); |
获取地址里的,然后把 id 传入向 RequestsContorller 里的 show method。
当然,这么做不好的地方就是暴露了数据库 id,比方 2015-EU-OE,这样的地址,会更安全一些,缺点是加个字段,而且要人维护,还要保证 unique。
接下来是关于表单,视频里建议安装一个插件叫 illuminate\html:
1 2 |
cd /home/wwwroot/quality.mstar.top composer require illuminate/html |
然后修改配置:
1 2 3 4 5 6 7 8 9 |
#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 可以这样:
1 2 3 4 |
{!! Form::open() !!} {!! Form::close() !!} |
我想说,这样的事情我干过,我是这写的:
1 2 3 |
html::form("login_form","GET","index.php","FORM"); html::end("form"); |
殊途同归的感觉,其实自己造轮子确实也可以哈哈,说明我的思路是对滴,只是技术水平还不到家。
创建出来的 HTML 是这样的:
1 |
<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 的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{!! 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 的跳转:
1 |
Route::post('requests','RequestsController@store'); |
然后,修改一下 FORM post 的地址,同样也是有 3 种方法,最高大上的似乎是 route 方法,视频里没介绍,action 相对比较好,url 方法最简单但后期如果地址改变也最麻烦:
1 2 |
{!! Form::open(['action'=>'RequestsController@store']) !!} {!! Form::open(['url'=>'/requests']) !!} |
然后回到 RequestsController,建立一个新的 method 叫 store,然后有个比较 tricky 的地方,视频说把开头的一个 class 改成这样子:
1 2 |
#use Illuminate\Http\Request; use Request; |
然后所有 input 的东西,都会传到 Request Model 里面,用下面这段,就可以看到我们刚才输入的东西:
1 2 3 4 5 6 7 |
public function store(){ $input=Request::all(); return $input; } |
挺神奇的,接着就是写入数据库,可以用 create 方法,但要注意 mass assignment 的问题,我觉得比较安全的还是手动建一个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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。为了把最新的记录放在最前面,稍微改一下排序:
1 2 3 4 5 6 |
public function index(){ $requests=Requests::latest()->get(); return view('request.index',compact('requests')); } |
OK,下一节作者又卖了个关子,开始讲了一些周边的东西,其中包括 Model 的两种用法,不过掌握的不是很好,暂且不提,这篇笔记就写到这里。