ExtBase: parent ID is not updated after saving child object - parent-child

I have a 1:n relation:
class Parent {
protected $title = '';
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Child>
* #lazy
*/
protected $allChildren = NULL;
public function addChild(\...\Child $child) {
$this->allChildren->attach($child);
}
}
class Child {
/**
* reference to the parent object
* #var Parent
*/
protected $parent = NULL;
public function getParent() {
if ($this->parent instanceof \TYPO3\CMS\Extbase\Persistence\LazyLoadingProxy) {
$this->parent->_loadRealInstance();
}
return $this->parent;
}
}
Normally, when children objects are read from the database (e.g. by findByUid), they have already reference to the parent object.
But if I create a new child object and immediately persist it - uid is fetched from the database and parent reference - not.
Here is the function in the parent class doing described above:
public function appendNewChild() {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
$child = $objectManager->get(\...\Child::class);
$this->addChild($child);
$objectManager->get(\...\ParentRepository::class)->update($this);
$objectManager->get(\...\PersistenceManager::class)->persistAll();
//at this point is child's 'uid' property updated. 'parent' - is not.
return $child;
}
I also tried
$child = $objectManager->get(\...\ChildRepository::class)->findByUid($child->getUid());
after calling persistAll() - with no effect. It looks like this object is buffered by ExtBase and not read again.
EDIT
Here is the child model:
<?php
namespace MyVendor\MyExt\Domain\Model;
class Child extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* ready
*
* #var boolean
*/
protected $ready = FALSE;
/**
* Object latest change timestamp
*
* #var int
*/
protected $tstamp = 0;
/**
* Parent object
*
* #var \MyVendor\MyExt\Domain\Model\Parent
*/
protected $parent = NULL;
/**
* Returns the ready
*
* #return boolean $ready
*/
public function getReady() {
return $this->ready;
}
/**
* Sets the ready
*
* #param boolean $ready
* #return void
*/
public function setReady($ready) {
$this->ready = $ready;
}
/**
* Returns object's latest change timestamp
*
* #return int
*/
public function getEvaluationDate() {
return $this->tstamp;
}
public function getParent() {
if ($this->parent instanceof \TYPO3\CMS\Extbase\Persistence\LazyLoadingProxy) {
$this->parent->_loadRealInstance();
}
return $this->parent;
}
public function startEvaluation() {
//some stuff
}
}
Here is the parent model:
<?php
namespace MyVendor\MyExt\Domain\Model;
class Parent extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* title
*
* #var string
* #validate NotEmpty
*/
protected $title = '';
/**
* children
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\MyExt\Domain\Model\Child>
* #cascade remove
* #lazy
*/
protected $children = NULL;
public function __construct() {
//Do not remove the next line: It would break the functionality
$this->initStorageObjects();
}
protected function initStorageObjects() {
$this->children = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Returns the title
*
* #return string $title
*/
public function getTitle() {
return $this->title;
}
/**
* Sets the title
*
* #param string $title
* #return void
*/
public function setTitle($title) {
$this->title = $title;
}
/**
* Adds a Child
*
* #param \MyVendor\MyExt\Domain\Model\Child $child
* #return void
*/
public function addChild(\MyVendor\MyExt\Domain\Model\Child $child) {
$this->children->attach($child);
}
/**
*
* #return \MyVendor\MyExt\Domain\Model\Child
*/
public function appendNewChild() {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
$child = $objectManager->get(\MyVendor\MyExt\Domain\Model\Child::class);
$this->addChild($child);
$objectManager->get(\MyVendor\MyExt\Domain\Repository\ParentRepository::class)->update($this);
$objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
//even here, after repeated reading, is parent property =null
$t = $objectManager->get(\MyVendor\MyExt\Domain\Repository\ChildRepository::class)->findByUid($child->getUid());
//if I read another child object - it is fetched correctly, with 'parent' filled
$t = $objectManager->get(\MyVendor\MyExt\Domain\Repository\ChildRepository::class)->findByUid(42);
return $child;
}
/**
* Removes a Child
*
* #param \MyVendor\MyExt\Domain\Model\Child $childToRemove
* #return void
*/
public function removeChild(\MyVendor\MyExt\Domain\Model\Child $childToRemove) {
$this->children->detach($childToRemove);
}
/**
* Returns the children
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\MyExt\Domain\Model\Child> $children
*/
public function getChildren() {
return $this->children;
}
/**
* Sets the children
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\MyVendor\MyExt\Domain\Model\Child> $children
* #return void
*/
public function setChildren(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $children) {
$this->children = $children;
}
}
Here is parent control:
<?php
namespace MyVendor\MyExt\Controller;
class ParentController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* parentRepository
*
* #var \MyVendor\MyExt\Domain\Repository\ParentRepository
* #inject
*/
protected $parentRepository = NULL;
/**
* action list
*
* #return void
*/
public function listAction() {
$parents = $this->parentRepository->findAll();
$this->view->assign('parents', $parents);
}
/**
* action show
*
* #param \MyVendor\MyExt\Domain\Model\Parent $parent
* #return void
*/
public function showAction(\MyVendor\MyExt\Domain\Model\Parent $parent) {
$this->view->assign('showObject', $parent);
}
/**
* action new
*
* #param \MyVendor\MyExt\Domain\Model\Parent $newParent
* #ignorevalidation $newParent
* #return void
*/
public function newAction(\MyVendor\MyExt\Domain\Model\Parent $newParent = NULL) {
$this->view->assign('newParent', $newParent);
}
/**
* action create
*
* #param \MyVendor\MyExt\Domain\Model\Parent $newParent
* #return void
*/
public function createAction(\MyVendor\MyExt\Domain\Model\Parent $newParent) {
$this->parentRepository->add($newParent);
$this->redirect('list');
}
/**
* action edit
*
* #param \MyVendor\MyExt\Domain\Model\Parent $parent
* #ignorevalidation $parent
* #return void
*/
public function editAction(\MyVendor\MyExt\Domain\Model\Parent $parent) {
$this->view->assign('parent', $parent);
}
/**
* action update
*
* #param \MyVendor\MyExt\Domain\Model\Parent $parent
* #return void
*/
public function updateAction(\MyVendor\MyExt\Domain\Model\Parent $parent) {
$this->parentRepository->update($parent);
$this->redirect('list');
}
/**
* Confirmation prompt for delete action
*
* #param \MyVendor\MyExt\Domain\Model\Parent $deleteObject
* #return void
*/
public function deleteConfirmAction(\MyVendor\MyExt\Domain\Model\Parent $deleteObject) {
$this->view->assign ( 'deleteObject', $deleteObject );
}
/**
* Delete classifier
*
* #param \MyVendor\MyExt\Domain\Model\Parent $parent
* #return void
*/
public function deleteAction(\MyVendor\MyExt\Domain\Model\Parent $deleteObject) {
$this->parentRepository->remove($deleteObject);
$this->redirect('list');
}
/**
* action evaluate
*
* #param \MyVendor\MyExt\Domain\Model\Parent $parent
* #return void
*/
public function evaluateAction(\MyVendor\MyExt\Domain\Model\Parent $parent) {
$parent->appendNewChild()->startEvaluation();
$this->redirect('list');
}
}
Here is child's TCA:
<?php
if (!defined ('TYPO3_MODE')) {
die ('Access denied.');
}
$GLOBALS['TCA']['tx_myext_domain_model_child'] = array(
'ctrl' => $GLOBALS['TCA']['tx_myext_domain_model_child']['ctrl'],
'interface' => array(
'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, ready, tstamp',
),
'types' => array(
'1' => array('showitem' => 'sys_language_uid;;;;1-1-1, l10n_parent, l10n_diffsource, ready, '),
),
'palettes' => array(
'1' => array('showitem' => ''),
),
'columns' => array(
'sys_language_uid' => array(
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.language',
'config' => array(
'type' => 'select',
'foreign_table' => 'sys_language',
'foreign_table_where' => 'ORDER BY sys_language.title',
'items' => array(
array('LLL:EXT:lang/locallang_general.xlf:LGL.allLanguages', -1),
array('LLL:EXT:lang/locallang_general.xlf:LGL.default_value', 0)
),
),
),
'l10n_parent' => array(
'displayCond' => 'FIELD:sys_language_uid:>:0',
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.l18n_parent',
'config' => array(
'type' => 'select',
'items' => array(
array('', 0),
),
'foreign_table' => 'tx_myext_domain_model_child',
'foreign_table_where' => 'AND tx_myext_domain_model_child.pid=###CURRENT_PID### AND tx_myext_domain_model_child.sys_language_uid IN (-1,0)',
),
),
'l10n_diffsource' => array(
'config' => array(
'type' => 'passthrough',
),
),
't3ver_label' => array(
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.versionLabel',
'config' => array(
'type' => 'input',
'size' => 30,
'max' => 255,
)
),
'ready' => array(
'exclude' => 0,
'label' => 'LLL:\...\<SomeReadyLabel>',
'config' => array(
'type' => 'check',
'default' => 0
)
),
'parent' => array(
'exclude' => 1,
'label' => 'LLL:\...\<SomeParentLabel>',
'config' => array(
'type' => 'select',
'minitems' => 1,
'maxitems' => 1,
'foreign_table' => 'tx_myext_domain_model_parent',
)
),
'tstamp' => array(
'exclude' => 1,
'label' => 'TimeStamp',
'config' => Array (
'type' => 'none',
'format' => 'date',
'eval' => 'date',
),
),
),
);
Here is parent's TCA:
<?php
if (!defined ('TYPO3_MODE')) {
die ('Access denied.');
}
$GLOBALS['TCA']['tx_myext_domain_model_parent'] = array(
'ctrl' => $GLOBALS['TCA']['tx_myext_domain_model_parent']['ctrl'],
'interface' => array(
'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, title, children',
),
'types' => array(
'1' => array('showitem' => 'sys_language_uid;;;;1-1-1, l10n_parent, l10n_diffsource, title, children, '),
),
'palettes' => array(
'1' => array('showitem' => ''),
),
'columns' => array(
'sys_language_uid' => array(
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.language',
'config' => array(
'type' => 'select',
'foreign_table' => 'sys_language',
'foreign_table_where' => 'ORDER BY sys_language.title',
'items' => array(
array('LLL:EXT:lang/locallang_general.xlf:LGL.allLanguages', -1),
array('LLL:EXT:lang/locallang_general.xlf:LGL.default_value', 0)
),
),
),
'l10n_parent' => array(
'displayCond' => 'FIELD:sys_language_uid:>:0',
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.l18n_parent',
'config' => array(
'type' => 'select',
'items' => array(
array('', 0),
),
'foreign_table' => 'tx_myext_domain_model_parent',
'foreign_table_where' => 'AND tx_myext_domain_model_parent.pid=###CURRENT_PID### AND tx_myext_domain_model_parent.sys_language_uid IN (-1,0)',
),
),
'l10n_diffsource' => array(
'config' => array(
'type' => 'passthrough',
),
),
't3ver_label' => array(
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.versionLabel',
'config' => array(
'type' => 'input',
'size' => 30,
'max' => 255,
)
),
'title' => array(
'exclude' => 0,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_parent.title',
'config' => array(
'type' => 'input',
'size' => 30,
'eval' => 'trim,required'
),
),
'children' => array(
'exclude' => 0,
'label' => 'LLL:\...\<SomeChildrenLabel>',
'config' => array(
'type' => 'inline',
'foreign_table' => 'tx_myext_domain_model_child',
'foreign_field' => 'parent',
'maxitems' => 9999,
'appearance' => array(
'collapseAll' => 0,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => 1,
'showPossibleLocalizationRecords' => 1,
'showAllLocalizationLink' => 1
),
),
),
),
);

Take care of TCA table configurations.
This should work.
class Parent {
/**
* #var string
*/
protected $title = '';
/**
* Init object storage
*/
public function __construct()
{
$this->initStorageObjects();
}
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Child>
* #lazy
*/
protected $allChildren = NULL;
/**
* Attach child
*
* #param \..\Child $child
* #return void
*/
public function addChild(\...\Child $child) {
$this->allChildren->attach($child);
}
/**
* returns allChildren
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\..\Child> allChildren
*/
public function getAllChildren() {
return $this->allChildren;
}
/**
* sets allChildren
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\..\Child> allChildren
* #return void
*/
public function setAllChildren(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $allChildren) {
$this->allChildren = $allChildren
}
/**
* #return void
*/
protected function initStorageObjects()
{
$this->allChildren = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
}
class Child {
/**
* reference to the parent object
* #var \..\Parent
*/
protected $parent = NULL;
/**
* returns parent
* #return \...\Parent
*/
public function getParent() {
return $this->parent;
}
}

Related

symfony - dropdown menu doesn't select the option

I made a page on symfony to administrate some products with a dropdown menu linked to a category table.
The categoryid is well saved on the product, but the dropdown never select the good option, I don't find why.
Here is the category part in my class Dproduct :
/**
* #ORM\ManyToOne(targetEntity=RCategory::class, inversedBy="products")
* #ORM\JoinColumn(name="categoryId", referencedColumnName="categoryId", onDelete="CASCADE")
*/
private $categoryId;
public function getCategoryId(): ?RCategory
{
return $this->categoryId;
}
public function setCategoryId(?RCategory $categoryId): self
{
$this->categoryId = $categoryId;
return $this;
}
here is my class RCategory:
/**
* RCategory
*
* #ORM\Table(name="r_category")
* #ORM\Entity
*/
class RCategory
{
/**
* #var int
*
* #ORM\Column(name="categoryId", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $categoryId;
/**
* #var string
*
* #ORM\Column(name="categoryLib", type="string", length=50, nullable=false)
*/
private $categoryLib;
/**
* #Gedmo\Slug(fields={"categoryLib"})
* #ORM\Column(type="string", length=128, unique=true)
*/
public function getCategoryId(): ?int
{
return $this->categoryId;
}
public function getCategoryLib(): ?string
{
return $this->categoryLib;
}
public function setCategoryLib(string $categoryLib): self
{
$this->categoryLib = $categoryLib;
return $this;
}
here is my buildForm function :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('categoryId', ChoiceType::class, [
'mapped' => false,
'choices' => $options['choiceCategory'],
'label' => 'Catégorie'
])
->add('productLib', TextType::class, [
'label' => 'Libellé',
])
->add('Enregister', SubmitType::class)
;
}
And my choiceCategory function :
public function choiceCategory(){
$choices = [];
$categories = $this->getCategory();
foreach ($categories as $categorie) {
$choices[$categorie->getCategorylib()] = $categorie->getCategoryid();
}
return $choices;
}
And finaly my controller :
$product = $this->getDoctrine()
->getRepository('App:DProduct')
->findOneBy(array('slug' => $slug));
...
$form = $this->createForm(productType::class, $product, array('choiceCategory'=>$categoryController->choiceCategory()));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
... Data is well saved ...
}
return $this->render(
'form/meuble.html.twig',
array(
'form' => $form->createView(),
'titlePage' => $titlePage,
'product' => $product
)
);
Does anyone see where is the problem ?
Thanks,
Best regards,
Matthieu
You are using an unmapped field, therefore it won't set any data based on the product you are editing.
You can set the data with de data option
$product = $builder->getData();
...
->add('categoryId', ChoiceType::class, [
'mapped' => false,
'choices' => $options['choiceCategory'],
'label' => 'Catégorie'
'data' => $product->getCategory(),
])
U might be able to use a mapped field and use the EntityType instead of the ChoiceType to build the choices. Notice the choice_label and query_builder options.

sonata: Dealing with sonata_type_model (one-to-many)

1- I have an Entity:
EmployeeMedicalService
/**
* #ORM\Entity
* #ORM\Table(name="employee_medical_file")
*/
class EmployeeMedicalService extends BaseEntity
{
//
// Some
// Fields
//
/**
* #Assert\NotBlank
* #ORM\ManyToOne(targetEntity="PersonnelBundle\Entity\Lookup\Lookup")
* #ORM\JoinColumn(name="medical_service_id", referencedColumnName="id")
*/
private $medicalService;
//
// getters
// & setters
//
2- Another Entity:
Lookup
/**
* #ORM\Entity
* #ORM\Table(name="lookup")
* #UniqueEntity(fields="name")
*/
class Lookup extends BaseEntity
{
// const ...
const TYPE_MEDICAL_SERVICE = 'medical_service';
// more constants ...
public function __construct($type)
{
$this->type = $type;
}
//
// Some Fields
//
/**
* #var string
* --stuff--
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=50)
* #Assert\NotBlank
*/
private $type;
//getters
// &setters
Now in the
EmployeeMedicalServiceAdmin
protected function configureFormFields(\Sonata\AdminBundle\Form\FormMapper $formMapper)
{
$msquery = $this->getModelManager()
->getEntityManager('PersonnelBundle:Lookup\Lookup')
->createQueryBuilder();
$msquery->select('l')->from('PersonnelBundle:Lookup\Lookup', 'l')->where('l.type = :type')
->orderBy('l.name', 'ASC')
->setParameter('type', 'medical_service');
$formMapper
->add(..)
->add('medicalService', 'sonata_type_model', array(
'label' => 'personnel.employee.medical_service.form.medical_service',
'property' => 'name',
'placeholder' => '',
'required' => false,
'query' => $msquery,
))
->add(..)
;
}
** My Problem: **
I need the form for add new lookup(medical service) from inside the EmployeeMedicalService Admin Form to be preloaded with the field Type value set to 'medical_service' When I attempt to add a new Medical Service from inside the EmployeeMedicalService Admin Form or else a new lookup is added without the value if Type set to NULL
This is the
LookupAdmin
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name', 'text', array(
'label' => 'personnel.lookup.form.name'
))
->add('type', 'hidden', array(
'label' => 'personnel.lookup.form.type',
))
;
}
If you inspect ajax request for the popup form you will notice extra query parameters, such as pcode. You could check if this parameter exists and equals EmployeeMedicalServiceAdmin admin class code then set lookup type to medical_service.
UPDATE
Add this king of logic in the getNewInstance() method:
public function getNewInstance()
{
$type = isset($_GET['pcode']) ? 'medical_service' : '';
$instance = new \PersonnelBundle\Entity\Employee\EmployeeMedicalService($type);
return $object;
}

How can i remove a form item which is a oneToMany relation

I have two entities. Container and Schooltype. The entity container have a "oneToMany" relation to entity Schooltype.
Entity Container:
/**
* #ORM\OneToMany(targetEntity="App\MyBundle\Entity\SchoolType", mappedBy="container", cascade={"persist", "remove"})
*/
protected $schooltype;
Entity Schooltype:
/**
* #ORM\ManyToOne(targetEntity="App\MyBundle\Entity\Container", inversedBy="schooltype")
* #ORM\JoinColumn(name="container_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $container;
Now i create a form for container, so i can add one or many schooltypes. In my entity Container i modify the "removeSchooltype" method, it's look like.
Entity Container, remove method for schooltype:
public function removeSchooltype(\App\MyBundle\Entity\SchoolType $schooltype)
{
$this->schooltype->removeElement($schooltype);
$schooltype->setContainer(null);
}
Form ContainerType:
->add('schooltype', 'entity', array(
'class' => 'AppMyBundle:Schooltype',
'choices' => $schoolTypes,
'label' => 'msg.schoolType',
'translation_domain' => 'messages',
'multiple' => true,
'expanded' => false)
)
I try to handle the store process in my controller.
Container controller, edit method:
$object = new Container();
// Exists any object?
if (!$object) {
$this->get('session')->getFlashBag()->add('danger', $this->get('translator')->trans('notfound'));
return $this->redirect($this->generateUrl('app_container_list'));
}
$form = $this->createForm($this->get('form.type.container'), $object)->add('save', 'submit', array('label' => 'save', 'translation_domain' => 'messages', 'attr' => array('class' => 'btn btn-primary')));
$form->handleRequest($request);
// Check if form isValid
if ($form->isValid()) {
// Store object
$em = $this->getDoctrine()->getManager();
$em->persist($object);
// Flush statements
$em->flush();
$em->clear();
$this->get('session')->getFlashBag()->add('info', $this->get('translator')->trans('objectEdited', array()));
return $this->redirect($this->generateUrl('app_container_list'));
}
return $this->render('AppMyBundle:Container:edit.html.twig', array("form" => $form->createView()));
Everything works fine, i can add one or many schooltypes in my container and this was saved successfull. But if i remove a schooltype from selectbox in form and post my form the relation between container and schooltype will not be removed, have someone a hint why this happens?
1 Country to N League. Example below shows you how things are done. Just apply to yours. If you want the full CRUD example for 1 to N relationships, it is here.
Country
class Country
{
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="League",
* mappedBy="country",
* cascade={"persist", "remove"}
* )
*/
protected $league;
public function __construct()
{
$this->league = new ArrayCollection();
}
public function addLeague(League $league)
{
$this->league[] = $league;
return $this;
}
public function removeLeague(League $league)
{
$this->league->removeElement($league);
}
public function getLeague()
{
return $this->league;
}
}
League
class League
{
/**
* #ORM\ManyToOne(
* targetEntity="Country",
* inversedBy="league"
* )
* #ORM\JoinColumn(
* name="country_id",
* referencedColumnName="id",
* onDelete="CASCADE",
* nullable=false
* )
*/
protected $country;
public function setCountry(Country $country)
{
$this->country = $country;
return $this;
}
public function getCountry()
{
return $this->country;
}
}
LeagueType
class LeagueType extends AbstractType
{
private $country;
public function __construct()
{
$this->country = [
'class' => 'FootballFrontendBundle:Country',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'required' => false,
'empty_value' => '',
'query_builder' => function (EntityRepository $repo)
{
return $repo->createQueryBuilder('c')->orderBy('c.name', 'ASC');
}
];
}
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add('whatever properties you have in your entitiy')
->add('country', 'entity', $this->country);
}
public function getName()
{
return 'league';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'Football\FrontendBundle\Entity\League']
);
}
}
Controller delete/remove method
/**
* Deletes country.
*
* #param int $id
*
* #Route("/delete/{id}", requirements={"id"="\d+"})
* #Method({"GET"})
*
* #return RedirectResponse|Response
* #throws LeagueException
*/
public function deleteAction($id)
{
try {
$em = $this->getDoctrine()->getEntityManager();
$repo = $em->getRepository('FootballFrontendBundle:League');
$league = $repo->findOneByIdAsObject($id);
if (!$league instanceof League) {
throw new LeagueException(sprintf('League read: league [%s] cannot be found.', $id));
}
$em->remove($league);
$em->flush();
} catch (DBALException $e) {
$message = sprintf('DBALException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (ORMException $e) {
$message = sprintf('ORMException [%s]: %s', $e->getCode(), $e->getMessage());
} catch (Exception $e) {
$message = sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage());
}
if (isset($message)) {
throw new LeagueException($message);
}
return $this->redirect($this->generateUrl('where ever you want'));
}

Add a query/filter to sonata_type_collection

I have an entity "Product" which has a OTM relation with "ProductVariant".
I would like to be able to generate a sonata_type_collection wherein only the ProductVariants with "isMaster = false" are shown.
I can't use the query_builder in the sonata_type_collection. Is there another way of manipulating the generated list and still be able to insert new ProductVariants into the Product entity?
My entities:
/**
* Product
*
* #ORM\Table(name="product")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
* #ORM\Entity
*/
class Product
{
/**
* #var ArrayCollection $variants
* #ORM\OneToMany(targetEntity="My\Bundle\ProductVariant", mappedBy="product", cascade={"all"}, orphanRemoval=true)
*/
private $variants;
}
/**
* ProductVariant
*
* #ORM\Table(name="productVariant")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
* #ORM\Entity
*/
class ProductVariant
{
/**
* #var Product
* #ORM\ManyToOne(targetEntity="My\Bundle\Product", inversedBy="variants")
*/
private $product;
}
The ProductAdmin:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('variants', 'sonata_type_collection',
array(
'label' => 'A nice label',
'btn_add' => false, // Because adding won't work with the softdeletable variants
'type_options' => array(
'delete' => true,
)
),
array(
'edit' => 'inline',
'inline' => 'table',
'delete' => 'inline'
)
)
;
}
You could create different getters and setters for the different types of ProductVariants.
My\Bundle\Product
public function getNonMasterVariants() {
return $this->variants->filter(function(My\Bundle\ProductVariant $item) {
return !$item->isMaster();
});
}
public function getMasterVariants() {
return $this->variants->filter(function(My\Bundle\ProductVariant $item) {
return $item->isMaster();
});
}
ProductAdmin
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('nonMasterVariants', 'sonata_type_collection',
array(
...
),
array(
...
)
)
;
}

Right way to handle relations between entities in forms

I have a table stock which have relation 1:m with a table called warranty. I made this entity for stock table:
<?php
namespace StockBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use CompanyBundle\Entity\Company;
use StockBundle\Entity\NStockStatus;
use ProductBundle\Entity\NLength;
use ProductBundle\Entity\NWeight;
/**
* #ORM\Table(name="stock")
* #ORM\Entity(repositoryClass="StockBundle\Entity\Repository\KStockRepository")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class KStock {
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Product", inversedBy="stocks" )
* #ORM\JoinColumn(name="product", referencedColumnName="upc")
*/
protected $product;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="CompanyBundle\Entity\Company", inversedBy="companyHasStock" )
* #ORM\JoinColumn(name="company", referencedColumnName="id")
*/
protected $company;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NCondition", inversedBy="stocks" )
* #ORM\JoinColumn(name="kcondition", referencedColumnName="id")
*/
protected $condition;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NStockStatus", inversedBy="stocks" )
* #ORM\JoinColumn(name="status", referencedColumnName="id")
*/
protected $status;
/**
*
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\Warranty", inversedBy="stocks" )
* #ORM\JoinColumn(name="warranty", referencedColumnName="id")
*/
// protected $warranty;
/**
* #ORM\Column(type="string", length=255, name="sku")
*/
protected $sku;
/**
* #ORM\Column(type="integer")
*/
protected $availability;
/**
* #ORM\Column(type="string", length=255)
*/
protected $description;
/**
*
* #ORM\Column(type="decimal", precision=19, scale=4)
*/
protected $price;
/**
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NUnit")
* #ORM\JoinColumn(name="unit", referencedColumnName="id")
*/
protected $unit;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $width;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $height;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $weight;
/**
*
* #ORM\Column(type="decimal", precision=4, scale=2)
*/
protected $length;
/**
*
* #ORM\Column(type="integer")
*/
protected $amount;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\NWeight")
* #ORM\JoinColumn(name="nweight", referencedColumnName="id")
*/
protected $nweight;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\NLength")
* #ORM\JoinColumn(name="nlength", referencedColumnName="id")
*/
protected $nlength;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
public function setProduct(\ProductBundle\Entity\Product $param) {
$this->product = $param;
}
public function getProduct() {
return $this->product;
}
public function setCompany(\CompanyBundle\Entity\Company $param) {
$this->company = $param;
}
public function getCompany() {
return $this->company;
}
public function setCondition(\StockBundle\Entity\NCondition $condition) {
$this->condition = $condition;
}
public function getCondition() {
return $this->condition;
}
public function setStatus(\StockBundle\Entity\NStockStatus $param) {
$this->status = $param;
}
public function getStatus() {
return $this->status;
}
public function setSku($param) {
$this->sku = $param;
}
public function getSku() {
return $this->sku;
}
public function setAvailability($param) {
$this->availability = $param;
}
public function getAvailability() {
return $this->availability;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setPrice($param) {
$this->price = $param;
}
public function getPrice() {
return $this->price;
}
public function setUnit(\StockBundle\Entity\NUnit $unit) {
$this->unit = $unit;
}
public function getUnit() {
return $this->unit;
}
public function setWidth($width) {
$this->width = $width;
}
public function getWidth() {
return $this->width;
}
public function setHeight($height) {
$this->height = $height;
}
public function getHeight() {
return $this->height;
}
public function setWeight($weight) {
$this->weight = $weight;
}
public function getWeight() {
return $this->weight;
}
public function setLength($length) {
$this->length = $length;
}
public function getLength() {
return $this->length;
}
public function setAmount($amount) {
$this->amount = $amount;
}
public function getAmount() {
return $this->amount;
}
public function setCreated($created) {
$this->created = $created;
}
public function getCreated() {
return $this->created;
}
public function setModified($modified) {
$this->modified = $modified;
}
public function getModified() {
return $this->modified;
}
public function getDeletedAt() {
return $this->deletedAt;
}
public function setDeletedAt($deletedAt) {
$this->deletedAt = $deletedAt;
}
public function setNWeight(\ProductBundle\Entity\NWeight $nweight) {
$this->nweight = $nweight;
}
public function getNWeight() {
return $this->nweight;
}
public function setNLength(\ProductBundle\Entity\NLength $nlength) {
$this->nlength = $nlength;
}
public function getNLength() {
return $this->nlength;
}
public function __toString() {
return $this->company . ' -- ' . $this->product;
}
}
And this entity for Warranty table:
<?php
namespace StockBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity
* #ORM\Table(name="warranty")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class Warranty {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Warranty")
* #ORM\JoinColumn(name="parent", referencedColumnName="id")
*/
protected $parent;
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Product")
* #ORM\JoinColumn(name="product", referencedColumnName="upc")
*/
protected $product;
/**
* #ORM\ManyToOne(targetEntity="StockBundle\Entity\NCondition")
* #ORM\JoinColumn(name="kcondition", referencedColumnName="id")
*/
protected $kcondition;
/**
* #ORM\ManyToOne(targetEntity="CompanyBundle\Entity\Company")
* #ORM\JoinColumn(name="company", referencedColumnName="id")
*/
protected $company;
/**
*
* #ORM\Column(type="date")
*/
protected $valid_time;
/**
*
* #ORM\Column(type="text")
*/
protected $description;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
*/
protected $created;
/**
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="modified", type="datetime")
*/
protected $modified;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
/**
* #ORM\OneToMany(targetEntity="StockBundle\Entity\KStock")
*/
protected $stocks;
public function getId() {
return $this->id;
}
public function setParent(Warranty $parent = null) {
$this->parent = $parent;
}
public function getParent() {
return $this->parent;
}
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setCreated($created) {
$this->created = $created;
}
public function getCreated() {
return $this->created;
}
public function setModified($modified) {
$this->modified = $modified;
}
public function getModified() {
return $this->modified;
}
public function getDeletedAt() {
return $this->deletedAt;
}
public function setDeletedAt($deletedAt) {
$this->deletedAt = $deletedAt;
}
public function setProduct(\ProductBundle\Entity\Product $product) {
$this->product = $product;
}
public function getProduct() {
return $this->product;
}
public function setCompany(\CompanyBundle\Entity\Company $company) {
$this->company = $company;
}
public function getCompany() {
return $this->company;
}
public function setKCondition(\StockBundle\Entity\NCondition $condition) {
$this->kcondition = $condition;
}
public function getKCondition() {
return $this->kcondition;
}
public function setValidTime($valid_time) {
$this->valid_time = $valid_time;
}
public function getValidTime() {
return $this->valid_time;
}
}
Then I create my KStockType.php with this code:
<?php
namespace StockBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use CatalogBundle\Form\KCatalogType;
class KStockType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('sku', 'text', array('required' => true, 'label' => 'SKU (Número de Referencia)'))
->add('price', 'money', array('label' => 'Precio', 'currency' => 'VEF'))
->add('unit', 'entity', array('label' => 'Moneda', 'class' => 'StockBundle:NUnit', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('amount', 'integer', array('label' => 'Cantidad'))
->add('status', 'entity', array('label' => 'Estado', 'class' => 'StockBundle:NStockStatus', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('condition', 'entity', array('label' => 'Condición del producto', 'class' => 'ProductBundle:NCondition', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('width', 'integer', array('required' => true, 'label' => 'Ancho'))
->add('height', 'integer', array('required' => true, 'label' => 'Alto'))
->add('length', 'integer', array('required' => true, 'label' => 'Largo'))
->add('nlength', 'entity', array('label' => 'Unidad de Medida', 'class' => 'ProductBundle:NLength', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('weight', 'integer', array('required' => true, 'label' => 'Peso'))
->add('nweight', 'entity', array('label' => 'Unidad de Peso', 'class' => 'ProductBundle:NWeight', 'property' => 'name', 'required' => true, 'multiple' => false, 'expanded' => false))
->add('description', 'textarea', array('label' => 'Descripción'))
->add('start_date', 'date', array('label' => 'Fecha (Inicio de la Publicación)', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker')))
->add('warranty', 'textarea', array('required' => true, 'label' => 'Condiciones de Garantía'))
->add('valid_time', 'date', array('label' => 'Tiempo de Validez de la Garantía', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker')));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'StockBundle\Entity\KStock'
));
}
/**
* #return string
*/
public function getName() {
return 'stockbundle_kstock';
}
}
As you may notice I have some fields on that form that belongs to warranty. Then in my controller I have this method for edit action:
/**
* Stock
*
* #Route("/edit/{company_id}/{product_id}", name="stock_edit")
* #Method("GET")
*/
public function editAction($company_id, $product_id) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('StockBundle:KStock')->findOneBy(array('company' => $company_id, 'product' => $product_id));
$response = array();
$response["response"] = true;
if (!$entity) {
$response['response'] = FALSE;
$response['message'] = 'Unable to find Stock entity.';
return new JsonResponse($response);
}
$product = $em->getRepository('ProductBundle:Product')->find($product_id);
$company = $em->getRepository('CompanyBundle:Company')->find($company_id);
if (!$product || !$company) {
$response['response'] = FALSE;
$response['message'] = 'Error not found product or company';
return new JsonResponse($response);
}
$editForm = $this->createForm(new KStockType(), $entity);
return $this->render("StockBundle:Stock:edit.html.twig", array('entity' => $entity, 'edit_form' => $editForm->createView()));
}
But when I call it I got this error:
Neither the property "start_date" nor one of the methods
"getStartDate()", "isStartDate()", "hasStartDate()", "_get()" or
"_call()" exist and have public access in class
"StockBundle\Entity\KStock".
What I am missing?
Take a look at this page: Symfony documentation
A little brief from that:
In more complex examples, you can embed entire forms, which is useful when
creating forms that expose one-to-many relationships
I think you have two possible aproaches:
1 - Look how to make forms with relational entities
2 - Make a form without a class and manage all information in your controller ( I wouldn't use this)
Try to replace your line about start_date in your form by:
->add('start_date', 'date', array('label' => 'Fecha (Inicio de la Publicación)', 'widget' => 'single_text', 'format' => 'yyyy-MM-dd', 'attr' => array('class' => 'dpicker'), 'mapped' => false))

Resources