上传图片/文件:
<div class="form-group mb-4"> <label for="" class="avatar-label">用户头像</label> <input type="file" name="avatar" class="form-control-file"> @if($user->avatar) <br> <img class="thumbnail img-responsive" src="{{ $user->avatar }}" width="200" /> @endif
</div>
view里面一个input, type是file, 记得把form的enctype="multipart/form-data"
在后台, 有两种方法, 都通过Request来获取上传的文件.
$ file = $request -> file('avatar');
$ file = $request -> avatar;
然后起一个类, app\Handlers\ImageHandler.php
namespace App\Handlers; class ImageUploadHandler { // 只允许以下后缀名的图片文件上传 protected $allowed_ext = ["png", "jpg", "gif", 'jpeg']; public function save($file, $folder, $file_prefix) { // 构建存储的文件夹规则,值如:uploads/images/avatars/201709/21/ // 文件夹切割能让查找效率更高。 $folder_name = "uploads/images/$folder/" . date("Ym/d", time()); // 文件具体存储的物理路径,public_path() 获取的是 public 文件夹的物理路径。 // 值如:/home/vagrant/Code/larabbs/public/uploads/images/avatars/201709/21/ $upload_path = public_path() . '/' . $folder_name; // 获取文件的后缀名,因图片从剪贴板里黏贴时后缀名为空,所以此处确保后缀一直存在 $extension = strtolower($file->getClientOriginalExtension()) ?: 'png'; // 拼接文件名,加前缀是为了增加辨析度,前缀可以是相关数据模型的 ID // 值如:1_1493521050_7BVc9v9ujP.png $filename = $file_prefix . '_' . time() . '_' . str_random(10) . '.' . $extension; // 如果上传的不是图片将终止操作 if ( ! in_array($extension, $this->allowed_ext)) { return false; } // 将图片移动到我们的目标存储路径中 $file->move($upload_path, $filename); return [ 'path' => config('app.url') . "/$folder_name/$filename" ]; } }
返回的是保存地址跟文件名.
在UserController里面调用这个handler
public function update(UserRequest $request,ImageUploadHandler $uploader, User $user) { $data = $request->all(); //如果上传了头像 if ($request->avatar) { //尝试保存头像并返回保存目录 $result = $uploader->save($request->avatar, 'avatars', $user->id); //成功之后, 写入数组 if ($result) { $data['avatar'] = $result['path']; } } //写库 $user->update($data); //dd($request->avatar); //$user->update($request->all()); return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功!'); }
引出一个问题, php的函数形参, 不讲求顺序的么?
可以乱来的?
这可能就是这种语言的好处, 函数都是可以变的, 不用编译, 不像c, java这种, 一定要定义好方法的形参, 返回值, 因为一旦编译完, 不可能动态的修改函数的形参, 甚至数据类型都不可以, 但是好处是执行效率更高, 而php的灵活性更好, 甚至function的调用生成都可以是动态的.
裁剪图片
如果用户的图片太大, 为了节省宝贵的CDN流量费用, 用第三方的包进行图片裁剪吧, Intervention/image
$ composer require intervention/image
获取配置信息
创建image的配置文件:
$ php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravel5"
在ImageUploadHandler里面写好裁剪的逻辑
... if ($max_width && $extension != 'gif') { // 此类中封装的函数,用于裁剪图片 $this->reduceSize($upload_path . '/' . $filename, $max_width); } ... public function reduceSize($file_path, $max_width) { // 先实例化,传参是文件的磁盘物理路径 $image = Image::make($file_path); // 进行大小调整的操作 $image->resize($max_width, null, function ($constraint) { // 设定宽度是 $max_width,高度等比例双方缩放 $constraint->aspectRatio(); // 防止裁图时图片尺寸变大 $constraint->upsize(); }); // 对图片修改后进行保存 $image->save(); }
安全性
游客是不可以edit任何人信息的, 现在不是, 所以要加中间件进行控制.
在UsersController里面, 添加auth中间件, 除了show操作之外都要进行auth.
public function __construct() { $this->middleware('auth', ['except' => ['show']]); }
之所以加在controller里面, 因为controller每次被访问都会被实例化一次, 用Log::debug('')可验证.
认证是一种逻辑, 另一种逻辑是, 用户自己编辑自己的资料, 而不能编辑别人的. 这样就需要UserPolicy, 生成policy可以用make:policy
$ php artisan make:policy UserPolicy
增加update方法,
public function update(User $currentUser, User $user) { return $currentUser->id === $user->id; }
然后再AuthServiceProvider里面注册一下这个update, 不然usercontroller的auth哪知道你定的什么规则.
protected $policies = [ 'App\Model' => 'App\Policies\ModelPolicy', \App\Models\User::class => \App\Policies\UserPolicy::class, ];
然后在UserController里面, 把这个update认证policy用authorize方法限定一下:
public function edit(User $user) { $this->authorize('update', $user); return view('users.edit', compact('user')); } public function update(UserRequest $request, ImageUploadHandler $uploader, User $user) { $this->authorize('update', $user); $data = $request->all(); if ($request->avatar) { $result = $uploader->save($request->avatar, 'avatars', $user->id, 416); if ($result) { $data['avatar'] = $result['path']; } } $user->update($data); return redirect()->route('users.show', $user->id)->with('success', '个人资料更新成功!'); }
这事儿是这么个意思吧, 本来是controller需要验证这个user, 如果单独写验证方法, 显得太傻, 无法复用, 于是写个规则, 就是这个叫'update'的policy, 然后将这个policy注册到auth的provider里面, 连同别的规则, 一起都放这里面, 然后你要使用的时候无论你是更新的时候还是update的时候, 都统一用authorize+policy名字的方法, 调用认证规则进行认证, 以后你要在其他controller, 操控其他资源, 也同样可以直接调用authorize这个policy即可, 这样就解耦了, 厉害!
这个就是"提供者provider"的pattern.