Pavel Vasiluk
Pavel Vasiluk

Reputation: 317

Symfony2 Doctrine ORM: id field remains NULL

I am using CollectionType Field in my Symfony project, to be able to generate as many similar form items as user needs. The problem I have appears while form is being submitted - it passes all the values directly to db, but the foreign key id (household_app_id) remains null, even though relations seem to be correct. What can I do to prevent this bug? And why is this happening?

My project code is below:

Both Entities:

<?php //HouseholdApp.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * HouseholdApp
 *
 * @ORM\Table(name="household_application")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\HouseholdAppRepository")
 * @Vich\Uploadable
 *
 */
class HouseholdApp
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * One Application has One Household.
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\Household", inversedBy="householdApp")
     * @ORM\JoinColumn(name="household_id", referencedColumnName="id")
     */
    private $household;


    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="union_register", fileNameProperty="unionRegisterName")
     */
    private $unionRegister;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="apt_owners_decision", fileNameProperty="aptOwnersDecisionName")
     */
    private $aptOwnersDecision;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="mulaptinventory", fileNameProperty="multiAptInventoryName")
     */
    private $multiAptInventory;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="buildtechsurvey", fileNameProperty="buildingTechnicalSurveyName")
     */
    private $buildingTechnicalSurvey;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="defectact", fileNameProperty="defectActName")
     */
    private $defectAct;

    /**
     * @var File
     *
     * @Vich\UploadableField(mapping="activitycost", fileNameProperty="activityCostName")
     */
    private $activityCost;

    /**
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\DefectAct", mappedBy="householdApp", cascade={"persist"}, orphanRemoval=true)
     */
    protected $actOfDefects;

    /**
     * Set unionRegister
     *
     * @param File|UploadedFile $unionRegister
     *
     * @return HouseholdApp
     */
    public function setUnionRegister(File $unionRegister = null)
    {
        $this->unionRegister = $unionRegister;
        if ($unionRegister) {
            // 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->uUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get unionRegister
     *
     * @return File|UploadedFile
     */
    public function getUnionRegister()
    {
        return $this->unionRegister;
    }


    /**
     * Set aptOwnersDecision
     *
     * @param File|UploadedFile $aptOwnersDecision
     *
     * @return HouseholdApp
     */
    public function setAptOwnersDecision(File $aptOwnersDecision = null)
    {
        $this->aptOwnersDecision = $aptOwnersDecision;
        if ($aptOwnersDecision) {
            // 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->aUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get aptOwnersDecision
     *
     * @return File|UploadedFile
     */
    public function getAptOwnersDecision()
    {
        return $this->aptOwnersDecision;
    }


    /**
     * Set multiAptInventory
     *
     * @param File|UploadedFile $multiAptInventory
     *
     * @return HouseholdApp
     */
    public function setMultiAptInventory(File $multiAptInventory = null)
    {
        $this->multiAptInventory = $multiAptInventory;
        if ($multiAptInventory) {
            // 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->mUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get multiAptInventory
     *
     * @return File|UploadedFile
     */
    public function getMultiAptInventory()
    {
        return $this->multiAptInventory;
    }


    /**
     * Set buildingTechnicalSurvey
     *
     * @param File|UploadedFile $buildingTechnicalSurvey
     *
     * @return HouseholdApp
     */
    public function setBuildingTechnicalSurvey(File $buildingTechnicalSurvey = null)
    {
        $this->buildingTechnicalSurvey = $buildingTechnicalSurvey;
        if ($buildingTechnicalSurvey) {
            // 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->bUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get buildingTechnicalSurvey
     *
     * @return File|UploadedFile
     */
    public function getBuildingTechnicalSurvey()
    {
        return $this->buildingTechnicalSurvey;
    }


    /**
     * Set defectAct
     *
     * @param File|UploadedFile $defectAct
     *
     * @return HouseholdApp
     */
    public function setDefectAct(File $defectAct = null)
    {
        $this->defectAct = $defectAct;
        if ($defectAct) {
            // 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->dUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get defectAct
     *
     * @return File|UploadedFile
     */
    public function getDefectAct()
    {
        return $this->defectAct;
    }


    /**
     * Set activityCost
     *
     * @param File|UploadedFile $activityCost
     *
     * @return HouseholdApp
     */
    public function setActivityCost(File $activityCost = null)
    {
        $this->activityCost = $activityCost;
        if ($activityCost) {
            // 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->acUpdatedAt = new \DateTime('now');
        }

        return $this;
    }

    /**
     * Get activityCost
     *
     * @return File|UploadedFile
     */
    public function getActivityCost()
    {
        return $this->activityCost;
    }
}

<?php //DefectAct.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * DefectAct
 *
 * @ORM\Table(name="defect_act")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\DefectActRepository")
 *
 */
class DefectAct
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $defectWork;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private  $defectDescription;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $preventionDeadline;

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\HouseholdApp", inversedBy="actOfDefects")
     */
    private $householdApp;



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

    /**
     * Set defectWork
     *
     * @param string $defectWork
     *
     * @return DefectAct
     */
    public function setDefectWork($defectWork)
    {
        $this->defectWork = $defectWork;

        return $this;
    }

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

    /**
     * Set defectDescription
     *
     * @param string $defectDescription
     *
     * @return DefectAct
     */
    public function setDefectDescription($defectDescription)
    {
        $this->defectDescription = $defectDescription;

        return $this;
    }

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

    /**
     * Set preventionDeadline
     *
     * @param string $preventionDeadline
     *
     * @return DefectAct
     */
    public function setPreventionDeadline($preventionDeadline)
    {
        $this->preventionDeadline = $preventionDeadline;

        return $this;
    }

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

    /**
     * Set householdApp
     *
     * @param \AppBundle\Entity\HouseholdApp $householdApp
     *
     * @return DefectAct
     */
    public function setHouseholdApp(\AppBundle\Entity\HouseholdApp $householdApp = null)
    {
        $this->householdApp = $householdApp;

        return $this;
    }

    /**
     * Get householdApp
     *
     * @return \AppBundle\Entity\HouseholdApp
     */
    public function getHouseholdApp()
    {
        return $this->householdApp;
    }
}

Controller:

/**
 * @Security("has_role('ROLE_HOUSEHOLD')")
 * @param \Symfony\Component\HttpFoundation\Request $request
 * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
 */
public function householdApplicationAction(Request $request) {
    $application = $this->getUser()->getRhousehold()->getHouseholdApp();

    $flag = true;
    if(is_null($application)) {
        $flag = false;
    }

    $form = $this->createForm(HouseholdAppType::class, $application, ['disabled' => false]);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $appData = $form->getData();
        $em = $this->get("doctrine.orm.default_entity_manager");
        $household = $this->getUser()->getRhousehold();

        $appData->setHousehold($household);

        $em->persist($appData);
        $em->flush();
        return $this->redirectToRoute("householder_app");
    }

    return $this->render("@App/household_application.html.twig",
        ['form' => $form->createView(), 'householdapp' => $application]);
}

Forms:

<?php //HouseholdAppType

namespace AppBundle\Form;

use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class HouseholdAppType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('multiapthouseaddress', TextType::class, ['label' => 'form.multiapthouseaddress'])
            ->add('cadastralnumberofbuilding', TextType::class, ['label' => 'form.cadastralnumberofbuilding'])
            ->add('totalbuildingarea', TextType::class, ['label' => 'form.totalbuildingarea'])
            ->add('house_constructive_solution', TextType::class, ['label' => 'form.house_constructive_solution'])
            ->add('exploitation_start_year', IntegerType::class, ['label' => 'form.exploitation_start_year'])
            ->add('non_residential_area', TextType::class, ['label' => 'form.non_residential_area'])
            ->add('has_heat_supply_system', CheckboxType::class, ['label' => 'form.has_heat_supply_system'])
            ->add('apts_with_individual_heating', TextType::class, ['label' => 'form.apts_with_individual_heating'])
            ->add('authorized_person_data', TextType::class, ['label' => 'form.authorized_person_data'])
            ->add('is_vatpayer', CheckboxType::class, ['label' => 'form.is_vatpayer'])
            ->add('personal_code', TextType::class, ['label' => 'form.personal_code'])
            ->add('declared_address', TextType::class, ['label' => 'form.declared_address'])
            ->add('contact_person_data', TextType::class, ['label' => 'form.contact_person_data'])
            ->add('unionRegister', VichFileType::class, ['required' => false, 'label' => 'form.unionRegister'])
            ->add('aptOwnersDecision', VichFileType::class, ['required' => false, 'label' => 'form.aptOwnersDecision'])
            ->add('multiAptInventory', VichFileType::class, ['required' => false, 'label' => 'form.multiAptInventory'])
            ->add('buildingTechnicalSurvey', VichFileType::class, ['required' => false, 'label' => 'form.buildingTechnicalSurvey'])
            ->add('defectAct', VichFileType::class, ['required' => false, 'label' => 'form.defectAct'])
            ->add('activityCost', VichFileType::class, ['required' => false, 'label' => 'form.activityCost'])
            ->add('actOfDefects', CollectionType::class, [
                'label' => true,
                'entry_type' => DefectsActCollectionType::class,
                'allow_add' => true,
                'prototype' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'delete_empty' => true,
                'required' => false
            ])
            ->add('save', SubmitType::class, [
                'label' => 'form.save',
                'attr' => [
                    'class' => 'btn2'
                ]]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => HouseholdApp::class,
            'translation_domain' => 'main',
        ));
    }
}

<?php //DefectActCollectionType

namespace AppBundle\Form;

use AppBundle\Entity\DefectAct;
use AppBundle\Entity\HouseholdApp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class DefectsActCollectionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('defectWork', TextType::class)
            ->add('defectDescription', TextType::class)
            ->add('preventionDeadline', TextType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults([
                'translation_domain' => 'main',
                'data_class' => DefectAct::class
            ]);
    }

    public function getBlockPrefix()
    {
        return 'app_bundle_defects_act_collection_type';
    }
}

Template:

{% extends '@App/templates/base.html.twig' %}
{% trans_default_domain "main" %}
{% block main %}
    {% include '@App/templates/progressbar.html.twig' %}
    {% include '@App/templates/tabs.html.twig' %}
    {% include '@App/h_apply_menu.html.twig' %}

    <section id='cabinet_household_app'>
        <div class='container'>
            <div class="tab-content">
                <div id="h-apply" class="tab-pane fade in active form_page">
                {{ form_start(form)}}
                {{ form_row(form._token) }}
                <h3>{{ 'form.house_data'|trans }}</h3>
                <div class='field'><div class="form-group"> {{ form_row(form.multiapthouseaddress)}}</div></div>
                <div class='field'><div class="form-group"> {{ form_row(form.cadastralnumberofbuilding)}}</div></div>
                <div class='field'><div class="form-group"> {{ form_row(form.totalbuildingarea)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.house_constructive_solution)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.exploitation_start_year)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.non_residential_area)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.has_heat_supply_system, {'required': false})}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.apts_with_individual_heating)}}</div></div>
                <h3>{{ 'form.app_applying_person'|trans }}</h3>
                <div class='field'><div class="form-group">{{ form_row(form.authorized_person_data)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.is_vatpayer, {'required': false})}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.personal_code)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.declared_address)}}</div></div>
                <div class='field'><div class="form-group">{{ form_row(form.contact_person_data)}}</div></div>

                <h3>{{ 'form.apply_documents'|trans }}</h3>
                <section id='cabinet_household_inner_app'>
                {% if householdapp is null %}
                    <h4>{{ 'form.unionRegister'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.unionRegister) }}</div></div>

                    <h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.aptOwnersDecision) }}</div></div>

                    <h4>{{ 'form.multiAptInventory'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.multiAptInventory) }}</div></div>

                    <h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.buildingTechnicalSurvey) }}</div></div>

                    <h4>{{ 'form.defectAct'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.defectAct) }}</div></div>

                    <h4>{{ 'form.activityCost'|trans }}</h4>
                    <div class='field'><div class="form-group">{{ form_widget(form.activityCost) }}</div></div>
                {% else %}
                    <h4>{{ 'form.unionRegister'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'unionRegister') }}" class="btn2">Download</a>
                    <h4>{{ 'form.aptOwnersDecision'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'aptOwnersDecision') }}" class="btn2">Download</a>
                    <h4>{{ 'form.multiAptInventory'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'multiAptInventory') }}" class="btn2">Download</a>
                    <h4>{{ 'form.buildingTechnicalSurvey'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'buildingTechnicalSurvey') }}" class="btn2">Download</a>
                    <h4>{{ 'form.defectAct'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'defectAct') }}" class="btn2">Download</a>
                    <h4>{{ 'form.activityCost'|trans }}</h4>
                    <a href="{{ vich_uploader_asset(householdapp, 'activityCost') }}" class="btn2">Download</a>
                {% endif %}

                    <div class="container center" data-prototype="{{ form_widget(form.actOfDefects.vars.prototype)|e('html_attr') }}">
                        {% for actOfDefect in form.actOfDefects %}
                            <div class="row document">{{ form_row(actOfDefect) }}</div>
                        {% endfor %}
                        <div class="row">
                            <div class="col-xs-12 center">
                                <div id="add" class="btn2">{{ "common.addFile"|trans }}</div>
                            </div>
                        </div>
                    </div>
                </section>

                <div class="center">{{ form_row(form.save) }}</div>
                    {{ form_end(form, {'render_rest': false}) }}
                </div>

            </div>
        </div>
    </section>
{% endblock main %}

{% block js_bottom %}
    <script>
        var $collectionHolder;

        $(document).ready(function () {
            // Get the ul that holds the collection of tags
            $collectionHolder = $('div.container.center');

            // add a delete link to all of the existing tag form li elements
            $collectionHolder.find('li').each(function() {
                addTagFormDeleteLink($(this));
            });

            // add the "add a tag" anchor and li to the tags ul
            //$collectionHolder.append($newLinkLi);

            // count the current form inputs we have (e.g. 2), use that as the new
            // index when inserting a new item (e.g. 2)
            $collectionHolder.data('index', $collectionHolder.find(':input').length);

            $('#add').on('click', function (e) {
                // prevent the link from creating a "#" on the URL
                e.preventDefault();
                addTagForm($collectionHolder);
            });
        });

        function addTagFormDeleteLink($tagFormLi) {
            var $removeFormA = $('<a href="#">{{ "common.cancel"|trans }}</a>');
            $tagFormLi.append($removeFormA);

            $removeFormA.on('click', function(e) {
                // prevent the link from creating a "#" on the URL
                e.preventDefault();

                // remove the li for the tag form
                $tagFormLi.remove();
            });
        }

        function addTagForm($collectionHolder) {
            var prototype = $collectionHolder.data('prototype');

            // get the new index
            var index = $collectionHolder.data('index');

            // Replace '__name__' in the prototype's HTML to
            // instead be a number based on how many items we have
            var newForm = prototype.replace(/__name__/g, index);

            // increase the index with one for the next item
            $collectionHolder.data('index', index + 1);

            // Display the form in the page in an li, before the "Add a tag" link li
            var $newFormBlock = $('<div class="row document"></div>').append(newForm);
            $collectionHolder.append($newFormBlock);
            addTagFormDeleteLink($newFormBlock);
        }
    </script>
{% endblock %}

Upvotes: 1

Views: 1098

Answers (1)

Jibato
Jibato

Reputation: 493

In your class HouseholdApp, you must have :

public function addActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
    $this->actOfDefects[] = $actOfDefect;
    $actOfDefect->setHouseholdApp($this);

    return $this;
}

public function removeActOfDefects(\AppBundle\Entity\DefectAct $actOfDefect)
{
    $this->actOfDefects->removeElement($actOfDefect);
}

Upvotes: 2

Related Questions