Wednesday, September 16, 2009

CakePHP : Membina sistem "attachments" yang mudah





Sebelum mengikuti tutorial ini, pastikan anda telah mempunyai asas CakePHP. Jika belum, anda boleh download  PDF untuk memulakan sistem Ebook berasaskan CakePHP di sini

Senario
Anda perlu membina satu sistem untuk mengurus fail-fail seperti Microsoft Documents, PDF, gambar-gambar dan juga fail berbentuk zip.

Penyelesaian
Kita akan gunakan CakePHP versi 1.2 stable
Database storan akan menggunakan MySQL
1 table akan digunakan iaitu attachments
Kita akan simpan fail yang diupload ke dalam satu kolum bernama 'data'
Kolum 'data' adalah berbentuk BLOB ( untuk menyimpan binary )





Langkah pertama : Bina table bernama "attachments"
Gunakan PHPMyAdmin atau MySQL Console untuk bina table berikut di dalam database yang digunakan oleh aplikasi CakePHP anda

CREATE TABLE attachments (
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(75) NOT NULL,
  type VARCHAR(255) NOT NULL,
  size INT(11) NOT NULL,
  data MEDIUMBLOB NOT NULL,
  created DATETIME,
  modified DATETIME,
  PRIMARY KEY (id)
);

Langkah kedua : bina model bernama Attachment
cara pertama : cake bake shell di dalam console
cake bake model attachment

Arahan di atas akan membina satu fail model di app/models/attachment.php

<?php
class Attachment extends AppModel {

  var $name = 'Attachment';

}
?>
Atau anda boleh cipta fail tersebut menggunakan editor dan letakkan di folder app/models/attachment.php

Langkah ke-tiga : bina controller bernama Attachments
Dengan menggunakan cake shell command, laksanakan arahan berikut untuk membina controller untuk Attachments

cake bake controller attachments
hasilnya adalah seperti berikut

<?php
class AttachmentsController extends AppController {

  var $name = 'Attachments';
  var $scaffold;
}
?>

Tetapi kita tidak akan menggunakan fungsi $scaffold, dan hanya akan gunakan 3 function / action sahaja iaitu
  1. index
  2. download
  3. delete
Jadi ubah controller  app/controllers/attachments_controller.php anda seperti kod di bawah

<?php
class AttachmentsController extends AppController {

  var $name = 'Attachments';
  #var $scaffold;

  # untuk paparkan senarai fail
  # untuk upload fail
  function index(){
  }

  # untuk membolehkan fail download dari database
  function download(){
  }

  # untuk padam fail dalam database
  function delete(){
  }
}

?>

Langkah ke-empat : bina folder views untuk Attachments
Seterusnya, kita perlu bina satu folder khas untuk attachments di app/views/attachments .Di dalam folder ini akan menyimpan fail-fail views untuk controller attachments. Dalam projek ini, kita hanya perlukan satu fail view sahaja, iaitu index.ctp . Fail ini adalah untuk mengeluarkan data yang dikeluarkan oleh action index di dalam controller Attachments

mkdir app/views/attachments

Setelah folder container untuk attachments telah dibuat, cipta satu fail bernama index.ctp di dalamnya dan masukkan kod berikut

<?= $form->create('Attachment', array('action' => 'index', 'type' => 'file')); ?>
<?= $form->file('file'); ?>
<?= $form->end('Muat Naik'); ?>

Kod di atas akan menjana borang html dengan menggunakan bantuan dari librari CakePHP bernama FormHelper. Hasilnya adalah kod HTML seperti di bawah

Hasilnya di dalam browser, http://localhost/attachments/index




Langkah ke-lima : menguji sistem upload
Sekarang kita telah mempunyai sistem upload, jika klik buton "Muat Naik" sistem akan "upload" fail ke dalam server. Sebelum kita boleh masukkan data ke dalam database, kita perlu tahu data-data yang dihantar menggunakan borang HTML tersebut.

Di dalam CakePHP ada satu arahan bernama debug($var). Arahan ini berfungsi sebagai pasaukan bedah siasat, yakni untuk melihat kandungan yang dibawa oleh variable. Di dalam CakePHP, variable borang HTML akan dihantar ke controller menggunakan satu variable yang dipanggil $this->data . Jadi untuk melihat kandungan variable $this->data, kita boleh letakkan arahan debug($this->data) di dalam action index . Ubahsuai kod app/controllers/attachments_controller.php anda supaya berbentuk seperti di bawah :

<?php
class AttachmentsController extends AppController {

  var $name = 'Attachments';
  #var $scaffold;

  # untuk paparkan senarai fail
  # untuk upload fail
  function index(){
    # untuk melihat isi kandung $this->data
    debug($this->data);
  }

  # untuk membolehkan fail download dari database
  function download(){
  }

  # untuk padam fail dalam database
  function delete(){
  }
}

?>

Seterusnya cuba upload sebarang fail dan lihat hasilnya di dalam browser.


Cuba lihat gambar di atas, fungsi debug(); telah berjaya membuat bedah siasat kandungan yang dibawa oleh variable $this->data. Data yang di bawa oleh variable $this->data adalah berbentuk Array() yang mengikut susunan berikut
$this->data[ nama model yang digunakan, Attachment ][ form field,file ][ maklumat mengenai fail ]

Untuk mengesan jika ada fail yang diupload, kita perlu cipta satu mekanisme pengesanan. Hanya lakukan proses penyimpanan data ke dalam database jika fail berjaya diupload. Untuk itu kita perlu ubah kod action index di dalam attachments_controller.php seperti berikut :

# untuk paparkan senarai fail
  # untuk upload fail
  function index(){
    # melihat kandungan fail
    debug($this->data);
    if( $this->data &&
        is_uploaded_file($this->data['Attachment']['file']['tmp_name']) ){
      # fail wujud
    }else{
      # fail tidak wujud dan beri mesej
      $this->Session->setFlash('Tiada fail dikesan !');
    }

  }
Anda boleh cuba untuk klik Muat Naik tanpa memilih sebarang fail dan hasilnya akan akan dapat mesej error seperti gambar di bawah.


Jika anda berjaya mendapat mesej seperti di atas, bermaksud kod pengesanan telah berjaya. Mari kita lihat kod pengesanan yang digunakan
  1. if( $this->data &&  
  2.       is_uploaded_file($this->data['Attachment']['file']['tmp_name']) ){ 

Pada baris 1, fungsi if() akan cuba  mengesan kehadiran 2 variable, iaitu $this->data, untuk menandakan bahawa sesorang telah submit HTML form dan juga kehadiran fail sementara di dalam folder sementara, /tmp, menggunakan arahan fungsi is_uploaded_file() . Jika kedua-dua variable adalah benar ( true ) baru laksanakan arahan penyimpanan ke dalam database.

Langkah ke-enam : Simpan fail yang dihantar ke dalam database
Setelah kita berjaya membuat kod pengesanan fail, barulah kita boleh extrak data daripada array dan susun semula mengikut format CakePHP untuk menyimpan data dan fail ke dalam table attachments. Sila ubah kod action index seperti berikut

# untuk paparkan senarai fail
  # untuk upload fail
  function index(){
    # melihat kandungan fail
    debug($this->data);
    if( $this->data &&
        is_uploaded_file($this->data['Attachment']['file']['tmp_name']) ){

        # fail wujud dan file dah upload, kita nak baca bentuk binari
        # untuk simpan dalam database
        $filedata = fread(fopen($this->data['Attachment']['file']['tmp_name'], "r"),
                                     $this->data['Attachment']['file']['size']);

        # untuk beri nama kepada fail
        $filename = basename($this->data['Attachment']['file']['name']);

        # assignkan uploaded data ke bentuk cakephp
        $this->data['Attachment']['data'] = $filedata;
        $this->data['Attachment']['name'] = $filename;
        $this->data['Attachment']['size'] = $this->data['Attachment']['file']['size'];
        $this->data['Attachment']['type'] = $this->data['Attachment']['file']['type'];


        # save ke dalam database
        if($this->Attachment->save($this->data)){
          $this->Session->setFlash('Berjaya save ke database');
        } else {
          $this->Session->setFlash('Gagal save ke database');
        }

    }else{
      # fail tidak wujud dan beri mesej
      $this->Session->setFlash('Tiada fail dikesan !');
    }

  }
Sekarang, anda boleh mencuba untuk upload sebarang jenis fail dan kemudian lihat kandungan fail tersebut di dalam database menggunakan phpMyAdmin.


 Jika anda berjaya melihat kandungan data tersebut di dalam PhpMyAdmin, anda telah berjaya ! Tahniah.

Langkah ke-tujuh : Paparkan senarai fail
Dalam siri di atas, kita telah berjaya upload fail ke dalam database MySQL. Seterusnya, kita akan buat sistem paparan menggunakan ORM CakePHP. Ubahsuai kod action index seperti berikut

# untuk paparkan senarai fail
  # untuk upload fail
  function index(){
    # melihat kandungan fail
    debug($this->data);
    if( $this->data &&
        is_uploaded_file($this->data['Attachment']['file']['tmp_name']) ){

        # fail wujud dan file dah upload, kita nak baca bentuk binari
        # untuk simpan dalam database
        $filedata = fread(fopen($this->data['Attachment']['file']['tmp_name'], "r"),
                                     $this->data['Attachment']['file']['size']);

        # untuk beri nama kepada fail
        $filename = basename($this->data['Attachment']['file']['name']);

        # assignkan uploaded data ke bentuk cakephp
        $this->data['Attachment']['data'] = $filedata;
        $this->data['Attachment']['name'] = $filename;
        $this->data['Attachment']['size'] = $this->data['Attachment']['file']['size'];
        $this->data['Attachment']['type'] = $this->data['Attachment']['file']['type'];


        # save ke dalam database
        if($this->Attachment->save($this->data)){
          $this->Session->setFlash('Berjaya save ke database');
        } else {
          $this->Session->setFlash('Gagal save ke database');
        }

    }else{
      # fail tidak wujud dan beri mesej
      $this->Session->setFlash('Tiada fail dikesan !');
    }

    # senaraikan fail yang disimpan dalam database
    $options = array(
                  # hanya keluarkan field yang perlu sahaja
                  'fields' => array('id','name','type','size','created'),
                  # buat susunan supaya order by id DESC
                  'order' => 'id DESC'
                   );
    # laksanakan arahan carian dan beri nilai ke variable $attachments
    $attachments = $this->Attachment->find('all',$options);

    # setkan nilai ke index.ctp
    $this->set(compact('attachments'));


  }

Kemudian, ubahsuai kod app/views/attachments/index.ctp untuk masukkan kod untuk tujuan penyenaraian fail

<?= $form->create('Attachment', array('type' => 'file')); ?>
<?= $form->file('file'); ?>
<?= $form->end('Muat Naik'); ?>

<? if(isset($attachments)): ?>













    <? foreach($attachments as $a): ?>
  • <?= $html->link($a['Attachment']['name'], array('action' => 'download', $a['Attachment']['id'])); ?> [ <?= round($a['Attachment']['size']/1000); ?> kb ] <?= $html->link('Padam', array('action' => 'delete', $a['Attachment']['id'])); ?>
  • <? endforeach; ?>
<? endif; ?>
Cuba upload beberapa fail dan hasilnya adalah seperti berikut :


Jika anda berjaya melihat paparan seperti di atas, tahniah sekali lagi. Anda telah berjaya membuat sistem senarai fail. Jika anda lihat kod di dalam index.ctp, saya menggunakan arahan Foreach() dan endForeach() untuk menyenaraikan data yang terkandung di dalam variable $attachments

Langkah ke-lapan : action download()
Jika anda klik pautan nama fail di action index, anda akan mendapat ralat Missing View. Fungsi action download() ialah untuk mengeluarkan data dari database dan kemudiannya pengguna akan download fail yang mereka pilih dalam format yang sama. jadi untuk itu kita perlu ubahsuai action download() di dalam Attachments Controller. Sila lihat kod di bawah:

# untuk membolehkan fail download dari database
  # id akan digunakan sebagai identifier
  function download($id = null){
    # cari fail berdasarkan id yang diberi
    $file = $this->Attachment->findById($id);

    if($file){
      # fail wujud, laksanakan arahan download
      header('Content-type: ' . $file['Attachment']['type']);
      header('Content-length: ' . $file['Attachment']['size']);
      header('Content-Disposition: attachment; filename='.$file['Attachment']['name']);
      echo $file['Attachment']['data'];
      exit();
    } else {
      # fail tidak wujud, bagi mesej dan redirect ke index
      $this->Session->setFlash('Fail tidak wujud');
      $this->redirect(array('action' => 'index'));
    }
  }

Kod di atas telah diberi komen, anda boleh memahaminya dengan membaca komen di atas. Untuk menguji sistem di atas, sila ke action index, http://localhost/attachments/index dan klik pautan nama fail tersebut, sepatutnya anda akan dapat dialog untuk download fail tersebut ke dalam komputer seperti gambar di bawah





Jika anda berjaya mendapat paparan seperti di atas, tahniah, anda telah berjaya membuat sistem untuk download fail dari database.

Langkah ke-sembilan : Sistem padam fail dari database
Ini adalah langkah terakhir di dalam tutorial ini, sistem untuk memadam fail dari database. Ubahsuai action delete() di dalam Attachments Controller seperti berikut

# untuk padam fail dalam database
  function delete($id = null){
    if($id){
      $file = $this->Attachment->del($id);
      if($file){
        $this->Session->setFlash('Berjaya padam');
      }else{
        $this->Session->setFlash('Gagal untuk dipadam');
      }
    }
    # kembali ke action index
    $this->redirect(array('action' => 'index'));
  }


Cuba anda klik pautan padam dan jika anda mendapat mesej seperti gambar di bawah, anda berjaya.




Kod penuh attachments_controller.php
<?php
class AttachmentsController extends AppController {

 var $name = 'Attachments';
 #var $scaffold;

  # untuk paparkan senarai fail
 # untuk upload fail
 function index(){
    # melihat kandungan fail
    #debug($this->data);
    if( $this->data &&
        is_uploaded_file($this->data['Attachment']['file']['tmp_name']) ){
   
       # fail wujud dan file dah upload, kita nak baca bentuk binari
        # untuk simpan dalam database
        $filedata = fread(fopen($this->data['Attachment']['file']['tmp_name'], "r"),
                                     $this->data['Attachment']['file']['size']);

    # untuk beri nama kepada fail
    $filename = basename($this->data['Attachment']['file']['name']);

        # assignkan uploaded data ke bentuk cakephp
        $this->data['Attachment']['data'] = $filedata;
        $this->data['Attachment']['name'] = $filename;
        $this->data['Attachment']['size'] = $this->data['Attachment']['file']['size'];
        $this->data['Attachment']['type'] = $this->data['Attachment']['file']['type'];


        # save ke dalam database
        if($this->Attachment->save($this->data)){
          $this->Session->setFlash('Berjaya save ke database');
        } else {
          $this->Session->setFlash('Gagal save ke database');
        }

  }else{
      # fail tidak wujud dan beri mesej
   #$this->Session->setFlash('Tiada fail dikesan !');
    }

    # senaraikan fail yang disimpan dalam database
  $options = array(
         # hanya keluarkan field yang perlu sahaja
         'fields' => array('id','name','type','size','created'),
         # buat susunan supaya order by id DESC
                  'order' => 'id DESC'
                   );
  # laksanakan arahan carian dan beri nilai ke variable $attachments
    $attachments = $this->Attachment->find('all',$options);
  
    # setkan nilai ke index.ctp
    $this->set(compact('attachments'));
 }

 # untuk membolehkan fail download dari database
 # id akan digunakan sebagai identifier
  function download($id = null){
  # cari fail berdasarkan id yang diberi
    $file = $this->Attachment->findById($id);

  if($file){
   # fail wujud, laksanakan arahan download
     header('Content-type: ' . $file['Attachment']['type']);
     header('Content-length: ' . $file['Attachment']['size']);
     header('Content-Disposition: attachment; filename='.$file['Attachment']['name']);
     echo $file['Attachment']['data'];
     exit();
  } else {
   # fail tidak wujud, bagi mesej dan redirect ke index
      $this->Session->setFlash('Fail tidak wujud');
   $this->redirect(array('action' => 'index'));
  }
  }


 # untuk padam fail dalam database
  function delete($id = null){
    if($id){
      $file = $this->Attachment->del($id);
   if($file){
       $this->Session->setFlash('Berjaya padam');
   }else{
       $this->Session->setFlash('Gagal untuk dipadam');
   }
  }
  # kembali ke action index
    $this->redirect(array('action' => 'index'));
  }
}
?> 

Sambungan siri ini , Membuat Pagination dan Custom Layout

2 comments:

  1. ada tak alternative lain supaya kita tidak menggunakan blob untuk simpan file. Atau ini sahaja cara yang kita ada.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete