Friday, February 20, 2015

Symfony 2 Hari 11: Upload file di Symfony 2

Artikel ini dirunut berdasarkan Jobeet Tutorial, yang dibuat oleh Fabien Potencier, untuk Symfony 1.4.

Menangani file upload di Symfony 2

Tutorial upload kali ini adalah melanjutkan form posting job. Untuk memungkin user mengupload file kita akan menggunakan virtual field, untuk itu  kita akan mengedit file formnya:

src/Ibw/JobeetBundle/Entity/Job.php

  // ...
    public $file;
    // ...
Sekarang kita harus mengubah filed logo di form menjadi file input tag:
src/Ibw/JobeetBundle/Form/JobType.php
// ...
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('file', 'file', array('label' => 'Company logo', 'required' => false))
            // ...
    }
// ...
Untuk memastikan bahwa user telah mengupload file maka kita harus melakukan validasi:
src/Ibw/JobeetBundle/Resources/config/validation.yml
Ibw\JobeetBundle\Entity\Job:
    properties:
        # ...
        file:
            - Image: ~
Saat tombol submit diklik kita harus memproses dan menyimpan file yang diupload oleh user:
src/Ibw/JobeetBundle/Controller/JobController.php 
// ...
    public function createAction(Request $request)
    {
        // ...
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $entity->file->move(__DIR__.'/../../../../web/uploads/jobs', $entity->file->getClientOriginalName());
            $entity->setLogo($entity->file->getClientOriginalName());
            $em->persist($entity);
            $em->flush();
            return $this->redirect($this->generateUrl('ibw_job_show', array(
                'company' => $entity->getCompanySlug(),
                'location' => $entity->getLocationSlug(),
                'id' => $entity->getId(),
                'position' => $entity->getPositionSlug()
            )));
        }
        // ...
    }
// ...
 
  
Selanjutnya kamu harus membuat folder di direktori web/uploads/jobs/ untuk menyimpan filenya. Dan pastikan folder tersebut rewrite able oleh server.

sudo chmod -R 777 web/uploads/jobs/
Untuk membuatnya dapat bekerja dengan baik kita harus mengubah file model Job.php sebagai berikut ini:
src/Ibw/JobeetBundle/Entity/Job.php
class Job
{
    // ...
    protected function getUploadDir()
    {
        return 'uploads/jobs';
    }
    protected function getUploadRootDir()
    {
        return __DIR__.'/../../../../web/'.$this->getUploadDir();
    }
    public function getWebPath()
    {
        return null === $this->logo ? null : $this->getUploadDir().'/'.$this->logo;
    }
    public function getAbsolutePath()
    {
        return null === $this->logo ? null : $this->getUploadRootDir().'/'.$this->logo;
    }
}

method getAbsolutePath digunakan untuk menyimpan path ke database. Sedangkan method getWebPath digunakan untuk memanggil file dari template.

Ok sekarang kita perlu untuk menambahkan lifecycleCallbacks di entity Job yaitu preUpload, upload dan removeUpload. Untuk melakukannya edit file Job.orm.yml dan tambahkan kode berikut:

src/Ibw/JobeetBundle/Resources/config/doctrine/Job.orm.yml

Ibw\JobeetBundle\Entity\Job:
    # ...
 
    lifecycleCallbacks:
        prePersist: [ preUpload, setCreatedAtValue, setExpiresAtValue ]
        preUpdate: [ preUpload, setUpdatedAtValue ]
        postPersist: [ upload ]
        postUpdate: [ upload ]
        postRemove: [ removeUpload ]
 
Sekarang jalankan perintah generate entities untuk mengupdate schema di entitinya:
 
php app/console doctrine:generate:entities IbwJobeetBundle
 
Edit file entity job.php dan tambahkan script berikut:
 
src/Ibw/JobeetBundle/Entity/Job.php
  
class Job
{
    // ...
 
    /**
     * @ORM\PrePersist
     */
    public function preUpload()
    {
         if (null !== $this->file) {
             $this->logo = uniqid().'.'.$this->file->guessExtension();
         }
    }
 
    /**
     * @ORM\PostPersist
     */
    public function upload()
    {
        if (null === $this->file) {
            return;
        }
 
        // If there is an error when moving the file, an exception will
        // be automatically thrown by move(). This will properly prevent
        // the entity from being persisted to the database on error
        $this->file->move($this->getUploadRootDir(), $this->logo);
 
        unset($this->file);
    }
 
    /**
     * @ORM\PostRemove
     */
    public function removeUpload()
    {
        if(file_exists($file)) {
            if ($file = $this->getAbsolutePath()) {
                unlink($file);
            }
        }    
    }
}
 
Sekarang entitinya sudah dapat menggenerate nama file yang unik untuk itu sekarang kita sesuaikan controllernya:

src/Ibw/JobeetBundle/Controller/JobController.php

// ...
 
    public function createAction(Request $request)
    {
        $entity  = new Job();
        $form = $this->createForm(new JobType(), $entity);
        $form->bind($request);
 
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
 
            $em->persist($entity);
            $em->flush();
 
            return $this->redirect($this->generateUrl('ibw_job_show', array(
                'company' => $entity->getCompanySlug(),
                'location' => $entity->getLocationSlug(),
                'id' => $entity->getId(),
                'position' => $entity->getPositionSlug()
            )));
        }
 
        return $this->render('IbwJobeetBundle:Job:new.html.twig', array(
            'entity' => $entity,
            'form'   => $form->createView(),
        ));
    }
 
// ...
 
  

Thursday, February 19, 2015

Symfony 2 Hari 10: Form di Symfony 2

Artikel ini dirunut berdasarkan Jobeet Tutorial, yang dibuat oleh Fabien Potencier, untuk Symfony 1.4.

Memodifikasi form Job

Job form adalah contoh yang sempurna untuk memodifikasi form. Mari kita mulai selangkah demi selangkah. Pertama tambahkan link untuk memposting Job.

src/Ibw/JobeetBundle/Resources/views/layout.html.twig

<a href="{{ path('ibw_job_new') }}">Post a Job</a>
Kemudian ubah roting untuk createAction di JobController


src/Ibw/JobeetBundle/Controller/JobController.php
// ...
public function createAction(Request $request)
{
    $entity  = new Job();
    $form = $this->createForm(new JobType(), $entity);
    $form->bind($request);
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($entity);
        $em->flush();
        return $this->redirect($this->generateUrl('ibw_job_show', array(
            'company' => $entity->getCompanySlug(),
            'location' => $entity->getLocationSlug(),
            'id' => $entity->getId(),
            'position' => $entity->getPositionSlug()
        )));
    }
    return $this->render('IbwJobeetBundle:Job:new.html.twig', array(
        'entity' => $entity,
        'form'   => $form->createView(),
    ));
}
// ...
Secara default doctrine menggenerate form sesuai dengan field yang ada di table. Untuk itu edit Job form menjadi seperti berikut ini:
src/Ibw/JobeetBundle/Form/JobType.php
namespace Ibw\JobeetBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class JobType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('type')
            ->add('category')
            ->add('company')
            ->add('logo')
            ->add('url')
            ->add('position')
            ->add('location')
            ->add('description')
            ->add('how_to_apply')
            ->add('token')
            ->add('is_public')
            ->add('email')
        ;
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Ibw\JobeetBundle\Entity\Job'
        ));
    }
    public function getName()
    {
        return 'job';
    }
}

Ok, untuk menambahkan validasi pada form, buatlah file validation.yml di dalam folder src/Ibw/JobeetBundle/Resources/config/validation.yml.


src/Ibw/JobeetBundle/Resources/config/validation.yml

Ibw\JobeetBundle\Entity\Job:
    properties:
        email:
            - NotBlank: ~
            - Email: ~


Meskipun di dalam skema database kolom type berupa varchar tetapi kita ingin membuatnya lebih ketat hanya dengan tiga pilihan yang kita set sendiri yaitu: fulltime, partime dan freelence. Tambahkan option tersebut pada form Job tadi:


src/Ibw/JobeetBundle/Form/JobType.php

// ...
use Ibw\JobeetBundle\Entity\Job;
class JobType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('type', 'choice', array('choices' => Job::getTypes(), 'expanded' => true))
            // ...
    }
    // ...
}


Agar pilihan tersebut dapat muncul di halaman form tambahakan kode berikut ini di file model Job.php 

src/Ibw/JobeetBundle/Entity/Job.php

  // ...
    public static function getTypes()
    {
        return array('full-time' => 'Full time', 'part-time' => 'Part time', 'freelance' => 'Freelance');
    }
    public static function getTypeValues()
    {
        return array_keys(self::getTypes());
    }
    // ...
Method getTypes() digunakan untuk mendapatkan value tipe job yang kita buat tadi, dan method getTypeVlues(), digunakan untuk melakukan validasi field.
src/Ibw/JobeetBundle/Resources/config/validation.yml
Ibw\JobeetBundle\Entity\Job:
    properties:
        type:
            - NotBlank: ~
            - Choice: { callback: getTypeValues }
        email:
            - NotBlank: ~
            - Email: ~
Untuk masing-masing field di Symfony 2 sudah memiliki label yang digenerate secara otomatis. Ini dapat diedit dengan label option pada form.
src/Ibw/JobeetBundle/Form/JobType.php
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('logo', null, array('label' => 'Company logo'))
            // ...
            ->add('how_to_apply', null, array('label' => 'How to apply?'))
            // ...
            ->add('is_public', null, array('label' => 'Public?'))
            // ...
    }
Kamu dapat menambahkan validase untuk kesemua field dengan cara sebagai berikut:
src/Ibw/JobeetBundle/Resources/config/validation.yml
Ibw\JobeetBundle\Entity\Job:
    properties:
        category:
            - NotBlank: ~
        type:
            - NotBlank: ~
            - Choice: {callback: getTypeValues}
        company:
            - NotBlank: ~
        position:
            - NotBlank: ~
        location:
            - NotBlank: ~
        description:
            - NotBlank: ~
        how_to_apply:
            - NotBlank: ~
        token:
            - NotBlank: ~
        email:
            - NotBlank: ~
            - Email: ~
        url:
            - Url: ~
Setelah melakukan perubahan pada validasi kamu perlu membersihkan cachenya: 
  
   
php app/console cache:clear --env=prod


php app/console cache:clear --env=dev