Nền tảng Kiến thức - Hành trang tới Tương lai
Card image

Chương 9-Bài 11. Xây dựng danh mục Sản phẩm có chức năng upload nhiều Hình ảnh cùng lúc

Tác giả: Dương Nguyễn Phú Cường #9259
Ngày đăng: Hồi xưa đó
Lượt xem: 459

Xem xét mối quan hệ giữa table cusc_sanphamcusc_hinhanh

Thiết kế của table cusc_hinhanh

Do table cusc_hinhanh sử dụng cặp khóa chính gồm 2 cột (columns) là sp_maha_stt nên chúng ta cần hiệu chỉnh model HinhAnh để có thể đáp ứng được việc lưu trữ cặp khóa chính thông qua Laravel như sau:

Step 1: Hiệu chỉnh file model app\HinhAnh.php, bổ sung các hàm xử lý với trường hợp sử dụng cặp khóa chính (Multi PrimaryKey) model HinhAnh:

<?php

namespace App;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class HinhAnh extends Model
{
    public    $timestamps   = false; //created_at, updated_at

    protected $table        = 'cusc_hinhanh';
    protected $fillable     = ['ha_ten'];
    protected $guarded      = ['sp_ma', 'ha_stt'];

    protected $primaryKey   = ['sp_ma', 'ha_stt'];
    public    $incrementing = false;

    /**
     * Set the keys for a save update query.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected function setKeysForSaveQuery(Builder $query)
    {
        $keys = $this->getKeyName();
        if(!is_array($keys)){
            return parent::setKeysForSaveQuery($query);
        }

        foreach($keys as $keyName){
            $query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
        }

        return $query;
    }

    /**
     * Get the primary key value for a save query.
     *
     * @param mixed $keyName
     * @return mixed
     */
    protected function getKeyForSaveQuery($keyName = null)
    {
        if(is_null($keyName)){
            $keyName = $this->getKeyName();
        }

        if (isset($this->original[$keyName])) {
            return $this->original[$keyName];
        }

        return $this->getAttribute($keyName);
    }
}

Step 2: Thêm các hàm lấy danh sách Hình ảnh liên quan trong Model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class SanPham extends Model
{
    const     CREATED_AT    = 'sp_taoMoi';
    const     UPDATED_AT    = 'sp_capNhat';

    protected $table        = 'cusc_sanpham';
    protected $fillable     = ['sp_ten', 'sp_giaGoc', 'sp_giaBan', 'sp_hinh', 'sp_thongTin', 'sp_danhGia', 'sp_taoMoi', 'sp_capNhat', 'sp_trangThai', 'l_ma'];
    protected $guarded      = ['sp_ma'];

    protected $primaryKey   = 'sp_ma';

    protected $dates        = ['sp_taoMoi', 'sp_capNhat'];
    protected $dateFormat   = 'Y-m-d H:i:s';

    public function loaisanpham()
    {
        return $this->belongsTo('App\Loai', 'l_ma', 'l_ma');
    }

    public function hinhanhlienquan()
    {
        return $this->hasMany('App\HinhAnh', 'sp_ma', 'sp_ma');
    }
}

Step 3: Hiệu chỉnh chức năng Thêm mới Sản phẩm (create), bổ sung thêm ô chọn file cho phép người dùng upload cùng lúc nhiều hình ảnh

  • Hiệu chỉnh view: resources/views/backend/sanpham/create.blade.php
@extends('backend.layouts.master')

@section('title')
Thêm mới sản phẩm
@endsection

@section('custom-css')
<!-- Các css dành cho thư viện bootstrap-fileinput -->
<link href="{{ asset('vendor/bootstrap-fileinput/css/fileinput.css') }}" media="all" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" crossorigin="anonymous">
<link href="{{ asset('vendor/bootstrap-fileinput/themes/explorer-fas/theme.css') }}" media="all" rel="stylesheet" type="text/css" />
@endsection

@section('content')
@if ($errors->any())
<div class="alert alert-danger">
  <ul>
    @foreach ($errors->all() as $error)
    <li>{{ $error }}</li>
    @endforeach
  </ul>
</div>
@endif

<form method="post" action="{{ route('admin.sanpham.store') }}" enctype="multipart/form-data">
  {{ csrf_field() }}
  <div class="form-group">
    <label for="l_ma">Loại sản phẩm</label>
    <select name="l_ma" class="form-control">
      @foreach($danhsachloai as $loai)
      @if(old('l_ma') == $loai->l_ma)
      <option value="{{ $loai->l_ma }}" selected>{{ $loai->l_ten }}</option>
      @else
      <option value="{{ $loai->l_ma }}">{{ $loai->l_ten }}</option>
      @endif
      @endforeach
    </select>
  </div>
  <div class="form-group">
    <label for="sp_ten">Tên sản phẩm</label>
    <input type="text" class="form-control" id="sp_ten" name="sp_ten" value="{{ old('sp_ten') }}">
  </div>
  <div class="form-group">
    <label for="sp_giaGoc">Giá gốc</label>
    <input type="text" class="form-control" id="sp_giaGoc" name="sp_giaGoc" value="{{ old('sp_giaGoc') }}">
  </div>
  <div class="form-group">
    <label for="sp_giaGoc">Giá bán</label>
    <input type="text" class="form-control" id="sp_giaBan" name="sp_giaBan" value="{{ old('sp_giaBan') }}">
  </div>
  <div class="form-group">
    <div class="file-loading">
      <label>Hình đại diện</label>
      <input id="sp_hinh" type="file" name="sp_hinh">
    </div>
  </div>
  <div class="form-group">
    <label for="sp_thongTin">Thông tin</label>
    <input type="text" class="form-control" id="sp_thongTin" name="sp_thongTin" value="{{ old('sp_thongTin') }}">
  </div>
  <div class="form-group">
    <label for="sp_danhGia">Đánh giá</label>
    <input type="text" class="form-control" id="sp_danhGia" name="sp_danhGia" value="{{ old('sp_danhGia') }}">
  </div>
  <div class="form-group">
    <label for="sp_taoMoi">Ngày tạo mới</label>
    <input type="text" class="form-control" id="sp_taoMoi" name="sp_taoMoi" value="{{ old('sp_taoMoi') }}">
  </div>
  <div class="form-group">
    <label for="sp_capNhat">Ngày cập nhật</label>
    <input type="text" class="form-control" id="sp_capNhat" name="sp_capNhat" value="{{ old('sp_capNhat') }}">
  </div>
  <div class="form-group">
    <label for="sp_trangThai">Trạng thái</label>
    <select name="sp_trangThai" class="form-control">
      <option value="1" {{ old('sp_trangThai') == 1 ? "selected" : "" }}>Khóa</option>
      <option value="2" {{ old('sp_trangThai') == 2 ? "selected" : "" }}>Khả dụng</option>
    </select>
  </div>
  <div class="form-group">
    <div class="file-loading">
      <label>Hình ảnh liên quan sản phẩm</label>
      <input id="sp_hinhanhlienquan" type="file" name="sp_hinhanhlienquan[]" multiple>
    </div>
  </div>
  <button type="submit" class="btn btn-primary">Lưu</button>
</form>
@endsection

@section('custom-scripts')
<!-- Các script dành cho thư viện bootstrap-fileinput -->
<script src="{{ asset('vendor/bootstrap-fileinput/js/plugins/sortable.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/js/fileinput.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/js/locales/fr.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/themes/fas/theme.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/themes/explorer-fas/theme.js') }}" type="text/javascript"></script>

<script>
  $(document).ready(function() {
    $("#sp_hinh").fileinput({
      theme: 'fas',
      showUpload: false,
      showCaption: false,
      browseClass: "btn btn-primary btn-lg",
      fileType: "any",
      previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
      overwriteInitial: false
    });

    // Ô nhập liệu cho phép chọn nhiều hình ảnh cùng lúc (các hình ảnh liên quan đến sản phẩm)
    $("#sp_hinhanhlienquan").fileinput({
      theme: 'fas',
      showUpload: false,
      showCaption: false,
      browseClass: "btn btn-primary btn-lg",
      fileType: "any",
      previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
      overwriteInitial: false,
      allowedFileExtensions: ["jpg", "gif", "png", "txt"]
    });
  });
</script>

<!-- Các script dành cho thư viện Mặt nạ nhập liệu InputMask -->
<script src="{{ asset('vendor/input-mask/jquery.inputmask.min.js') }}"></script>
<script src="{{ asset('vendor/input-mask/bindings/inputmask.binding.js') }}"></script>

<script>
  $(document).ready(function() {
    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Giá gốc
    $('#sp_giaGoc').inputmask({
      alias: 'currency',
      positionCaretOnClick: "radixFocus",
      radixPoint: ".",
      _radixDance: true,
      numericInput: true,
      groupSeparator: ",",
      suffix: ' vnđ',
      min: 0, // 0 ngàn
      max: 100000000, // 1 trăm triệu
      autoUnmask: true,
      removeMaskOnSubmit: true,
      unmaskAsNumber: true,
      inputtype: 'text',
      placeholder: "0",
      definitions: {
        "0": {
          validator: "[0-9\uFF11-\uFF19]"
        }
      }
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Giá bán
    $('#sp_giaBan').inputmask({
      alias: 'currency',
      positionCaretOnClick: "radixFocus",
      radixPoint: ".",
      _radixDance: true,
      numericInput: true,
      groupSeparator: ",",
      suffix: ' vnđ',
      min: 0, // 0 ngàn
      max: 100000000, // 1 trăm triệu
      autoUnmask: true,
      removeMaskOnSubmit: true,
      unmaskAsNumber: true,
      inputtype: 'text',
      placeholder: "0",
      definitions: {
        "0": {
          validator: "[0-9\uFF11-\uFF19]"
        }
      }
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Ngày tạo mới
    $('#sp_taoMoi').inputmask({
      alias: 'datetime',
      inputFormat: 'yyyy-mm-dd' // Định dạng Năm-Tháng-Ngày
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Ngày cập nhật
    $('#sp_capNhat').inputmask({
      alias: 'datetime',
      inputFormat: 'yyyy-mm-dd' // Định dạng Năm-Tháng-Ngày
    });
  });
</script>

@endsection

Step 4: hiệu chỉnh action store của Backend\SanPhamController

Bổ sung chức năng Lưu trữ cùng lúc nhiều hình ảnh
use App\HinhAnh; // Chúng ta cần sử dụng model HinhAnh để truy vấn dữ liệu

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    // Bổ sung ràng buộc Validate
    $validation = $request->validate([
        'sp_hinh' => 'required|file|image|mimes:jpeg,png,gif,webp|max:2048',

        // Cú pháp dùng upload nhiều file
        'sp_hinhanhlienquan.*' => 'file|image|mimes:jpeg,png,gif,webp|max:2048'
    ]);

    // Tạo mới object SanPham
    $sp = new SanPham();
    $sp->sp_ten = $request->sp_ten;
    $sp->sp_giaGoc = $request->sp_giaGoc;
    $sp->sp_giaBan = $request->sp_giaBan;
    $sp->sp_thongTin = $request->sp_thongTin;
    $sp->sp_danhGia = $request->sp_danhGia;
    $sp->sp_taoMoi = $request->sp_taoMoi;
    $sp->sp_capNhat = $request->sp_capNhat;
    $sp->sp_trangThai = $request->sp_trangThai;
    $sp->l_ma = $request->l_ma;

    // Kiểm tra xem người dùng có upload hình ảnh Đại diện hay không?
    if ($request->hasFile('sp_hinh')) {
        $file = $request->sp_hinh;

        // Lưu tên hình vào column sp_hinh
        $sp->sp_hinh = $file->getClientOriginalName();

        // Chép file vào thư mục "storate/public/photos"
        $fileSaved = $file->storeAs('public/photos', $sp->sp_hinh);
    }
    $sp->save();

    // Lưu hình ảnh liên quan
    if($request->hasFile('sp_hinhanhlienquan')) {
        $files = $request->sp_hinhanhlienquan;

        // duyệt từng ảnh và thực hiện lưu
        foreach ($request->sp_hinhanhlienquan as $index => $file) {
            
            $file->storeAs('public/photos', $file->getClientOriginalName());

            // Tạo đối tưọng HinhAnh
            $hinhAnh = new HinhAnh();
            $hinhAnh->sp_ma = $sp->sp_ma;
            $hinhAnh->ha_stt = ($index + 1);
            $hinhAnh->ha_ten = $file->getClientOriginalName();
            $hinhAnh->save();
        }
    }

    // Hiển thị câu thông báo 1 lần (Flash session)
    Session::flash('alert-info', 'Them moi thanh cong ^^~!!!');

    // Điều hướng về route index
    return redirect()->route('admin.sanpham.index');
}

Step 5: Hiệu chỉnh chức năng Sửa Sản phẩm (edit), bổ sung thêm ô chọn file cho phép người dùng upload cùng lúc nhiều hình ảnh

  • Hiệu chỉnh view: resources/views/backend/sanpham/edit.blade.php
@extends('backend.layouts.master')

@section('title')
Hiệu chỉnh sản phẩm
@endsection

@section('custom-css')
<!-- Các css dành cho thư viện bootstrap-fileinput -->
<link href="{{ asset('vendor/bootstrap-fileinput/css/fileinput.css') }}" media="all" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" crossorigin="anonymous">
<link href="{{ asset('vendor/bootstrap-fileinput/themes/explorer-fas/theme.css') }}" media="all" rel="stylesheet" type="text/css"/>
@endsection

@section('content')
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<form method="post" action="{{ route('admin.sanpham.update', ['id' => $sp->sp_ma]) }}" enctype="multipart/form-data">
    <input type="hidden" name="_method" value="PUT" />
    {{ csrf_field() }}
    <div class="form-group">
        <label for="l_ma">Loại sản phẩm</label>
        <select name="l_ma" class="form-control">
            @foreach($danhsachloai as $loai)
                @if($loai->l_ma == $sp->l_ma)
                <option value="{{ $loai->l_ma }}" selected>{{ $loai->l_ten }}</option>
                @else
                <option value="{{ $loai->l_ma }}">{{ $loai->l_ten }}</option>
                @endif
            @endforeach
        </select>
    </div>
    <div class="form-group">
        <label for="sp_ten">Tên sản phẩm</label>
        <input type="text" class="form-control" id="sp_ten" name="sp_ten" value="{{ old('sp_ten', $sp->sp_ten) }}">
    </div>
    <div class="form-group">
        <label for="sp_giaGoc">Giá gốc</label>
        <input type="text" class="form-control" id="sp_giaGoc" name="sp_giaGoc" value="{{ old('sp_giaGoc', $sp->sp_giaGoc) }}">
    </div>
    <div class="form-group">
        <label for="sp_giaGoc">Giá bán</label>
        <input type="text" class="form-control" id="sp_giaBan" name="sp_giaBan" value="{{ old('sp_giaBan', $sp->sp_giaBan) }}">
    </div>
    <div class="form-group">
        <div class="file-loading">
            <label>Hình đại diện</label>
            <input id="sp_hinh" type="file" name="sp_hinh">
        </div>
    </div>
    <div class="form-group">
        <label for="sp_thongTin">Thông tin</label>
        <input type="text" class="form-control" id="sp_thongTin" name="sp_thongTin" value="{{ old('sp_thongTin', $sp->sp_thongTin) }}">
    </div>
    <div class="form-group">
        <label for="sp_danhGia">Đánh giá</label>
        <input type="text" class="form-control" id="sp_danhGia" name="sp_danhGia" value="{{ old('sp_danhGia', $sp->sp_danhGia) }}">
    </div>
    <div class="form-group">
        <label for="sp_taoMoi">Ngày tạo mới</label>
        <input type="text" class="form-control" id="sp_taoMoi" name="sp_taoMoi" value="{{ old('sp_taoMoi', $sp->sp_taoMoi) }}" data-mask-datetime>
    </div>
    <div class="form-group">
        <label for="sp_capNhat">Ngày cập nhật</label>
        <input type="text" class="form-control" id="sp_capNhat" name="sp_capNhat" value="{{ old('sp_capNhat', $sp->sp_capNhat) }}" data-mask-datetime>
    </div>
    <div class="form-group">
      <label for="sp_trangThai">Trạng thái</label>
      <select name="sp_trangThai" class="form-control">
          <option value="1" {{ old('sp_trangThai', $sp->sp_trangThai) == 1 ? "selected" : "" }}>Khóa</option>
          <option value="2" {{ old('sp_trangThai', $sp->sp_trangThai) == 2 ? "selected" : "" }}>Khả dụng</option>
      </select>
    </div>
    <div class="form-group">
      <div class="file-loading">
          <label>Hình ảnh liên quan sản phẩm</label>
          <input id="sp_hinhanhlienquan" type="file" name="sp_hinhanhlienquan[]" multiple>
      </div>
    </div>
    <button type="submit" class="btn btn-primary">Lưu</button>
</form>
@endsection

@section('custom-scripts')
<!-- Các script dành cho thư viện bootstrap-fileinput -->
<script src="{{ asset('vendor/bootstrap-fileinput/js/plugins/sortable.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/js/fileinput.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/js/locales/fr.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/themes/fas/theme.js') }}" type="text/javascript"></script>
<script src="{{ asset('vendor/bootstrap-fileinput/themes/explorer-fas/theme.js') }}" type="text/javascript"></script>

<script>
    $(document).ready(function() {
        $("#sp_hinh").fileinput({
            theme: 'fas',
            showUpload: false,
            showCaption: false,
            browseClass: "btn btn-primary btn-lg",
            fileType: "any",
            append: false,
            showRemove: false,
            autoReplace: true,
            previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
            overwriteInitial: false,
            initialPreviewShowDelete: false,
            initialPreviewAsData: true,
            initialPreview: [
                "{{ asset('storage/photos/' . $sp->sp_hinh) }}"
            ],
            initialPreviewConfig: [
                {
                    caption: "{{ $sp->sp_hinh }}", 
                    size: {{ Storage::exists('public/photos/' . $sp->sp_hinh) ? Storage::size('public/photos/' . $sp->sp_hinh) : 0 }}, 
                    width: "120px", 
                    url: "{$url}", 
                    key: 1
                },
            ]
        });

      // Ô nhập liệu cho phép chọn nhiều hình ảnh cùng lúc (các hình ảnh liên quan đến sản phẩm)
      $("#sp_hinhanhlienquan").fileinput({
            theme: 'fas',
            showUpload: false,
            showCaption: false,
            browseClass: "btn btn-primary btn-lg",
            fileType: "any",
            append: false,
            showRemove: false,
            autoReplace: true,
            previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
            overwriteInitial: false,
            allowedFileExtensions: ["jpg", "gif", "png", "txt"],
            initialPreviewShowDelete: false,
            initialPreviewAsData: true,
            initialPreview: [
                @foreach($sp->hinhanhlienquan()->get() as $hinhAnh)
                "{{ asset('storage/photos/' . $hinhAnh->ha_ten) }}",
                @endforeach
            ],
            initialPreviewConfig: [
                @foreach($sp->hinhanhlienquan()->get() as $index=>$hinhAnh)
                {
                    caption: "{{ $hinhAnh->ha_ten }}", 
                    size: {{ Storage::exists('public/photos/' . $hinhAnh->ha_ten) ? Storage::size('public/photos/' . $hinhAnh->ha_ten) : 0 }}, 
                    width: "120px", 
                    url: "{$url}", 
                    key: {{ ($index + 1) }}
                },
                @endforeach
            ]
        });
    });
</script>

<!-- Các script dành cho thư viện Mặt nạ nhập liệu InputMask -->
<script src="{{ asset('vendor/input-mask/jquery.inputmask.min.js') }}"></script>
<script src="{{ asset('vendor/input-mask/bindings/inputmask.binding.js') }}"></script>

<script>
  $(document).ready(function() {
    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Giá gốc
    $('#sp_giaGoc').inputmask({
      alias: 'currency',
      positionCaretOnClick: "radixFocus",
      radixPoint: ".",
      _radixDance: true,
      numericInput: true,
      groupSeparator: ",",
      suffix: ' vnđ',
      min: 0,         // 0 ngàn
      max: 100000000, // 1 trăm triệu
      autoUnmask: true,
      removeMaskOnSubmit: true,
      unmaskAsNumber: true,
      inputtype: 'text',
      placeholder: "0",
      definitions: {
        "0": {
          validator: "[0-9\uFF11-\uFF19]"
        }
      }
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Giá bán
    $('#sp_giaBan').inputmask({
      alias: 'currency',
      positionCaretOnClick: "radixFocus",
      radixPoint: ".",
      _radixDance: true,
      numericInput: true,
      groupSeparator: ",",
      suffix: ' vnđ',
      min: 0,         // 0 ngàn
      max: 100000000, // 1 trăm triệu
      autoUnmask: true,
      removeMaskOnSubmit: true,
      unmaskAsNumber: true,
      inputtype: 'text',
      placeholder: "0",
      definitions: {
        "0": {
          validator: "[0-9\uFF11-\uFF19]"
        }
      }
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Ngày tạo mới
    $('#sp_taoMoi').inputmask({
      alias: 'datetime',
      inputFormat: 'yyyy-mm-dd' // Định dạng Năm-Tháng-Ngày
    });

    // Gắn mặt nạ nhập liệu cho các ô nhập liệu Ngày cập nhật
    $('#sp_capNhat').inputmask({
      alias: 'datetime',
      inputFormat: 'yyyy-mm-dd' // Định dạng Năm-Tháng-Ngày
    });
  });
</script>

@endsection

Step 6: hiệu chỉnh action update của Backend\SanPhamController

Bổ sung chức năng Lưu trữ cùng lúc nhiều hình ảnh
/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function update(Request $request, $id)
{
    // Bổ sung ràng buộc Validate
    $validation = $request->validate([
        'sp_hinh' => 'file|image|mimes:jpeg,png,gif,webp|max:2048',

        // Cú pháp dùng upload nhiều file
        'sp_hinhanhlienquan.*' => 'image|mimes:jpeg,png,gif,webp|max:2048'
    ]);

    // Tìm object Sản phẩm theo khóa chính
    $sp = SanPham::where("sp_ma",  $id)->first();
    $sp->sp_ten = $request->sp_ten;
    $sp->sp_giaGoc = $request->sp_giaGoc;
    $sp->sp_giaBan = $request->sp_giaBan;
    $sp->sp_thongTin = $request->sp_thongTin;
    $sp->sp_danhGia = $request->sp_danhGia;
    $sp->sp_taoMoi = $request->sp_taoMoi;
    $sp->sp_capNhat = $request->sp_capNhat;
    $sp->sp_trangThai = $request->sp_trangThai;
    $sp->l_ma = $request->l_ma;

    // Kiểm tra xem người dùng có upload hình ảnh Đại diện hay không?
    if ($request->hasFile('sp_hinh')) {
        // Xóa hình cũ để tránh rác
        Storage::delete('public/photos/' . $sp->sp_hinh);

        // Upload hình mới
        // Lưu tên hình vào column sp_hinh
        $file = $request->sp_hinh;
        $sp->sp_hinh = $file->getClientOriginalName();

        // Chép file vào thư mục "photos"
        $fileSaved = $file->storeAs('public/photos', $sp->sp_hinh);
    }

    // Lưu hình ảnh liên quan
    if ($request->hasFile('sp_hinhanhlienquan')) {
        // DELETE các dòng liên quan trong table `HinhAnh`
        foreach ($sp->hinhanhlienquan()->get() as $hinhAnh) {
            // Xóa hình cũ để tránh rác
            Storage::delete('public/photos/' . $hinhAnh->ha_ten);

            // Xóa record
            $hinhAnh->delete();
        }

        $files = $request->sp_hinhanhlienquan;

        // duyệt từng ảnh và thực hiện lưu
        foreach ($request->sp_hinhanhlienquan as $index => $file) {

            $file->storeAs('public/photos', $file->getClientOriginalName());

            // Tạo đối tưọng HinhAnh
            $hinhAnh = new HinhAnh();
            $hinhAnh->sp_ma = $sp->sp_ma;
            $hinhAnh->ha_stt = ($index + 1);
            $hinhAnh->ha_ten = $file->getClientOriginalName();
            $hinhAnh->save();
        }
    }
    $sp->save();

    // Hiển thị câu thông báo 1 lần (Flash session)
    Session::flash('alert-info', 'Cập nhật thành công ^^~!!!');

    // Điều hướng về trang index
    return redirect()->route('admin.sanpham.index');
}

Step 7: hiệu chỉnh action despoy của Backend\SanPhamController

Bổ sung chức năng Xóa cùng lúc nhiều hình ảnh
/**
 * Remove the specified resource from storage.
 *
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function destroy($id)
{
    // Tìm object Sản phẩm theo khóa chính
    $sp = SanPham::where("sp_ma",  $id)->first();

    // Nếu tìm thấy được sản phẩm thì tiến hành thao tác DELETE
    if (empty($sp) == false) {

        // DELETE các dòng liên quan trong table `HinhAnh` 
        foreach ($sp->hinhanhlienquan()->get() as $hinhAnh) {
            // Xóa hình cũ để tránh rác 
            Storage::delete('public/photos/' . $hinhAnh->ha_ten);

            // Xóa record 
            $hinhAnh->delete();
        }

        // Xóa hình cũ để tránh rác
        Storage::delete('public/photos/' . $sp->sp_hinh);
    }
    $sp->delete();

    // Hiển thị câu thông báo 1 lần (Flash session)
    Session::flash('alert-info', 'Xóa sản phẩm thành công ^^~!!!');

    // Điều hướng về trang index
    return redirect()->route('admin.sanpham.index');
}

Chương trình học


  1. Bức tranh Tổng thể về Lập trình WEB
  2. Giới thiệu, cài đặt, cấu hình môi trường lập trình 9
    1. Cài đặt web server XAMPP #84
    2. Cài đặt công cụ truy vấn database HeidiSQL #85
    3. Cài đặt Composer để quản lý các gói thư viện trong PHP #86
    4. Cài đặt trình soạn thảo code Visual Studio Code IDE #64
    5. Tạo tên miền ảo trên máy cục bộ (virtual host on localhost) bằng XAMPP #107
    6. Cài đặt nền tảng quản lý Source Code sử dụng GIT #1030
    7. Cài đặt công cụ quản lý Source Code TortoiseGit #1045
    8. Cài đặt chế độ Debug PHP với Visual Studio Code #8066
    9. Tạo chứng chỉ SSL trên Localhost #10399
  3. Tập làm quen với quản lý source code bằng GitHub 3
    1. GitHub là gì? #79
    2. Tạo tài khoản, tạo kho dữ liệu (repository), clone source, commit/push và pull source với GitHub #2303
    3. Cách đóng gói phiên bản (Alpha, Beta, Release) bằng Tag trong GitHub #9415
  4. Cài đặt framework Laravel 1
    1. Cài đặt Framework Laravel #66
  5. Tìm hiểu mô hình kiến trúc MVC vận hành trong framework Laravel 4
    1. Cấu trúc thư mục trong Framework Laravel #9211
    2. Kiến trúc MVC là gì? #65
    3. Mô hình kiến trúc MVC vận hành trong Framework Laravel #67
    4. Thực hiện code theo mô hình kiến trúc MVC vận hành trong Framework Laravel #2341
  6. Sử dụng Template Engine để trình diễn nội dung trong các VIEW 3
    1. Template Engine là gì? #2356
    2. Blade Template Engine trong Laravel #2357
    3. Bài tập View - tạo trang Danh sách nhân viên sử dụng Blade Template #2381
  7. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế CSDL 7
    1. Phân tích các yêu cầu, nghiệp vụ của khách hàng #68
    2. Mô hình thiết kế CSDL mẫu Sunshine #69
    3. Khởi tạo database sunshine, thiết lập kết nối CSDL trong Laravel #1143
    4. Tạo cấu trúc table bằng tính năng MIGRATION trong Laravel - Danh mục phẳng #70
    5. Tạo cấu trúc table bằng tính năng MIGRATION trong Laravel - Danh mục có liên kết khóa ngoại #1157
    6. Tạo dữ liệu ban đầu cho CSDL bằng tính năng SEED trong Laravel - Danh mục phẳng #71
    7. Tạo dữ liệu ban đầu cho CSDL bằng tính năng SEED trong Laravel - Danh mục có liên kết khóa ngoại #7851
  8. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Ánh xạ CSDL và Laravel 3
    1. Tạo lớp (class) ánh xạ CSDL bằng tính năng MODEL trong Laravel - Danh mục Phẳng - Loại sản phẩm #1165
    2. Tạo lớp (class) ánh xạ CSDL bằng tính năng MODEL trong Laravel - Danh mục phẳng - Sản phẩm #1166
    3. Tạo mối quan hệ giữa các lớp (class) MODEL trong Laravel #1174
  9. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế Backend 20
    1. Thiết kế bố cục (layouts) cho giao diện Backend #72
    2. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (index) #135
    3. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (create) #137
    4. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (edit) #138
    5. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (delete) #139
    6. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Index #75
    7. Lưu đồ Upload file từ Client lên Server #2229
    8. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Create #108
    9. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Edit #87
    10. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Delete #88
    11. Xây dựng danh mục Sản phẩm có chức năng upload nhiều Hình ảnh cùng lúc #9259
    12. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Bổ sung menu vào sidebar #104
    13. Xây dựng chức năng xuất biểu mẫu và In ấn trực tiếp trên web #78
    14. Xây dựng chức năng xuất Excel #76
    15. Xây dựng chức năng xuất PDF #77
    16. Tạo chức năng Đăng nhập #1870
    17. Lưu đồ Kiểm tra ràng buộc dữ liệu (Validation) #2205
    18. Kiểm tra ràng buộc dữ liệu (Validation) phía Client #2206
    19. Kiểm tra ràng buộc dữ liệu (Validation) phía Server #2207
    20. Bài tập tổng hợp - Tạo các chức năng Backend cho trang web đọc Truyện Tranh và Tiểu Thuyết Online sử dụng Laravel framework #9312
  10. AngularJS 7
    1. AngularJS là gì? Cài đặt AngularJS và cú pháp sử dụng AngularJS #95
    2. Cách mô hình kiến trúc MVC vận hành trong AngularJS #96
    3. Cách sử dụng AngularJS Controller, Scope #98
    4. Cách sử dụng AngularJS Directive #97
    5. Cách sử dụng AngularJS Event #100
    6. Cách sử dụng AngularJS Filter #99
    7. Cách sử dụng AngularJS Validation #101
  11. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế Frontend sử dụng AngularJS 11
    1. Thiết kế bố cục (layouts) cho giao diện Frontend #80
    2. Tích hợp AngularJS vào framework Laravel #102
    3. Tạo giao diện trang chủ (index) #81
    4. Tạo giao diện trang Giới thiệu (about) #109
    5. Tạo giao diện trang Liên hệ (contact) #114
    6. Tạo trang danh sách Sản phẩm (product) #110
    7. Tạo nút Thêm vào giỏ hàng cho từng sản phẩm (add-to-cart) #112
    8. Tạo trang Chi tiết Sản phẩm (product-detail) #111
    9. Tạo giỏ hàng (cart sidebar) #82
    10. Tạo trang thanh toán (checkout) #83
    11. Tạo đơn hàng và gởi mail xác nhận #103
  12. Bonus 9
    1. Đa ngôn ngữ trong Laravel #115
    2. Tạo báo cáo với biểu đồ ChartJS #116
    3. Tạo khung chọn Ngày tháng cho dự án #2431
    4. Tạo các trang thông báo lỗi tương ứng STATUS CODE #117
    5. Kiểm tra ứng dụng với tunnel ảo NGROK #2418
    6. Phân trang trong Laravel #2436
    7. Bổ sung khung xem Hình ảnh trước khi upload (preview image upload) #2439
    8. Tạo API trong Laravel và sử dụng AngularJS để hiển thị giao diện #9385
    9. Cách tự động sinh Ảnh nhiều kích cỡ (Automatic resize image) khi upload file Ảnh #9970
  13. Danh sách Đồ án xây dựng trang web sử dụng LARAVEL 2
    1. Đăng ký Đồ án Laravel #1188
    2. Hướng dẫn Nộp Đồ án Laravel #9417
  14. Tài liệu tham khảo 3
    1. Kho sách, nguồn tài liệu tham khảo #1313
    2. SourceCode Dự án mẫu #113
    3. Xây dựng chức năng Tìm kiếm theo nhiều tiêu chí bằng Model Eloquent #9426
  15. Thực hiện Đồ án 1
    1. Lộ trình Thực hiện đồ án Web Laravel #7883
Các bài học

Chương trình học

Bao gồm Module, Chương, Bài học, Bài tập, Kiểm tra...

Chương trình học


  1. Bức tranh Tổng thể về Lập trình WEB
  2. Giới thiệu, cài đặt, cấu hình môi trường lập trình 9
    1. Cài đặt web server XAMPP #84
    2. Cài đặt công cụ truy vấn database HeidiSQL #85
    3. Cài đặt Composer để quản lý các gói thư viện trong PHP #86
    4. Cài đặt trình soạn thảo code Visual Studio Code IDE #64
    5. Tạo tên miền ảo trên máy cục bộ (virtual host on localhost) bằng XAMPP #107
    6. Cài đặt nền tảng quản lý Source Code sử dụng GIT #1030
    7. Cài đặt công cụ quản lý Source Code TortoiseGit #1045
    8. Cài đặt chế độ Debug PHP với Visual Studio Code #8066
    9. Tạo chứng chỉ SSL trên Localhost #10399
  3. Tập làm quen với quản lý source code bằng GitHub 3
    1. GitHub là gì? #79
    2. Tạo tài khoản, tạo kho dữ liệu (repository), clone source, commit/push và pull source với GitHub #2303
    3. Cách đóng gói phiên bản (Alpha, Beta, Release) bằng Tag trong GitHub #9415
  4. Cài đặt framework Laravel 1
    1. Cài đặt Framework Laravel #66
  5. Tìm hiểu mô hình kiến trúc MVC vận hành trong framework Laravel 4
    1. Cấu trúc thư mục trong Framework Laravel #9211
    2. Kiến trúc MVC là gì? #65
    3. Mô hình kiến trúc MVC vận hành trong Framework Laravel #67
    4. Thực hiện code theo mô hình kiến trúc MVC vận hành trong Framework Laravel #2341
  6. Sử dụng Template Engine để trình diễn nội dung trong các VIEW 3
    1. Template Engine là gì? #2356
    2. Blade Template Engine trong Laravel #2357
    3. Bài tập View - tạo trang Danh sách nhân viên sử dụng Blade Template #2381
  7. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế CSDL 7
    1. Phân tích các yêu cầu, nghiệp vụ của khách hàng #68
    2. Mô hình thiết kế CSDL mẫu Sunshine #69
    3. Khởi tạo database sunshine, thiết lập kết nối CSDL trong Laravel #1143
    4. Tạo cấu trúc table bằng tính năng MIGRATION trong Laravel - Danh mục phẳng #70
    5. Tạo cấu trúc table bằng tính năng MIGRATION trong Laravel - Danh mục có liên kết khóa ngoại #1157
    6. Tạo dữ liệu ban đầu cho CSDL bằng tính năng SEED trong Laravel - Danh mục phẳng #71
    7. Tạo dữ liệu ban đầu cho CSDL bằng tính năng SEED trong Laravel - Danh mục có liên kết khóa ngoại #7851
  8. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Ánh xạ CSDL và Laravel 3
    1. Tạo lớp (class) ánh xạ CSDL bằng tính năng MODEL trong Laravel - Danh mục Phẳng - Loại sản phẩm #1165
    2. Tạo lớp (class) ánh xạ CSDL bằng tính năng MODEL trong Laravel - Danh mục phẳng - Sản phẩm #1166
    3. Tạo mối quan hệ giữa các lớp (class) MODEL trong Laravel #1174
  9. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế Backend 20
    1. Thiết kế bố cục (layouts) cho giao diện Backend #72
    2. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (index) #135
    3. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (create) #137
    4. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (edit) #138
    5. Xây dựng chức năng CRUD (Thêm, Sửa, Xóa, Xem) danh mục phẳng - Loại sản phẩm (delete) #139
    6. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Index #75
    7. Lưu đồ Upload file từ Client lên Server #2229
    8. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Create #108
    9. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Edit #87
    10. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Delete #88
    11. Xây dựng danh mục Sản phẩm có chức năng upload nhiều Hình ảnh cùng lúc #9259
    12. Xây dựng danh mục Sản phẩm có chức năng upload hình ảnh - Bổ sung menu vào sidebar #104
    13. Xây dựng chức năng xuất biểu mẫu và In ấn trực tiếp trên web #78
    14. Xây dựng chức năng xuất Excel #76
    15. Xây dựng chức năng xuất PDF #77
    16. Tạo chức năng Đăng nhập #1870
    17. Lưu đồ Kiểm tra ràng buộc dữ liệu (Validation) #2205
    18. Kiểm tra ràng buộc dữ liệu (Validation) phía Client #2206
    19. Kiểm tra ràng buộc dữ liệu (Validation) phía Server #2207
    20. Bài tập tổng hợp - Tạo các chức năng Backend cho trang web đọc Truyện Tranh và Tiểu Thuyết Online sử dụng Laravel framework #9312
  10. AngularJS 7
    1. AngularJS là gì? Cài đặt AngularJS và cú pháp sử dụng AngularJS #95
    2. Cách mô hình kiến trúc MVC vận hành trong AngularJS #96
    3. Cách sử dụng AngularJS Controller, Scope #98
    4. Cách sử dụng AngularJS Directive #97
    5. Cách sử dụng AngularJS Event #100
    6. Cách sử dụng AngularJS Filter #99
    7. Cách sử dụng AngularJS Validation #101
  11. Dự án thực tế mẫu - Trang web bán hàng trực tuyến - Thiết kế Frontend sử dụng AngularJS 11
    1. Thiết kế bố cục (layouts) cho giao diện Frontend #80
    2. Tích hợp AngularJS vào framework Laravel #102
    3. Tạo giao diện trang chủ (index) #81
    4. Tạo giao diện trang Giới thiệu (about) #109
    5. Tạo giao diện trang Liên hệ (contact) #114
    6. Tạo trang danh sách Sản phẩm (product) #110
    7. Tạo nút Thêm vào giỏ hàng cho từng sản phẩm (add-to-cart) #112
    8. Tạo trang Chi tiết Sản phẩm (product-detail) #111
    9. Tạo giỏ hàng (cart sidebar) #82
    10. Tạo trang thanh toán (checkout) #83
    11. Tạo đơn hàng và gởi mail xác nhận #103
  12. Bonus 9
    1. Đa ngôn ngữ trong Laravel #115
    2. Tạo báo cáo với biểu đồ ChartJS #116
    3. Tạo khung chọn Ngày tháng cho dự án #2431
    4. Tạo các trang thông báo lỗi tương ứng STATUS CODE #117
    5. Kiểm tra ứng dụng với tunnel ảo NGROK #2418
    6. Phân trang trong Laravel #2436
    7. Bổ sung khung xem Hình ảnh trước khi upload (preview image upload) #2439
    8. Tạo API trong Laravel và sử dụng AngularJS để hiển thị giao diện #9385
    9. Cách tự động sinh Ảnh nhiều kích cỡ (Automatic resize image) khi upload file Ảnh #9970
  13. Danh sách Đồ án xây dựng trang web sử dụng LARAVEL 2
    1. Đăng ký Đồ án Laravel #1188
    2. Hướng dẫn Nộp Đồ án Laravel #9417
  14. Tài liệu tham khảo 3
    1. Kho sách, nguồn tài liệu tham khảo #1313
    2. SourceCode Dự án mẫu #113
    3. Xây dựng chức năng Tìm kiếm theo nhiều tiêu chí bằng Model Eloquent #9426
  15. Thực hiện Đồ án 1
    1. Lộ trình Thực hiện đồ án Web Laravel #7883

Bài học trước Bài học tiếp theo