0

[CakePHP] Tìm hiểu Uploader plugin (P2)

Như ở phần 1 chúng ta đã cùng tìm hiểu lý thuyết về Uploader - một plugin hữu ích trong việc Upload files dùng cho CakePHP. Nên trong bài này hãy cùng làm một demo nho nhỏ sử dụng những kiến thức đã có, chúng ta sẽ xây dựng một demo có các chức năng đơn gỉan sau :

  • List ra các file đã upload
  • Upload với lựa chọn resize, rotate ảnh
  • Xóa ảnh đã upload Đầu tiên là phải đảm bảo đã download thành công Uploader, transit ... và setting để Uploader được load tự động. Trong file app/Config/bootstrap.php CakePlugin::loadAll(); Sau đó, chúng ta sẽ đi theo thứ tự từng bước để có một demo hoàn chỉnh : tạo DB, dựng layout, setting các thông số cho Uploader, đưa vào các logic ...

1) Tạo DB cho demo

Vì chỉ là demo đơn gỉan để đi vào thực tế xem Uploader hoạt động thế nào nên DB có duy nhất một bảng với số trường cũng tương đối ít ( các kiểu cũng như độ dài trường tôi chỉ đặt để chạy được demo chứ không quan tâm nhiều) :

    CREATE TABLE `pwi`.`images` (
        `id` INT NOT NULL AUTO_INCREMENT,
        `file_name` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci' NULL,
        `file_path` VARCHAR(45) NULL,
        `file_size` VARCHAR(45) NULL,
        `file_thumb` VARCHAR(45) NULL,
        `created` DATETIME NULL,
        `modified` DATETIME NULL,
        PRIMARY KEY (`id`),
        UNIQUE INDEX `id_UNIQUE` (`id` ASC),
        UNIQUE INDEX `file_name_UNIQUE` (`file_name` ASC));

2) Dựng layout

Sẽ chỉ có 2 views trong demo của chúng ta, bao gồm index.ctp (màn hình thực hiện list up các ảnh đã upload và link tới form upload) và upload.ctp (màn hình thực hiện việc upload) trong thư mục app/Views/Images. Do đậy là phần riêng của CakePHP nên tôi sẽ lược bỏ code, chi tiết hãy tham khảo file tương ứng. Nhưng ý tưởng sẽ là :

2-1) index.ctp

Layout là một bảng gồm các cột thể hiện được dữ liệu có trong bảng "images" như ID, Thumbail, File name, File path, File size, Created on và Last modified. Ngoài ra, thêm một cột để chứa link đến các màn hình khác như Edit hay Delete. Logic của view này rất đơn gỉan, dùng foreach() để list ra các bản ghi có trong DB. Chỉ chú ý một chỗ là phần Thumbail của ảnh, do khi lưu vào DB thì Uploader sẽ lưu cả path nên cần có xử lý lấy ra tên ảnh có kèm đuôi mở rộng.

2-2) upload.ctp

Layout cho phần này gồm 2 phần chính là 1) vùng chọn file ảnh để upload cùng nút Upload và 2) các setting có thể đối với ảnh sẽ upload, đó là lựa chọn có rotate ảnh hay không, nếu có thì rotate bao nhiêu độ hay có resize ảnh không, nếu có thì chọn kích thước là bao nhiêu ( tôi sẽ hạn chế không cho resize to hơn kích cỡ ảnh gốc bằng 'expand' => false). Rotate bao gồm các góc độ 90, 180 và 270, bạn chú ý là sẽ quay ảnh ngược chiều kim đồng hồ. Resize thì sẽ để 2 lựa chọn là 1280x720 và 800x600, bạn chú ý là nếu ratio của ảnh gốc không trùng ratio với 2 lựa chọn này thì bức ảnh sẽ bị resize không được như mong muốn. Trong layout này có lưu ý phần khai báo Form, bạn cần set như bên dưới để việc upload được thực hiện đúng, bạn có thể xem dòng 5 trong upload.ctp :

    echo $this->Form->create('Image', array('type' => 'file' )); ?>

3) Thiết lập các thông số của Uploader

Như các bạn đã biết, việc này sẽ được thực hiện trong Model với thuộc tính $actsAs nên cần phải tạo file app/Model/Images/Image.php. Nhưng trước khi sử dụng được Uploader bạn cần thiết lập để báo cho CakePHP biết bạn sẽ sử dụng 2 behavior, ngay đầu file, trên phần định nghĩa class :

    App::uses('AttachmentBehavior', 'Uploader.Model/Behavior');
    	App::uses('FileValidationBehavior', 'Uploader.Model/Behavior');

Tiếp theo, trước khi upload cần thiết lập để validate ảnh xem đã chọn ảnh chưa, file up lên có phải ảnh hay không, kích cỡ có hợp lệ không ... chi tiết bạn có thể xem source hoặc đoạn trích bên dưới :

    public $actsAs = array(
    	Uploader.FileValidation' => array(
    		file_path' => array(
    			type' => array(
    				value' => 'image',
    				error' => 'Bạn chỉ được upload file ảnh.',
    			),
    			extension' => array(
    				value' => array('jpg', 'png', 'jpeg'),
    				error' => 'Bạn chỉ được upload ảnh có định dạng sau : jpg, png, jpeg.',
    			),
    			filesize' => array(
    				value' => 1048576,
    				error' => 'Bạn chỉ được upload ảnh có dung lượng dưới 1MB.',
    			),
    			maxWidth' => array(
    				value' => 1920,
    				error' => 'Ảnh của bạn không được có width rộng qúa 1920px.',
    			),
    			maxHeight' => array(
    				value' => 1280,
    				error' => 'Ảnh của bạn không được có height cao qúa 1280px.',
    			),
    			required' => array(
    				value' => true,
    				error' => 'Bạn chưa chọn ảnh để upload.',
    				)
    		)
    	)
    );

Sau khi ảnh đã validate thì cần chỉ ra những thiết lập để lưu ảnh được thành công như thư mục chứa ảnh, trường lưu đường dẫn của ảnh trong DB, ghi đè những ảnh đã tồn tại ( nếu bạn để fasle thì ảnh upload lần trước vẫn được lưu trữ, ảnh mới sẽ được đếm số tăng dần), nếu có lỗi sẽ dừng việc upload, ảnh mặc định sẽ được crop để làm thumbail, lưu vào images.file_thumb ... cụ thể :

    'Uploader.Attachment' => array(
        'file_path' => array(
        'uploadDir' => UPLOAD_DIR,
        'finalPath' => UPLOAD_DIR,
        'dbColumn'  => 'file_path',
        'overwrite' => true,
        'stopSave'  => true,
        'allowEmpty'    => false,
        'transforms' => array(
         	array('method' => 'crop',
        		'overwrite' => true,
                	'width' => 100,
               	'height' => 100,
                	'quality' => 100,
                	'location' => 'center',
                	'dbColumn' => 'file_thumb'
                ),
           )
       )
    )

Ok, vậy là đã thiết lập xong nhưng do demo của chúng ta cho phép người dùng thêm lựa chọn có quay ảnh không, có thay đổi kích cỡ của ảnh hay không nên trong Model ta cần gọi đến hàm beforeUpload() để thực hiện việc này trước khi ảnh được đưa lên. Bạn có thể tham khảo chi tiết trong source nhưng logic cơ bản là :

Trước khi upload ảnh sẽ : Thay đổi thiết lập mặc định $actsAs bên trên tùy thuộc vào người dùng chọn lựa chọn nào
+ Chỉ chọn resize ảnh
+ Chỉ chọn rotate ảnh
+ Chọn cả 2

4) Xây dựng chức năng

4-1) List up ảnh

Tất cả các chức năng demo sẽ được tạo trong file app/Controller/ImagesController.php. Đầu tiên, ta đã xong view nên việc còn lại chỉ là tạo phương thức index() cho nó :

    public function index() {
    	$this->set('title_for_layout', 'Images list');
    	$this->set('images', $this->paginate());
    }

4-2) Upload ảnh

Đây là phương thức chính của demo, logic của nó rất đơn gỉan bởi vì không có những case check dữ liệu phức tạp như trong thực tế, mục đích là hiểu được cách upload với Uploader plugin nên tôi không đặt nặng vấn đề validate hay check dữ liệu qúa nhiều. Bạn có thể xem code và comment trong đó để hiểu logic khi upload :

    public function upload() {
    	// Check user đã submit chưa
    	if($this->request->is('post')) {
    		// nếu có submit và dữ liệu hợp lệ sẽ tiến hành lưu thông tin
    		$this->request->data['Image']['file_name'] =
                         $this->getFileName($this->request->data);
    		$this->request->data['Image']['file_size'] =
                         $this->getFileSize($this->request->data);

    		$this->setResizeOptions($this->request->data);
    		$this->setRotateDegrees($this->request->data);

    		$this->Image->create();
    		// nếu lưu thành công sẽ quay về list
    		if($this->Image->save($this->request->data)) {
    			$this->Session->setFlash(__('Đã upload thành công.'));
    				return $this->redirect(array('action' => 'index'));
    		}

    		// nếu có lỗi sẽ show ra màn hình
    		$errs = $this->Image->validationErrors;
    			foreach ($errs as $err => $contents) {
    				foreach ($contents as $key => $value) {
    					echo $this->Session->setFlash(__($value . '</br>'));
    				}
    			}
    		}
    	}

Như các bạn thấy, tôi có sử dụng một vài phương thức trong upload(), để đỡ rối trong code nên tôi tách chúng thành các phương thức riêng như sau :

    // phương thức này sẽ lấy ra tên của file gốc được upload
    protected function getFileName($data) {
    	$tmpArr = explode('.', $data['Image']['file_path']['name']);
    	return $fileName = $tmpArr[0];
    }
    // phương thức này hỗ trợ việc lấy và tính toán kích cỡ của file thành MB
    protected function getFileSize($data) {
    	$fileSizeInByte = $data['Image']['file_path']['size'];
    	return $fileSzieInMB = round($fileSizeInByte / 1000000, 2);
    }
    // phương thức này sẽ kiểm tra xem người dùng có lựa chọn resize ảnh không
    protected function setResizeOptions($data) {
    	if(isset($data['Image']['resize'])) {
                    // nếu có thì thay đổi gía trị trong Model để hàm
                    // beforeUpload() thực hiện việc của mình
    		$this->Image->resizeFlag = true;
    		if(isset($data['Image']['resizeOptions'])
                        && $data['Image']['resizeOptions'] == '2') {
                            // do mặc định trong Model gía trị là 1280x720 nên
                            // ta chỉ cần set gía trị còn lại nếu nó được chọn
     			$this->Image->width = 800;
    			$this->Image->height = 600;
    		}
    	}
    }
    // phương thức này sẽ kiểm tra xem người dùng có lựa chọn rotate ảnh không
    protected function setRotateDegrees($data) {
    	if(isset($data['Image']['rotate'])) {
                    // nếu có thì thay đổi gía trị trong Model để hàm
                    // beforeUpload() thực hiện việc của mình
    		$this->Image->rotateFlag = true;

                    /* do mặc định trong Model gía trị là 90 độ nên
                      ta chỉ cần set gía trị còn lại nếu nó được chọn
                      quay 180 hoặc 270 độ
                    */
    		if(isset($data['Image']['rotateOptions'])
                        && $data['Image']['rotateOptions'] == '2') {
    			$this->Image->degrees = 180;
    		}
    		if(isset($data['Image']['rotateOptions'])
                        && $data['Image']['rotateOptions'] == '3') {
    		$this->Image->degrees = 270;
    		}
    	}
    }

4-3) Xóa ảnh

Với Uploader thì khi bản ghi chứa đường dẫn ảnh bị xóa thì tự động file ảnh đã upload cũng sẽ bị xóa theo nên bạn không cần quan tâm tới các ảnh rác sau khi đã không còn dùng đến chúng nữa. Bạn thực hiện xóa bản ghi như bình thường, dùng phương thức delete() của CakePHP.

    public function delete($id = null) {
            // chỉ thực hiện khi request là post
    	$this->request->onlyAllow('post');
    	$this->Image->id = $id;
    	// ảnh phải còn tồn tại mới xóa được
    	if(!$this->Image->exists()) {
    		throw new NotFoundException(__('Image ID không tồn tại.'));
    	}
            // xóa ảnh và refresh list ảnh
    	if($this->Image->delete()) {
    		$this->Session->setFlash(__('Đã xóa ảnh thành công.'));
    			return $this->redirect(array('action' => 'index'));
    	}
            // báo lỗi nếu xảy ra
    	$this->Session->setFlash(__('Không thể xóa ảnh, hãy thử lại lúc khác.'));
    		return $this->redirect(array('action' => 'index'));
    }

Vậy là xong, qua vài bước chúng ta đã có một demo cho việc sử dụng Uploader cùng với CakePHP. Bạn có thể check lại kết qủa mình sau khi hoàn thành hết các bước ở trên. Nhưng có một lưu ý nhỏ là bạn nên tắt chế độ Debug của CakePHP bởi vì có thể có những notice hay warning show ra. Đến đây, ít nhiều các bạn cũng đã biết cách ứng dụng lý thuyết vào thực, Uploader còn rất nhiều những setting có thể thiết lập và chúng ta sẽ tiếp tục tìm hiểu ở phần sau.

Link source trên github, riêng source của CakePHP có rất nhiều file nên các bạn hãy download về rồi add những file demo của tôi vào đường dẫn tương ứng, và nhớ là cần cài đặt Uploader nữa.

To be continued ... (phần 3)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí