Project-Flyer-11-Multi-File-Uploads

跟随 Jeffrey Way 的视频学习,脑子不好,一些细节容易忘记,这里记录下来。相关视频在 laracasts.com
课程名称:Build “ProjectFlyer” With Me(11)

本节主要实现文件上传,用到的上传插件是:Dropzone。

官网:http://www.dropzonejs.com
Github:https://github.com/enyo/dropzone/

我们搜索:cnd dropzone可以看到很多 cdn 加速,选择一个打开,里面有 dropzone 的在线引用地址。

这里我考虑到国内的 cdn 不怕被墙,另国内的 cdn 已经更新到4.0.1版本了,虽然国外已经更新到4.2.0,但是作者是用的4.0.1,我这里就用过内的 cdn 了。

在 master.blade.php:

<script src="/js/libs.js"></script>

的下面添加:

@yield('scripts.footer')

在 show.blade.php 最下面添加:

@section('scripts.footer')
    <script src="//cdn.bootcss.com/dropzone/4.0.1/min/dropzone.min.js"></script>
@stop

在 master.blade.php:

<link rel="stylesheet" type="text/css" href="/css/libs.css">

的下面添加:

<link href="//cdn.bootcss.com/dropzone/4.0.1/min/dropzone.min.css" rel="stylesheet">

在 show.blade.php 里添加一个表单,放在 description 的下面:

<form action="foobar" method="POST" class="dropzone"></form>

注意: 如果用 dropzone 即使你目前不确定 action 指向哪里也要填写内容,否则脚本会出错。官方给的方法是配置 dropzone:Dropzone.autoDiscover = false;,我觉得这反而麻烦。

浏览器打开 http://flyer.dev/{zip}/{street},zip 和 street 替换成你的数据库里的数据。

随便上传图片试试,通过浏览器开发者工具可以看到错误信息:TokenMismatchException
那么在 form 表单里加入一行:

{{ csrf_field() }}

再试上传图片,继续报错:MethodNotAllowedHttpException
查看头信息看到:Request URL:http://flyer.dev/123123/foobar
说明foobar 方法不存在,它当然不存在。
我们先添加一条路由:

Route::post('{zip}/{street}/photos', 'FlyersController@addPhoto');

下面去 FlyerController 添加 addPhoto 这个方法:

public function addPhoto()
{
    return "Working on it!";
}

修改 form 表单的 action:

<form action="/{{ $flyer->zip }}/{{ $flyer->street }}/photos" method="POST" class="dropzone">

测试下,以上操作没问题就可以上传图片了。下面我们修改下 addPhoto() 方法:

public function addPhoto(Request $request)
{
    dd($request->file('file'));
}

在试试,此时通过浏览器开发者工具能看到 photo 的页面内容类似:

UploadedFile {#30
-test: false
-originalName: "foobar.jpg"
-mimeType: "image/jpeg"
-size: 224149
-error: 0
}

查找并打开 vendor 里的 UploadFile.php 文件,可以找到方法:getClientOriginalExtension()getClientOriginalName(),我们在 addPhoto 方法里用它。
修改 addPhoto 方法为:

public function addPhoto(Request $request)
{
    $file = $request->file('file');
    $name = time() . $file->getClientOriginalName();
    $file->move('uploads/photos', $name);
    return 'Done';
}

再试着上传图片,可以看到图片被上传到了:public/uploads/photos
下面我们让上传的图片和实际的 flyer 数据产生关联。继续编辑 addPhoto 方法为:

public function addPhoto($zip, $street, Request $request)
{
    $file = $request->file('file');
    $name = time() . $file->getClientOriginalName();
    $file->move('uploads/photos', $name);
    $flyer = Flyer::locatedAt($zip, $street)->first();
    $flyer->photos()->create(['path' => "/uploads/photos/{$name}"]);
    return 'Done';
}

修改 app/Photo.php的:

protected $fillable = ['photo'];

为:

protected $fillable = ['path'];

这里我发现和作者的数据表结构略有不同,它的表里的 path 对应的我的表是 photo,所以我修改了下:
database/migrations/create_flyer_photos_table.php
修改:

$table->string('photo');

为:

$table->string('path');

然后在 homestead 虚拟机网站根目录下运行:

php artisan migrate:refresh

跟着视频做的过程中,到这一步我遇到了一个问题,flyers 路由和 public/flyers 文件夹冲突,我的办法是更改上传路径,相信有更好的办法吧,看作者视频是怎么解决的。

继续,修改 show.blade.php 模板,将 form 表单以上的 HTML 代码修改为:

<div class="row">
<div class="col-md-3">
<h1>{{ $flyer->street }}</h1>
<h2>{!! $flyer->price !!}</h2>
<div>{!! nl2br($flyer->description) !!}</div>
</div>

<div class="col-md-9">
@foreach ($flyer->photos as $photo)
<img src="/{{ $photo->path}}" alt="">
@endforeach
</div>

</div>
<hr>
<h2>Add Your Photos</h2>

刷新看看内容页的效果,为了防止图片太宽撑开页面,修改 app.scss,添加下面的样式:

img{
    max-width:100%;
}

然后运行gulp。再刷新下看看。
下面给 form 表单添加id:addPhotosForm,然后在 show.blade.php 里为 form 表单添加 dropzone 的设置:

<script src="//cdn.bootcss.com/dropzone/4.0.1/min/dropzone.min.js"></script>

的下面添加:

<script>
    Dropzone.options.addPhotosForm = {
        paramName: 'photo',
        maxFilesize: 3,
        acceptedFiles: '.jpg, .jpeg, .png, .gif, .bmp'
    }
</script>

paramName: 'photo',一行表示上传表单里的数据的namephoto,所以之前的请求调用默认是file,但现在都得改成photo
修改 FlyerController 控制器里的:

$file = $request->file('file');

为:

$file = $request->file('photo');

刷新页面,添加非图片格式会被组织,并提示错误。但这是脚本层的验证,我们还应该在控制器里的 addPhoto 方法里增加验证,加在方法的最开始:

$this->validate($request, [
    'photo' => 'required|mimes:jpg,jpeg,png,gif,bmp'
]);

然后优化 Flyer::locatedAt 调用,打开 app/Flyer.php,修改为:

/**
 * Find the flyer at the given address.
 * @param  Builder $query  
 * @param  string $zip    
 * @param  string $street 
 * @return Builder         
 */
public static function locatedAt($zip, $street)
{
    $street = str_replace('-', ' ', $street);
    return static::where(compact('zip', 'street'))->first();
}

修改控制器,现在可以把 first() 去掉了,把:

$flyer = Flyer::locatedAt($zip, $street)->first();

修改为:

$flyer = Flyer::locatedAt($zip, $street);

删除:

$flyer->photos()->create(['path' => "/uploads/photos/{$name}"]);

修改:

$flyer = Flyer::locatedAt($zip, $street);

为:

Flyer::locatedAt($zip, $street)->addPhoto($photo);

此时并不存在 addPhoto 方法,打开 app/Flyer.php 添加这个方法:

public function addPhoto(Photo $photo)
{
    return $this->photos()->save($photo);
}

打开 FlyerController 控制器,添加引用:

use App\Photo;

在:

Flyer::locatedAt($zip, $street)->addPhoto($photo);

的前面添加:

$photo = Photo::fromForm($reuqest->file('photo'));

fromForm 方法是不存在的,我们去 app/Photo.php 里去创建它:

public static function fromForm(UploadedFile $file)
{
    $photo = new static;
}

剪切 FlyerController 控制器里 addPhoto 方法的这一行:

$name = time() . $file->getClientOriginalName();

到 app/Photo.php 的 fromForm 方法里,然后添加类变量:

protected $baseDir = '/uploads/photos';

在 fromForm 里为静态变量赋值:

$photo->path = $this->baseDir . '/' . $name;

删除 FlyerController 控制器里 addPhoto 方法的:

$file = $request->file('photo');

$file->move('uploads/photos', $name);

到 Photo.php 的 fromform 方法里,添加两行:

$file->move($photo->baseDir, $name);
return $photo;

添加 UploadedFile 的引用:

use Symfony\Component\HttpFoundation\File\UploadedFile;

因为静态方法,所以不应该再有$this这样的调用修改 $this$photo

最终,Photo.php 为:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class Photo extends Model
{
    protected $table = 'flyer_photos';
    protected $fillable = ['path'];
    protected $baseDir = 'uploads/photos';

    public function flyer()
    {
        return $this->belongsTo('App\Flyer');
    }

    public static function fromForm(UploadedFile $file)
    {
        $photo = new static;
        $name = time() . $file->getClientOriginalName();
        $photo->path = $photo->baseDir . '/' . $name;
        $file->move($photo->baseDir, $name);
        return $photo;
    }
}

Photo.php:

$photo->path = '/' . $photo->baseDir . '/' . $name; 

或者 show.blade.php:

<img src="/{{ $photo->path }}" alt="" />

总的有一个地方在前面加入/,否则图片会显示出错,作者的做法是后者。原因很简单,数据库里的数据不好改,取出来到界面了处理相对容易些。

控制器的 addPhoto 方法为:

public function addPhoto($zip, $street, Request $request)
{
    $this->validate($request, [
        'photo' => 'required|mimes:jpg,jpeg,png,gif,bmp'
    ]);

    $photo = Photo::fromForm($request->file('photo'));

    Flyer::locatedAt($zip, $street)->addPhoto($photo);

    // $flyer->photos()->create(['path' => "/uploads/photos/{$name}"]);

    return 'Done';
}

最后修改路由,增加中间件。打开 route.php,将:

Route::post('{zip}/{street}/photos', 'FlyersController@addPhoto');

修改为:

Route::post('{zip}/{street}/photos', ['as' => 'store_photo_path', 'uses' => 'FlyersController@addPhoto']);

由此就可以优化 form 表单的 action 了,修改:

action="/{{ $flyer->zip }}/{{ $flyer->street }}/photos"

为:

action="{{ route('store_photo_path', [$flyer->zip, $flyer->street]) }}"
0%