symfony2admingenerator / AvocodeFormExtensionsBundle

(old-legacy) Symfony2 form extensions for Admingenerator project (also working standalone!)
Other
48 stars 31 forks source link

How correctly make relations? #42

Open stajnert opened 11 years ago

stajnert commented 11 years ago

I have problem with relations one-to-many. I have two entities: company and related table company_images (with company_id column). Here is part of my company-generator.yml:

embed_types: 
    - "CompanyImageAdmin-generator.yml"
    fields:
      images:
        label:            Images
        dbType:           collection
        formType:         collection_upload
        addFormOptions:
          nameable:             false          
          editable:             [ file ]
          primary_key:          id
          #
          ### you can create your own form type
          # type:               \Acme\DemoBundle\Form\MyFormType
          #
          ### or use admin:generate-admin command and use the admingenerated form type
          type:               \Acme\CompanyBundle\Form\Type\CompanyImageAdmin\EditType
          #
          maxNumberOfFiles:     5
          maxFileSize:          500000
          minFileSize:          1000
          acceptFileTypes:      /(\.|\/)(gif|jpe?g|png)$/i
          loadImageFileTypes:   /^image\/(gif|jpe?g|png)$/i
          loadImageMaxFileSize: 250000
          previewMaxWidth:      100
          previewMaxHeight:     100
          previewAsCanvas:      true
          prependFiles:         false
          allow_add:            true
          allow_delete:         true
          error_bubbling:       false
          options:
            data_class:       Acme\CompanyBundle\Entity\CompanyImage

When I try submit form I have error

An exception occurred while executing 'INSERT INTO company_image (active, name, file, created, updated, company_id) VALUES (?, ?, ?, ?, ?, ?)' with params [null, "test.jpg", {}, "2013-08-09 10:07:54", "2013-08-09 10:07:54", null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'company_id' cannot be null

How I should do this in right way?

ioleo commented 11 years ago

@panslaw show your CompanyImage class, I think you're missing the setParent method

stajnert commented 11 years ago

Below you can find my CompanyImage. I add setParent method and (you're right) relation was saved. The file is not save on disk because I don't run upload method. On section How to handle File Uploads with Doctrine I can run this method after valid form. These methods: getAbsolutePath(), getWebPath(), getUploadRootDir(), getUploadDir(), upload() are needed with your bundle?

<?php

namespace Goat\CompanyBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Avocode\FormExtensionsBundle\Form\Model\UploadCollectionFileInterface;

/**
 * CompanyImage
 */
class CompanyImage implements UploadCollectionFileInterface
{

    /**
     * @var integer
     */
    private $id;

    /**
     * @var boolean
     */
    private $active;

    /**
     * @var string
     */
    protected $file;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @var \DateTime
     */
    private $updated;

    /**
     * @var \Goat\CompanyBundle\Entity\Company
     */
    private $company;
    protected $path;

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

    /**
     * Set active
     *
     * @param boolean $active
     * @return CompanyImage
     */
    public function setActive($active)
    {
        $this->active = $active;

        return $this;
    }

    /**
     * Get active
     *
     * @return boolean 
     */
    public function getActive()
    {
        return $this->active;
    }

    /**
     * Set file
     *
     * @param string $file
     * @return CompanyImage
     */
    /* public function setFile($file)
      {
      $this->file = $file;

      return $this;
      } */

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

    /**
     * Set created
     *
     * @param \DateTime $created
     * @return CompanyImage
     */
    public function setCreated($created)
    {
        $this->created = $created;

        return $this;
    }

    /**
     * Get created
     *
     * @return \DateTime 
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * Set updated
     *
     * @param \DateTime $updated
     * @return CompanyImage
     */
    public function setUpdated($updated)
    {
        $this->updated = $updated;

        return $this;
    }

    /**
     * Get updated
     *
     * @return \DateTime 
     */
    public function getUpdated()
    {
        return $this->updated;
    }

    /**
     * Set company
     *
     * @param \Goat\CompanyBundle\Entity\Company $company
     * @return CompanyImage
     */
    public function setCompany(\Goat\CompanyBundle\Entity\Company $company)
    {
        $this->company = $company;

        return $this;
    }

    /**
     * Get company
     *
     * @return \Goat\CompanyBundle\Entity\Company 
     */
    public function getCompany()
    {
        return $this->company;
    }

    // see note below
    public function getFileWebPath()
    {
        return $this->getWebPath();
    }

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

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

    protected function getUploadRootDir()
    {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__ . '/../../../../web/' . $this->getUploadDir();
    }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'uploads/company/images';
    }

    public function upload()
    {
        // the file property can be empty if the field is not required
        if (null === $this->getFile()) {
            return;
        }

        $filename = sha1(uniqid(mt_rand(), true)) . '.' . $this->getFile()->guessExtension();
        // use the original file name here but you should
        // sanitize it at least to avoid any security issues
        // move takes the target directory and then the
        // target filename to move to
        $this->getFile()->move(
                $this->getUploadRootDir(), $filename
        );

        // set the path property to the filename where you've saved the file
        $this->file = $filename;

        // clean up the file property as you won't need it anymore
        $this->path = null;
    }

    public function getFile()
    {
        // inject file into property (if uploaded)
        if ($this->getAbsolutePath()) {
            return new \Symfony\Component\HttpFoundation\File\File(
                    $this->getAbsolutePath()
            );
        }

        return null;
    }

    public function getSize()
    {
        return $this->getFile()->getFileInfo()->getSize();
    }

    public function getPreview()
    {
        return (preg_match('/image\/.*/i', $this->getFile()->getMimeType()));
    }

    public function setFile(\Symfony\Component\HttpFoundation\File\File $file)
    {
        $this->file = $file;
        return $this;
    }

    public function setParent($parent)
    {
        $this->setCompany($parent);
    }

    /**
     * @var string
     */
    private $name;

    /**
     * Set name
     *
     * @param string $name
     * @return CompanyImage
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
}
ioleo commented 11 years ago

@panslaw the way you handle file uploads is totally up to you:

Just remember that when the eventListener "catches" new uploaded files, it does this

<?php
$file = new $this->dataClass();
if (!$file instanceof UploadCollectionFileInterface) {
    throw new UnexpectedTypeException($file, '\Avocode\FormExtensionsBundle\Form\Model\UploadCollectionFileInterface');
}

$file->setFile($upload);
$file->setParent($data);

So.. when setFile is called you must trigger the upload method (move the file, save the path) and when setParent is called you must save the relation between the parent that holds the collection (eg. Album) and the child (eg. AlbumImage).

stajnert commented 11 years ago

I decided to use VichUploaderBundle and yes, file is move to correctly directory and save on database full path to file (is it correct behavior?) - one step close :) but after refresh page I can't see no one added files. Which bundle is responsible for display uploaded images and what else I should configure?

ioleo commented 11 years ago

VichUploaderBundle is only responsible for UPLOAD and injecting a file into property. So every time you do $object = $doctrine->getManager()->getRepository('AcmeDemoBundle:MyTestFile')->find($id) then $object->getFile() will return Symfony\Component\HttpFoundation\File\File (which extends PHP's SplFileInfo)

Displaying the already added files is CollectionUpload job.

If you have files in the database saved properly and you get no errors when displaying the form, then maybe check symfony2 logs for errors?

stajnert commented 11 years ago

I checked logs but there is no errors. On logs side everything looks fine. I have no idea. Maybe it's problem with file path? Should I have on database full file path or only path starts from /web directory (like /uploads/company/image/file.jpg)?

ioleo commented 11 years ago

No. Your path should contain only the file name (if you're useing Vich).

look here for example vich configuration

this config defines that all gallery_image should be placed into %kernel.root_dir%/../web/gallery/image directory. So the PATH property only holds the file name and the application knows where to look for gallery_images thanks to this (vich uploader bundle configuration) mapping

stajnert commented 11 years ago

I have the same configuration, but Vich Bundle saves full path to image in database, not only file name. It's not path problem, when I left in table only file name and remove rest of path file is not listed.

stajnert commented 11 years ago

Wow, something different! Before I tried edit object - it doesn't works. Now I tried add new object and I see that collection upload add another files below buttons but now when I submit form I've got error:

Warning: Invalid argument supplied for foreach() in C:\wamp\www\koziolki\vendor\avocode\form-extensions-bundle\Avocode\FormExtensionsBundle\Form\EventListener\CollectionUploadSubscriber.php line 122

ioleo commented 11 years ago

On line 122 the event listener is looping through ArrayCollection of items and removeing those, whose primary key has not been submitted.

Please show your \Acme\CompanyBundle\Form\Type\CompanyImageAdmin\EditType OR generator.yml file used to generate that form type

stajnert commented 11 years ago

My CompanyBundle\Form\Type\CompanyImageAdmin\EditType is empty - is generated by generator

namespace Goat\CompanyBundle\Form\Type\CompanyImageAdmin;

use Admingenerated\GoatCompanyBundle\Form\BaseCompanyImageAdminType\EditType as BaseEditType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class EditType extends BaseEditType
{

}

and my CompanyAdmin-generator.yml

generator: admingenerator.generator.doctrine
params:
    model: Goat\CompanyBundle\Entity\Company
    namespace_prefix: Goat
    entity_manager: ~
    concurrency_lock: ~
    bundle_name: CompanyBundle
    pk_requirement: ~
    embed_types: 
    - "CompanyImageAdmin-generator.yml"
    fields:
      images:
        label:            Images
        dbType:           collection
        formType:         collection_upload
        addFormOptions:
          nameable:             false          
          editable:             [ file ]
          #
          ### you can create your own form type
          # type:               \Acme\DemoBundle\Form\MyFormType
          #
          ### or use admin:generate-admin command and use the admingenerated form type
          type:               \Goat\CompanyBundle\Form\Type\CompanyImageAdmin\EditType
          #
          maxNumberOfFiles:     5
          maxFileSize:          500000
          minFileSize:          1000
          acceptFileTypes:      /(\.|\/)(gif|jpe?g|png)$/i
          loadImageFileTypes:   /^image\/(gif|jpe?g|png)$/i
          loadImageMaxFileSize: 250000
          previewMaxWidth:      100
          previewMaxHeight:     100
          previewAsCanvas:      true
          prependFiles:         false
          allow_add:            true
          allow_delete:         true
          error_bubbling:       false
          options:
            data_class:       Goat\CompanyBundle\Entity\CompanyImage
    object_actions:
        delete: ~
    batch_actions:
        delete: ~
builders:
    list:
        params:
            title: List for CompanyBundle
            display: [id, active, name, postcode, street, created]
            actions:
                new: ~
            object_actions:
                edit: ~
                delete: ~
    filters:
        params:
            display: [id, name, slug, postcode, street, www]
    new:
        params:
            title: New object for CompanyBundle
            display: [name, slug, postcode, street, www, images]
            actions:
                save: ~
                list: ~
    edit:
        params:
            title: "You're editing the object \"%object%\"|{ %object%: Company.name }|"
            display: ~
            tabs:
              "General information":
                "Basic": [active, city, district, name, logo, description, postcode, street, www, images]
              "Images":
                "Images": []
              "Subtrades":
                "Subtrades": []
            actions:
                save: ~
                list: ~
    show:
        params:
            title: "You're viewing the object \"%object%\"|{ %object%: Company.name }|"
            display: ~
            actions:
                list: ~
                new: ~
    actions:
        params:
            object_actions:
                delete: ~
            batch_actions:
                delete: ~

and CompanyImageAdmin-generator.yml

generator: admingenerator.generator.doctrine
params:
    model: Goat\CompanyBundle\Entity\CompanyImage
    namespace_prefix: Goat
    entity_manager: ~
    concurrency_lock: ~
    bundle_name: CompanyBundle
    pk_requirement: ~
    fields: ~
    object_actions:
        delete: ~
    batch_actions:
        delete: ~
builders:
    list:
        params:
            title: List for CompanyBundle
            display: ~
            actions:
                new: ~
            object_actions:
                edit: ~
                delete: ~
    filters:
        params:
            display: ~
    new:
        params:
            title: New object for CompanyBundle
            display: ~
            actions:
                save: ~
                list: ~
    edit:
        params:
            title: "You're editing the object \"%object%\"|{ %object%: YourModel.title }|"
            display: ~
            actions:
                save: ~
                list: ~
    show:
        params:
            title: "You're viewing the object \"%object%\"|{ %object%: YourModel.title }|"
            display: ~
            actions:
                list: ~
                new: ~
    actions:
        params:
            object_actions:
                delete: ~
            batch_actions:
                delete: ~
ioleo commented 11 years ago

CompanyImageAdmin needs only edit builder (to generate the edit form type):

generator: admingenerator.generator.doctrine
params:
    model: Goat\CompanyBundle\Entity\CompanyImage
    namespace_prefix: Goat
    bundle_name: CompanyBundle
builders:
    edit:
        params:
            display: [ id ] # here add all fields that have to be edited
            # including primary_key, nameable_field, sortable_field, but NOT file field

            # eg. [ id, name, description ]

            # we're useing it only to generate the form type
            title: "" # no need for title in this builder
            actions: [] # no need for actions in this builder 

Also, remove file from editable option in CompanyAdmin entity:

editable: [] 
# this is only for additional metadata fields, like 
# adding a "description" field

# primary_key, nameable_field, sortable_field and file should not be added in editable
ioleo commented 11 years ago

File field is not editable. The widget allows you to remove an image (and related entity) and add another one, but does not allow changeing image/file for already existing image/file entity.

stajnert commented 11 years ago

With changes I have error:

Key "id" in object (with ArrayAccess) of type "Symfony\Component\Form\FormView" does not exist in AvocodeFormExtensionsBundle:Form/CollectionUpload:template_download.html.twig at line 34 

my php CompanyImageAdmin-generator.yml

generator: admingenerator.generator.doctrine
params:
    model: Goat\CompanyBundle\Entity\CompanyImage
    namespace_prefix: Goat
    entity_manager: ~
    concurrency_lock: ~
    bundle_name: CompanyBundle
    pk_requirement: ~
    fields: ~
    object_actions:
        delete: ~
    batch_actions:
        delete: ~
builders:
    edit:
        params:
            title: "edit"
            display: [ id ]            
            actions: []            

and php CompanyAdmin-generator.yml

generator: admingenerator.generator.doctrine
params:
    model: Goat\CompanyBundle\Entity\Company
    namespace_prefix: Goat
    entity_manager: ~
    concurrency_lock: ~
    bundle_name: CompanyBundle
    pk_requirement: ~
    embed_types: 
    - "CompanyImageAdmin-generator.yml"
    fields:
      images:
        label:            Images
        dbType:           collection
        formType:         collection_upload
        addFormOptions:
          nameable:             false          
          editable:             []
          #
          ### you can create your own form type
          # type:               \Acme\DemoBundle\Form\MyFormType
          #
          ### or use admin:generate-admin command and use the admingenerated form type
          type:               \Goat\CompanyBundle\Form\Type\CompanyImageAdmin\EditType
          #
          maxNumberOfFiles:     5
          maxFileSize:          500000
          minFileSize:          1000
          acceptFileTypes:      /(\.|\/)(gif|jpe?g|png)$/i
          loadImageFileTypes:   /^image\/(gif|jpe?g|png)$/i
          loadImageMaxFileSize: 250000
          previewMaxWidth:      100
          previewMaxHeight:     100
          previewAsCanvas:      true
          prependFiles:         false
          allow_add:            true
          allow_delete:         true
          error_bubbling:       false
          options:
            data_class:       Goat\CompanyBundle\Entity\CompanyImage
    object_actions:
        delete: ~
    batch_actions:
        delete: ~
builders:
    list:
        params:
            title: List for CompanyBundle
            display: [id, active, name, postcode, street, created]
            actions:
                new: ~
            object_actions:
                edit: ~
                delete: ~
    filters:
        params:
            display: [id, name, slug, postcode, street, www]
    new:
        params:
            title: New object for CompanyBundle
            display: [name, slug, postcode, street, www, images]
            actions:
                save: ~
                list: ~
    edit:
        params:
            title: "You're editing the object \"%object%\"|{ %object%: Company.name }|"
            display: ~
            tabs:
              "General information":
                "Basic": [active, city, district, name, logo, description, postcode, street, www, images]
              "Images":
                "Images": []
              "Subtrades":
                "Subtrades": []
            actions:
                save: ~
                list: ~
    show:
        params:
            title: "You're viewing the object \"%object%\"|{ %object%: Company.name }|"
            display: ~
            actions:
                list: ~
                new: ~
    actions:
        params:
            object_actions:
                delete: ~
            batch_actions:
                delete: ~
ioleo commented 11 years ago

@panslaw This is weird as on this line of template_dowload.html.twig the primary_key is not used (it's rendered a bit lower, on line 43. I'm sorry but I must miss something. I don't see where is the error. I'm sure the widget works becouse I've been fixing it 2 weeks ago and tested everything, so it must be something in your configuration, i just dont see it.

stajnert commented 11 years ago

When I removed cache error with Key "id" in object (with ArrayAccess) is missing but I have previous one:

Warning: Invalid argument supplied for foreach() in C:\wamp\www\koziolki\vendor\avocode\form-extensions-bundle\Avocode\FormExtensionsBundle\Form\EventListener\CollectionUploadSubscriber.php line 122
ioleo commented 11 years ago

Edit CollectionUploadSubscriber.php on line 122 and check what is passed to foreach loop

stajnert commented 11 years ago

I checked three things (onSubmit method): 1) var_dump($data);

object(Goat\CompanyBundle\Entity\Company)[4267]
  private 'id' => null
  private 'active' => null
  private 'slug' => null
  private 'name' => null
  private 'logo' => null
  private 'description' => null
  private 'postcode' => null
  private 'street' => null
  private 'www' => null
  private 'created' => null
  private 'updated' => null
  private 'emails' => 
    object(Doctrine\Common\Collections\ArrayCollection)[4266]
      private '_elements' => 
        array (size=0)
          empty
  private 'faxes' => 
    object(Doctrine\Common\Collections\ArrayCollection)[4265]
      private '_elements' => 
        array (size=0)
          empty
  private 'phones' => 
    object(Doctrine\Common\Collections\ArrayCollection)[118]
      private '_elements' => 
        array (size=0)
          empty
  private 'city' => null
  private 'district' => null
  private 'subtrades' => 
    object(Doctrine\Common\Collections\ArrayCollection)[3546]
      private '_elements' => 
        array (size=0)
          empty
  private 'path' => null
  private 'images' => null

2) var_dump($getter);

string 'getImages' (length=9)

3) var_dump($data->$getter());

null
stajnert commented 11 years ago

And if you want this is my Company.php file from Entity directory

<?php

namespace Goat\CompanyBundle\Entity;

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

/**
 * Company
 */
class Company
{

    /**
     * @var integer
     */
    private $id;

    /**
     * @var boolean
     */
    private $active;

    /**
     * @var string
     */
    private $slug;

    /**
     * @var string
     */
    private $name;

    /**
     * @Assert\File(maxSize="6000000")
     */
    private $logo;

    /**
     * @var string
     */
    private $description;

    /**
     * @var string
     */
    private $postcode;

    /**
     * @var string
     */
    private $street;

    /**
     * @var string
     */
    private $www;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @var \DateTime
     */
    private $updated;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $emails;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $faxes;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $phones;

    /**
     * @var \Goat\CompanyBundle\Entity\City
     */
    private $city;

    /**
     * @var \Goat\CompanyBundle\Entity\District
     */
    private $district;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $subtrades;
    private $path;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->emails = new \Doctrine\Common\Collections\ArrayCollection();
        $this->faxes = new \Doctrine\Common\Collections\ArrayCollection();
        $this->phones = new \Doctrine\Common\Collections\ArrayCollection();
        $this->subtrades = new \Doctrine\Common\Collections\ArrayCollection();
    }

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

    /**
     * Set active
     *
     * @param boolean $active
     * @return Company
     */
    public function setActive($active)
    {
        $this->active = $active;

        return $this;
    }

    /**
     * Get active
     *
     * @return boolean 
     */
    public function getActive()
    {
        return $this->active;
    }

    /**
     * Set slug
     *
     * @param string $slug
     * @return Company
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;

        return $this;
    }

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

    /**
     * Set name
     *
     * @param string $name
     * @return Company
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set logo
     *
     * @param string $logo
     * @return Company
     */
    public function setLogo(UploadedFile $logo = null)
    {
        $this->logo = $logo;

        //return $this;
    }

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

    /**
     * Set description
     *
     * @param string $description
     * @return Company
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Set postcode
     *
     * @param string $postcode
     * @return Company
     */
    public function setPostcode($postcode)
    {
        $this->postcode = $postcode;

        return $this;
    }

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

    /**
     * Set street
     *
     * @param string $street
     * @return Company
     */
    public function setStreet($street)
    {
        $this->street = $street;

        return $this;
    }

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

    /**
     * Set www
     *
     * @param string $www
     * @return Company
     */
    public function setWww($www)
    {
        $this->www = $www;

        return $this;
    }

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

    /**
     * Set created
     *
     * @param \DateTime $created
     * @return Company
     */
    public function setCreated($created)
    {
        $this->created = $created;

        return $this;
    }

    /**
     * Get created
     *
     * @return \DateTime 
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * Set updated
     *
     * @param \DateTime $updated
     * @return Company
     */
    public function setUpdated($updated)
    {
        $this->updated = $updated;

        return $this;
    }

    /**
     * Get updated
     *
     * @return \DateTime 
     */
    public function getUpdated()
    {
        return $this->updated;
    }

    /**
     * Add emails
     *
     * @param \Goat\CompanyBundle\Entity\CompanyEmail $emails
     * @return Company
     */
    public function addEmail(\Goat\CompanyBundle\Entity\CompanyEmail $emails)
    {
        $this->emails[] = $emails;

        return $this;
    }

    /**
     * Remove emails
     *
     * @param \Goat\CompanyBundle\Entity\CompanyEmail $emails
     */
    public function removeEmail(\Goat\CompanyBundle\Entity\CompanyEmail $emails)
    {
        $this->emails->removeElement($emails);
    }

    /**
     * Get emails
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getEmails()
    {
        return $this->emails;
    }

    /**
     * Add faxes
     *
     * @param \Goat\CompanyBundle\Entity\CompanyFax $faxes
     * @return Company
     */
    public function addFaxe(\Goat\CompanyBundle\Entity\CompanyFax $faxes)
    {
        $this->faxes[] = $faxes;

        return $this;
    }

    /**
     * Remove faxes
     *
     * @param \Goat\CompanyBundle\Entity\CompanyFax $faxes
     */
    public function removeFaxe(\Goat\CompanyBundle\Entity\CompanyFax $faxes)
    {
        $this->faxes->removeElement($faxes);
    }

    /**
     * Get faxes
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getFaxes()
    {
        return $this->faxes;
    }

    /**
     * Add phones
     *
     * @param \Goat\CompanyBundle\Entity\CompanyPhone $phones
     * @return Company
     */
    public function addPhone(\Goat\CompanyBundle\Entity\CompanyPhone $phones)
    {
        $this->phones[] = $phones;

        return $this;
    }

    /**
     * Remove phones
     *
     * @param \Goat\CompanyBundle\Entity\CompanyPhone $phones
     */
    public function removePhone(\Goat\CompanyBundle\Entity\CompanyPhone $phones)
    {
        $this->phones->removeElement($phones);
    }

    /**
     * Get phones
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getPhones()
    {
        return $this->phones;
    }

    /**
     * Set city
     *
     * @param \Goat\CompanyBundle\Entity\City $city
     * @return Company
     */
    public function setCity(\Goat\CompanyBundle\Entity\City $city = null)
    {
        $this->city = $city;

        return $this;
    }

    /**
     * Get city
     *
     * @return \Goat\CompanyBundle\Entity\City 
     */
    public function getCity()
    {
        return $this->city;
    }

    /**
     * Set district
     *
     * @param \Goat\CompanyBundle\Entity\District $district
     * @return Company
     */
    public function setDistrict(\Goat\CompanyBundle\Entity\District $district = null)
    {
        $this->district = $district;

        return $this;
    }

    /**
     * Get district
     *
     * @return \Goat\CompanyBundle\Entity\District 
     */
    public function getDistrict()
    {
        return $this->district;
    }

    /**
     * Add subtrades
     *
     * @param \Goat\TradeBundle\Entity\Subtrade $subtrades
     * @return Company
     */
    public function addSubtrade(\Goat\TradeBundle\Entity\Subtrade $subtrades)
    {
        $this->subtrades[] = $subtrades;

        return $this;
    }

    /**
     * Remove subtrades
     *
     * @param \Goat\TradeBundle\Entity\Subtrade $subtrades
     */
    public function removeSubtrade(\Goat\TradeBundle\Entity\Subtrade $subtrades)
    {
        $this->subtrades->removeElement($subtrades);
    }

    /**
     * Get subtrades
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getSubtrades()
    {
        return $this->subtrades;
    }

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $images;

    /**
     * Add images
     *
     * @param \Goat\CompanyBundle\Entity\CompanyImage $images
     * @return Company
     */
    public function addImages(\Goat\CompanyBundle\Entity\CompanyImage $images)
    {
        $this->images[] = $images;

        return $this;
    }

    /**
     * Remove images
     *
     * @param \Goat\CompanyBundle\Entity\CompanyImage $images
     */
    public function removeImages(\Goat\CompanyBundle\Entity\CompanyImage $images)
    {
        $this->images->removeElement($images);
    }

    /**
     * Get images
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getImages()
    {
        return $this->images;
    }

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

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

    protected function getUploadRootDir()
    {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__ . '/../../../../web/' . $this->getUploadDir();
    }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'uploads/company/logo';
    }

    public function upload()
    {
        // the file property can be empty if the field is not required
        if (null === $this->getLogo()) {
            return;
        }

        $filename = sha1(uniqid(mt_rand(), true)) . '.' . $this->getLogo()->guessExtension();
        // use the original file name here but you should
        // sanitize it at least to avoid any security issues
        // move takes the target directory and then the
        // target filename to move to
        $this->getLogo()->move(
                $this->getUploadRootDir(), $filename
        );

        // set the path property to the filename where you've saved the file
        $this->logo = $filename;

        // clean up the file property as you won't need it anymore
        $this->path = null;
    }

}
ioleo commented 11 years ago

You're missing this in the contructor:

<?php
    public function __construct()
    {
        // ..
        $this->images = new \Doctrine\Common\Collections\ArrayCollection();
    }
stajnert commented 11 years ago

Yes, I've notice this after write comment. I added this and now error is missing but I have two other things: 1) when I choose more than one file only one from these (the last one) is uploaded and save into database. Just one. 2) after refresh page I have edit screen and I don't see any images and I can't add new image - on log I've got error: emergency.EMERGENCY: Call to a member function getFileInfo() on a non-object {"type":1,"file":"\\src\\Goat\\CompanyBundle\\Entity\\CompanyImage.php","line":266} []

stajnert commented 11 years ago

my CompanyImage.php:

<?php

namespace Goat\CompanyBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Avocode\FormExtensionsBundle\Form\Model\UploadCollectionFileInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @Vich\Uploadable
 */
class CompanyImage implements UploadCollectionFileInterface
{

    /**
     * @var integer
     */
    private $id;

    /**
     * @var boolean
     */
    private $active;

    /**
     * @Vich\UploadableField(mapping="company_image", fileNameProperty="path")
     */
    protected $file;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @var \DateTime
     */
    private $updated;

    /**
     * @var \Goat\CompanyBundle\Entity\Company
     */
    private $company;
    protected $path;

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

    /**
     * Set active
     *
     * @param boolean $active
     * @return CompanyImage
     */
    public function setActive($active)
    {
        $this->active = $active;

        return $this;
    }

    /**
     * Get active
     *
     * @return boolean 
     */
    public function getActive()
    {
        return $this->active;
    }

    /**
     * Set file
     *
     * @param string $file
     * @return CompanyImage
     */
    /* public function setFile($file)
      {
      $this->file = $file;

      return $this;
      } */

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

    /**
     * Set created
     *
     * @param \DateTime $created
     * @return CompanyImage
     */
    public function setCreated($created)
    {
        $this->created = $created;

        return $this;
    }

    /**
     * Get created
     *
     * @return \DateTime 
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * Set updated
     *
     * @param \DateTime $updated
     * @return CompanyImage
     */
    public function setUpdated($updated)
    {
        $this->updated = $updated;

        return $this;
    }

    /**
     * Get updated
     *
     * @return \DateTime 
     */
    public function getUpdated()
    {
        return $this->updated;
    }

    /**
     * Set company
     *
     * @param \Goat\CompanyBundle\Entity\Company $company
     * @return CompanyImage
     */
    public function setCompany(\Goat\CompanyBundle\Entity\Company $company)
    {
        $this->company = $company;

        return $this;
    }

    /**
     * Get company
     *
     * @return \Goat\CompanyBundle\Entity\Company 
     */
    public function getCompany()
    {
        return $this->company;
    }
/*
    // see note below
    public function getFileWebPath()
    {
        return $this->getWebPath();
    }

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

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

    protected function getUploadRootDir()
    {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__ . '/../../../../web/' . $this->getUploadDir();
    }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'uploads/company/images';
    }

    public function upload()
    {
        // the file property can be empty if the field is not required
        //if (null === $this->getFile()) {
         //   return;
        //}

        $filename = sha1(uniqid(mt_rand(), true)) . '.' . $this->getFile()->guessExtension();
        // use the original file name here but you should
        // sanitize it at least to avoid any security issues
        // move takes the target directory and then the
        // target filename to move to
        $this->getFile()->move(
                $this->getUploadRootDir(), $filename
        );

        // set the path property to the filename where you've saved the file
        $this->file = $filename;

        // clean up the file property as you won't need it anymore
        $this->path = null;
    }

    public function getFile()
    {
        // inject file into property (if uploaded)
        if ($this->getAbsolutePath()) {
            return new \Symfony\Component\HttpFoundation\File\File(
                    $this->getAbsolutePath()
            );
        }

        return null;
    }

    public function getSize()
    {
        return $this->getFile()->getFileInfo()->getSize();
    }

    public function setFile(\Symfony\Component\HttpFoundation\File\File $file)
    {
        $this->upload();
        $this->file = $file;
        return $this;
    }

    public function setParent($parent)
    {
        $this->setCompany($parent);
    }
    */

    public function setFile(\Symfony\Component\HttpFoundation\File\File $file) {
        $this->file = $file;
        return $this;
    }

    public function getFile() {
        return $this->file;
    }

    public function getSize() {
        return $this->file->getFileInfo()->getSize();
    }

    public function setParent($parent) {
        $this->setCompany($parent);
    }

    public function getPreview() {
        return (preg_match('/image\/.*/i', $this->file->getMimeType()));
    }

    /**
     * @var string
     */
    private $name;

    /**
     * Set name
     *
     * @param string $name
     * @return CompanyImage
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
}
ioleo commented 11 years ago

And which is the line 266 mentioned in the error?

stajnert commented 11 years ago

return $this->file->getFileInfo()->getSize();

ioleo commented 11 years ago

Do you have inject_on_load: true in your vich configuration for that file mapping?

stajnert commented 11 years ago

Yes, this is my configuration:

vich_uploader:
    db_driver: orm
    gaufrette: false
    storage: vich_uploader.storage.file_system
    mappings:
        company_image:
            uri_prefix: /company/images
            upload_destination: %kernel.root_dir%/../web/uploads/company/images
            namer: vich_uploader.namer_uniqid
            inject_on_load: true
            delete_on_remove: true
            delete_on_update: true
stajnert commented 11 years ago

Ok, I solved problem with this return $this->file->getFileInfo()->getSize(); error - I didn't have column path in database. Still I can't add more than one file. When I try upload (for example) three images only the last one was uploaded and saved in database. After reload page (edit) I see one image and when I try (on edit) add another I've got error:

Neither the property "id" nor one of the methods "setId()", "__set()" or "__call()" exist and have public access in class "Goat\CompanyBundle\Entity\CompanyImage".
stajnert commented 11 years ago

Do you have one full working example of this kind of relation? I don't know what I'm doing wrong.

ioleo commented 11 years ago

The example code in documentation is used and working in one of my projects. I only changed namespaces to Acme.