1

I have two Entities

 - Kitchen
 - KitchenSubImage

Each kitchen has a main image but also has many sub images (KitchenSubImage).

I have implemented both the entities and their form types. At this moment I have the form displayed and have implemented everything from How to Handle File Uploads with Symfony2 to handle the file upload.

The issue I have is that I have no idea how to handle both file uploads at once. It's made more complicated by the fact that the kitchen can have many sub images.

I also have the following error at the top of the form when I submit the form:

This value should be of type PWD\WebsiteBundle\Entity\KitchenSubImage.

Controller

<?php

namespace PWD\AdminBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use PWD\WebsiteBundle\Entity\Kitchen;
use PWD\AdminBundle\Form\Type\KitchenType;

use PWD\WebsiteBundle\Entity\KitchenSubImage;
use PWD\AdminBundle\Form\Type\KitchenSubImageType;

class KitchenController extends Controller
{
    public function indexAction()
    {
        return 'index';
    }

    public function addAction(Request $request)
    {
        $kitchen = new Kitchen();
        $image = new KitchenSubImage();
        $kitchen->addSubImage($image);
        $form = $this->createForm(new KitchenType(), $kitchen);

        $form->handleRequest($request);

        if ($form->isValid()) {
            $kitchen->upload();
            return $this->render('PWDWebsiteBundle:Pages:home.html.twig');
        }

        return $this->render('PWDAdminBundle:Pages:form-test.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

Kitchen Entity

<?php 

namespace PWD\WebsiteBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @ORM\Table(name="kitchen")
 */
class Kitchen
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=100)
     * @Assert\NotBlank()
     */
    protected $name;

    /**
     * @ORM\Column(type="text")
     * @Assert\NotBlank()
     */
    protected $description;

    /**
     * @Assert\File(maxSize="6000000")
     * @Assert\Image(
     *     minWidth = 800,
     *     maxWidth = 800,
     *     minHeight = 467,
     *     maxHeight = 467
     * )
     */
    protected $mainImage;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    protected $mainImagePath;

    /**
     * @Assert\Type(type="PWD\WebsiteBundle\Entity\KitchenSubImage")
     * @ORM\OneToMany(targetEntity="KitchenSubImage", mappedBy="kitchen")
     */
    protected $subImage;

    public function __construct()
    {
        $this->subImage = new ArrayCollection();
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getDescription()
    {
        return $this->description;
    }

    public function setDescription($description)
    {
        $this->description = $description;
    }

    public function getMainImage()
    {
        return $this->mainImage;
    }

    public function setMainImage(UploadedFile $mainImage = null)
    {
        $this->mainImage = $mainImage;
    }

    public function getSubImage()
    {
        return $this->subImage;
    }

    public function setSubImage(KitchenSubImage $subImage = null)
    {
        $this->subImage = $subImage;
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set mainImagePath
     *
     * @param string $mainImagePath
     * @return Kitchen
     */
    public function setMainImagePath($mainImagePath)
    {
        $this->mainImagePath = $mainImagePath;

        return $this;
    }

    /**
     * Get mainImagePath
     *
     * @return string 
     */
    public function getMainImagePath()
    {
        return $this->mainImagePath;
    }

    public function getAbsolutePath()
    {
        return null === $this->mainImagePath
            ? null
            : $this->getUploadRootDir().'/'.$this->mainImagePath;
    }

    public function getWebPath()
    {
        return null === $this->mainImagePath
            ? null
            : $this->getUploadDir().'/'.$this->mainImagePath;
    }

    public function getUploadRootDir()
    {
        return __DIR__.'/../../../../web/'.$this->getUploadDir();
    }

    public function getUploadDir()
    {
        return 'uploads/our-work';
    }

    public function upload()
    {
        if (null === $this->getMainImage()) {
            return;
        }

        $this->getMainImage()->move(
            $this->getUploadRootDir(),
            $this->getMainImage()->getClientOriginalName()
        );

        $this->mainImagePath = $this->getMainImage()->getClientOriginalName();

        $this->mainImage = null;
    }

    /**
     * Add subImage
     *
     * @param \PWD\WebsiteBundle\Entity\KitchenSubImage $subImage
     * @return Kitchen
     */
    public function addSubImage(\PWD\WebsiteBundle\Entity\KitchenSubImage $subImage)
    {
        $this->subImage[] = $subImage;
        $subImage->setKitchen($this); # used for persisting

        return $this;
    }

    /**
     * Remove subImage
     *
     * @param \PWD\WebsiteBundle\Entity\KitchenSubImage $subImage
     */
    public function removeSubImage(\PWD\WebsiteBundle\Entity\KitchenSubImage $subImage)
    {
        $this->subImage->removeElement($subImage);
    }
}

KitchenSubImage Entity

<?php

namespace PWD\WebsiteBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @ORM\Table(name="kitchenImages")
 */
class KitchenSubImage 
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @Assert\Image(
     *     minWidth = 800,
     *     maxWidth = 800,
     *     minHeight = 467,
     *     maxHeight = 467
     * )
     */
    public $image;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    public $imagePath;

    /**
     * @ORM\ManyToOne(targetEntity="Kitchen", inversedBy="subImage")
     * @ORM\JoinColumn(name="kitchen_id", referencedColumnName="id")
     **/
    protected $kitchen;

    public function getImage()
    {
        return $this->image;
    }

    public function setImage(UploadedFile $image = null)
    {
        $this->image = $image;
    }

    public function getAbsolutePath()
    {
        return null === $this->imagePath
            ? null
            : $this->getUploadRootDir().'/'.$this->imagePath;
    }

    public function getWebPath()
    {
        return null === $this->imagePath
            ? null
            : $this->getUploadDir().'/'.$this->imagePath;
    }

    public function getUploadRootDir()
    {
        return __DIR__.'/../../../../web/'.$this->getUploadDir();
    }

    public function getUploadDir()
    {
        return 'uploads/our-work';
    }

    public function upload()
    {
        if (null === $this->getImage()) {
            return;
        }

        $this->getImage()->move(
            $this->getUploadRootDir(),
            $this->getImage()->getClientOriginalName()
        );

        $this->mainImagePath = $this->getImage()->getClientOriginalName();

        $this->mainImage = null;
    }

    /**
     * Set imagePath
     *
     * @param string $imagePath
     * @return KitchenSubImage
     */
    public function setImagePath($imagePath)
    {
        $this->imagePath = $imagePath;

        return $this;
    }

    /**
     * Get imagePath
     *
     * @return string 
     */
    public function getImagePath()
    {
        return $this->imagePath;
    }

    /**
     * Set kitchen
     *
     * @param \PWD\WebsiteBundle\Entity\Kitchen $kitchen
     * @return KitchenSubImage
     */
    public function setKitchen(\PWD\WebsiteBundle\Entity\Kitchen $kitchen = null)
    {
        $this->kitchen = $kitchen;

        return $this;
    }

    /**
     * Get kitchen
     *
     * @return \PWD\WebsiteBundle\Entity\Kitchen 
     */
    public function getKitchen()
    {
        return $this->kitchen;
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
}

KitchenType:

<?php 

namespace PWD\AdminBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class KitchenType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
        $builder->add('description', 'textarea');
        $builder->add('mainImage', 'file');
        $builder->add('subImage', 'collection', array(
            'type' => new KitchenSubImageType(), 
            'label' => false,
            'allow_add' => true, 
            'by_reference' => false,
        ));
        $builder->add('submit', 'submit');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'PWD\WebsiteBundle\Entity\Kitchen',
            'cascade_validation' => true,
        ));
    }

    public function getName()
    {
        return 'kitchen';
    }
}

KitchenSubImageType:

<?php 

namespace PWD\AdminBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class KitchenSubImageType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('image', 'file', array('label' => 'Sub Images'));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'PWD\WebsiteBundle\Entity\KitchenSubImage',
        ));
    }

    public function getName()
    {
        return 'kitchensubimage';
    }
}

1 Answer 1

1

Welcome back. Kind of wish that you had taken my previous suggestion and gone though the blog/tags example. You are still having big picture issues with collections.

In your kitchen entity, this is all wrong:

protected $subImage;

public function getSubImage()
{
    return $this->subImage;
}
public function setSubImage(KitchenSubImage $subImage = null)
{
    $this->subImage = $subImage;
}

It should be:

protected $subImages;

public function getSubImages()
{
    return $this->subImages;
}
public function addSubImage(KitchenSubImage $subImage)
{
    $this->subImages[] = $subImage;
    $subImage->setKitchen($this);
}

See how a collection aka relation works in Doctrine?. Just like the bolg/tags example shows. As the form component processes the subImages collection, it will call addSubImage for each posted KitchenSubImage.

The above change may or may not fix everything. Kind of doubt it. If not:

Please tell me that you got the Kitchen form working before you added the sub image collection? You are able to load/store/retrieve the main image? If not, comment out $builder->add('subImage', 'collection', and focus on the kitchen entity.

Once kitchen is working, add subImages back into the form but comment out the allow_add and report what happens.

===================================================

With respect to how the sub images are processed, I can understand some of the confusion. I have not implemented a collection of images my self. Might be some gotchas.

I do know that your need to call upload on each sub image. upload is actually a somewhat misleading name. The file is already on your serve sitting in a tmp directory somewhere. Upload just moves it to a permanent location and stores the path in your entity.

Start by trying this:

    if ($form->isValid()) {
        $kitchen->upload();
        foreach($kitchen->getSubImages() as $subImage)
        {
            $subImage->upload();
        }
        // really should redirect here but okay for now
        return $this->render('PWDWebsiteBundle:Pages:home.html.twig');
    }

It might be better to loop on subImages in kitchen::upload but try it in the controller for now.

Sign up to request clarification or add additional context in comments.

13 Comments

Hey again! Yeah I got the Kitchen form working before I started to implement subImages. I've implemented your changes above and commented out subImages part of the form. Here's my controller class with a fully working kitchen - pastebin.com/1MCg5NEL
What I can't get my head around is how will the subImages be uploaded?
Done the changes but I'm unable to get the Sub Image file input to display?
I got the form to display (had to change the field name in form type from subImage to subImages. However the orginal still exists.
Clarify what "original still exists" means.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.