I'm not sure if it's some sort of bug or not, but I have serious problem with making it working, I must missing some important detail, that I couldn't find reading documentation and a lot of issues here.
My Entities:
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="educational_module")
* @ORM\Entity(repositoryClass="AppBundle\Repository\CourseUnitRepository")
*
* Class EducationalUnit
*/
class EducationalModule
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalUnitCategory", inversedBy="educationalUnits")
* @ORM\JoinColumn(name="category", referencedColumnName="course")
*/
private $category;
/**
* @var string
*
* @ORM\Column(type="string", nullable=false)
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Course", mappedBy="module")
*/
private $courses;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\EducationalUnit", mappedBy="module", cascade={"persist","remove"})
*/
private $units;
public function __construct()
{
$this->units = new ArrayCollection();
}
/**
* @return mixed
*/
public function getCategory()
{
return $this->category;
}
/**
* @param mixed $category
*/
public function setCategory($category)
{
$this->category = $category;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getName() : ?string
{
return $this->name;
}
/**
* @param string $name
*/
public function setName(string $name)
{
$this->name = $name;
}
/**
* @return mixed
*/
public function getCourses()
{
return $this->courses;
}
/**
* @return mixed
*/
public function getUnits()
{
return $this->units;
}
public function setUnits($units)
{
$this->units = new ArrayCollection();
foreach ($units as $unit) {
$this->addUnits($unit);
}
}
public function addUnits(EducationalUnit $file)
{
$this->units->add($file);
}
public function removeUnits($file)
{
$this->units->removeElement($file);
}
}
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="educational_unit")
* @ORM\Entity(repositoryClass="AppBundle\Repository\CourseUnitRepository")
*
* Class EducationalUnit
*/
class EducationalUnit
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalModule", inversedBy="units")
*/
private $module;
/**
* @ORM\Column(type="integer", options={"default" : 0})
*/
private $position = 0;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalUnitCategory", inversedBy="educationalUnits")
* @ORM\JoinColumn(name="category", referencedColumnName="course")
*/
private $category;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
*
* @ORM\OneToMany(targetEntity="AppBundle\Entity\EducationalFile", mappedBy="unit", cascade={"persist","remove"})
*/
protected $files;
public function __construct()
{
$this->files = new ArrayCollection();
}
/**
* @return mixed
*/
public function getCategory()
{
return $this->category;
}
/**
* @param mixed $category
*/
public function setCategory($category)
{
$this->category = $category;
}
/**
* @return mixed
*/
public function getModule()
{
return $this->module;
}
public function setModule($module)
{
$this->module = $module;
}
/**
* @return mixed
*/
public function getPosition()
{
return $this->position;
}
/**
* @param mixed $position
*/
public function setPosition($position)
{
$this->position = $position;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return \Doctrine\Common\Collections\ArrayCollection
*/
public function getFiles(): ArrayCollection
{
return $this->files;
}
public function setFiles($files)
{
$this->files = new ArrayCollection();
foreach ($files as $file) {
$this->addFiles($file);
}
}
public function addFiles(EducationalFile $file)
{
$this->files->add($file);
}
public function removeFiles($file)
{
$this->files->removeElement($file);
}
}
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Class EducationalFile
*
* @ORM\Entity
* @Vich\Uploadable
* @ORM\HasLifecycleCallbacks
*/
class EducationalFile
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* @Vich\UploadableField(mapping="educational_file", fileNameProperty="fileName", size="fileSize")
*
* @var File
*/
private $educational_file;
/**
* @ORM\Column(type="string", length=255)
*
* @var string
*/
private $fileName;
/**
* @ORM\Column(type="integer")
*
* @var integer
*/
private $fileSize;
/**
* @ORM\Column(type="datetime")
*
* @var \DateTime
*/
private $updatedAt;
/**
* @var EducationalUnit
*
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\EducationalUnit", inversedBy="files")
*/
private $unit;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $file
*
* @return EducationalFile
*/
public function setEducationalFile(File $file = null)
{
$this->educational_file = $file;
if ($file) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* @return File|null
*/
public function getEducationalFile()
{
return $this->educational_file;
}
/**
* @param string $imageName
*
* @return EducationalFile
*/
public function setFileName($imageName)
{
$this->fileName = $imageName;
return $this;
}
/**
* @return string|null
*/
public function getFileName()
{
return $this->fileName;
}
/**
* @param integer $imageSize
*
* @return EducationalFile
*/
public function setFileSize($imageSize)
{
$this->fileSize = $imageSize;
return $this;
}
/**
* @return integer|null
*/
public function getFileSize()
{
return $this->fileSize;
}
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return mixed
*/
public function getUnit()
{
return $this->unit;
}
public function setUnit($unit)
{
$this->unit = $unit;
}
}
As you can see I'm also using VichUploaderBundle but I think it's irrelevant here.
Next are my admin classes:
<?php
namespace AppBundle\Admin;
use AppBundle\Entity\EducationalFile;
use AppBundle\Entity\EducationalModule;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
/**
* Class EducationalModuleAdmin
*/
class EducationalModuleAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('name', 'text');
$formMapper->add('units', 'sonata_type_collection', [
'required' => false,
'by_reference' => false,
'label' => 'Units',
'type_options' => array(
// Prevents the "Delete" option from being displayed
'delete' => false,
),
], [
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
//'limit' => 1,
]);
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('id');
$datagridMapper->add('name');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('id');
$listMapper->add('name');
}
}
/**
* Class EducationalUnitAdmin
*/
class EducationalUnitAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'module';
protected function configureFormFields(FormMapper $formMapper)
{
//$formMapper->add('module', 'sonata_type_model_hidden');
$formMapper->add('files', 'sonata_type_collection', [
'required' => false,
'by_reference' => false,
'label' => 'Files',
'type_options' => array(
// Prevents the "Delete" option from being displayed
'delete' => false,
),
], [
'edit' => 'inline',
'inline' => 'table',
//'sortable' => 'position',
'limit' => 1,
]);
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('id');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('id');
}
/**
* @param EducationalModule $object
*/
//public function preUpdate($object)
//{
// /** @var EducationalUnit $unit */
// foreach ($object->getUnits() as $unit) {
// $unit->setModule($object);
// }
//}
//
///**
// * @param EducationalModule $object
// */
//public function prePersist($object)
//{
// /** @var EducationalUnit $unit */
// foreach ($object->getUnits() as $unit) {
// $unit->setModule($object);
// }
//}
}
namespace AppBundle\Admin;
use AppBundle\Entity\EducationalFile;
use AppBundle\Entity\EducationalUnit;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
/**
* Class EducationalFileAdmin
*/
class EducationalFileAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'unit';
protected function configureFormFields(FormMapper $formMapper)
{
//$formMapper->add('unit', 'sonata_type_model_hidden');
$formMapper->add('educational_file', 'file');
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('id');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('id');
}
/**
* @param EducationalUnit $object
*/
//public function prePersist($object)
//{
// /** @var EducationalFile $file */
// foreach ($object->getFiles() as $file) {
// $file->setUnit($object);
// }
//}
//
///**
// * @param EducationalUnit $object
// */
//public function preUpdate($object)
//{
// /** @var EducationalFile $file */
// foreach ($object->getFiles() as $file) {
// $file->setUnit($object);
// }
//}
}
And my related services:
app.admin.educational_unit:
class: AppBundle\Admin\EducationalUnitAdmin
arguments: [~, AppBundle\Entity\EducationalUnit, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: Educational unit }
calls:
- method: addChild
arguments: ['@app.admin.educational_file']
public: true
app.admin.educational_file:
class: AppBundle\Admin\EducationalFileAdmin
arguments: [~, AppBundle\Entity\EducationalFile, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: Educational file }
public: true
app.admin.educational_module:
class: AppBundle\Admin\EducationalModuleAdmin
arguments: [~, AppBundle\Entity\EducationalModule, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: Educational module }
calls:
- method: addChild
arguments: ['@app.admin.educational_unit']
public: true
And my config:
sonata_block:
default_contexts: [cms]
blocks:
# enable the SonataAdminBundle block
sonata.admin.block.admin_list:
contexts: [admin]
vich_uploader:
db_driver: orm
mappings:
educational_file:
uri_prefix: /educational_files
upload_destination: '%kernel.root_dir%/../web/educational_files'
I'm trying to create EducationalModule entity, and its children on the way. Unfortunately, with the above code, when adding an EducationalFile to the form, on the AJAX request I'm getting:
Could not get element id from s599b3074a0b2f_units_0_files Failing part: files
Stacktrace:
Exception:
Could not get element id from s599b3074a0b2f_units_0_files Failing part: files
at vendor/sonata-project/admin-bundle/Admin/AdminHelper.php:283
at Sonata\AdminBundle\Admin\AdminHelper->getElementAccessPath('s599b3074a0b2f_units_0_files', object(EducationalModule))
(vendor/sonata-project/admin-bundle/Admin/AdminHelper.php:127)
at Sonata\AdminBundle\Admin\AdminHelper->appendFormFieldElement(object(EducationalModuleAdmin), object(EducationalModule), 's599b3074a0b2f_units_0_files')
(vendor/sonata-project/admin-bundle/Controller/HelperController.php:107)
at Sonata\AdminBundle\Controller\HelperController->appendFormFieldElementAction(object(Request))
at call_user_func_array(array(object(HelperController), 'appendFormFieldElementAction'), array(object(Request)))
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php:153)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php:68)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php:171)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(web/app_dev.php:28)
The exception goes away when I will uncomment //$formMapper->add('module', 'sonata_type_model_hidden'); and //$formMapper->add('unit', 'sonata_type_model_hidden');, but then whatever I try to do, references to parents in children entities are always null, using prePersist and preUpdate doesn't work.
I'm too new to Sonata to recognize it as a bug as I might be missing something, could anyone help me to sort this out? What am I missing here?
sonata-project/admin-bundle 3.21.0 3.22.0 The missing Symfony Admin Generator
sonata-project/block-bundle 3.3.2 3.3.2 Symfony SonataBlockBundle
sonata-project/cache 1.0.7 1.0.7 Cache library
sonata-project/core-bundle 3.4.0 3.4.0 Symfony SonataCoreBundle
sonata-project/datagrid-bundle 2.2.1 2.2.1 Symfony SonataDatagridBundle
sonata-project/doctrine-orm-admin-bundle 3.1.6 3.1.6 Symfony Sonata / Integrate Doctrine ORM into the SonataAdminBundle
sonata-project/exporter 1.7.1 1.7.1 Lightweight Exporter library
symfony/monolog-bundle v3.1.0 v3.1.0 Symfony MonologBundle
symfony/phpunit-bridge v3.3.6 v3.3.6 Symfony PHPUnit Bridge
symfony/polyfill-apcu v1.4.0 v1.5.0 Symfony polyfill backporting apcu_* functions to lower PHP versions
symfony/polyfill-intl-icu v1.4.0 v1.5.0 Symfony polyfill for intl's ICU-related data and classes
symfony/polyfill-mbstring v1.4.0 v1.5.0 Symfony polyfill for the Mbstring extension
symfony/polyfill-php56 v1.4.0 v1.5.0 Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions
symfony/polyfill-php70 v1.4.0 v1.5.0 Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions
symfony/polyfill-util v1.4.0 v1.5.0 Symfony utilities for portability of PHP codes
symfony/security-acl v3.0.0 v3.0.0 Symfony Security Component - ACL (Access Control List)
symfony/swiftmailer-bundle v2.6.3 v3.0.3 Symfony SwiftmailerBundle
symfony/symfony v3.3.6 v3.3.6 The Symfony PHP framework
HP 7.1.2 (cli) (built: Mar 3 2017 22:56:29) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.1.2, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.5.1, Copyright (c) 2002-2017, by Derick Rethans
Oh, I think I got it, I changed $educational_file property of my EducationalFile entity into $educationalFile, then I changed $formMapper->add('educational_file', 'file'); into $formMapper->add('educationalFile', 'file'); in my EducationalFileAdmin and I think it helped, I also added to my OneToMany setters setting of the parent, so for example:
public function addFiles(EducationalFile $file)
{
$this->files->add($file);
}
Into:
public function addFiles(EducationalFile $file)
{
$file->setUnit($this);
$this->files->add($file);
}
I hope it will help someone.
Unfortunately it seems the problem still persists (Could not get element id from...). I have no idea why it disappeared before but it came back now. This is really strange.
Ok I got it, it appears that this is actually required:
$formMapper->add('module', 'sonata_type_model_hidden');
Without this line the above exception happens. I wish this exception would be more telling.
Most helpful comment
Ok I got it, it appears that this is actually required:
Without this line the above exception happens. I wish this exception would be more telling.