Update one entity after entering data into another

Asked

Viewed 421 times

0

I urgently need to find some way to resolve this problem that has trapped me for over a week. Once solified, I believe I can use this medium to do other operations that my system will have.

I have two tables, a call from Client and another of Budget, that are related 1:n and n:1 respectively. It works like this: in the form Budget has a field select listing all the Client, and which it assigns to the value of each option the id of each Client:

<div class="form-group">
    <select name="cdg_budget_type[client_id]" class="form-control">
        {% for client in clients %}
            <option value="{{ client.id }}">{{ client.name }}</option>
        {% endfor %}
    </select>
</div>

First, I must show the entity Client:

class Client
{
    private $id;

    private $name;

    private $phone;

    private $email;

    private $streetName;

    private $district;

    private $number;

    private $city;

    private $zipCode;

    private $budget;

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

    public function getId()
    {
        return $this->id;
    }

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

        return $this;
    }

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

    public function setPhone($phone)
    {
        $this->phone = $phone;

        return $this;
    }

    public function getPhone()
    {
        return $this->phone;
    }

    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setStreetName($streetName)
    {
        $this->streetName = $streetName;

        return $this;
    }

    public function getStreetName()
    {
        return $this->streetName;
    }

    public function setDistrict($district)
    {
        $this->district = $district;

        return $this;
    }

    public function getDistrict()
    {
        return $this->district;
    }

    public function setNumber($number)
    {
        $this->number = $number;

        return $this;
    }

    public function getNumber()
    {
        return $this->number;
    }

    public function setCity($city)
    {
        $this->city = $city;

        return $this;
    }

    public function getCity()
    {
        return $this->city;
    }

    public function setZipCode($zipCode)
    {
        $this->zipCode = $zipCode;

        return $this;
    }

    public function getZipCode()
    {
        return $this->zipCode;
    }

    function setBudget($budget)
    {
        $this->budget = $budget;
    }

    function getBudget()
    {
        return $this->budget;
    }
}

Now, the entity Budget:

class Budget
{
    private $id;

    private $clientId;

    private $address;

    private $installments;

    private $checkDays;

    private $dateStart;

    private $dateCreated;

    private $totalValue;

    public function __construct()
    {
        $this->dateCreated = new \DateTime();
    }

    public function getId()
    {
        return $this->id;
    }

    public function setClientId(Client $clientId)
    {
        $this->clientId = $clientId;

        return $this;
    }

    public function getClientId()
    {
        return $this->clientId;
    }

    public function setAddress($address)
    {
        $this->address = $address;

        return $this;
    }

    public function getAddress()
    {
        return $this->address;
    }

    public function setInstallments($installments)
    {
        $this->installments = $installments;

        return $this;
    }

    public function getInstallments()
    {
        return $this->installments;
    }

    public function setCheckDays($checkDays)
    {
        $this->checkDays = $checkDays;

        return $this;
    }

    public function getCheckDays()
    {
        return $this->checkDays;
    }

    public function setDateStart($dateStart)
    {
        $this->dateStart = $dateStart;

        return $this;
    }

    public function getDateStart()
    {
        return $this->dateStart;
    }

    public function setDateCreated($dateCreated)
    {
        $this->dateCreated = $dateCreated;

        return $this;
    }

    public function getDateCreated()
    {
        return $this->dateCreated;
    }

    public function setTotalValue($totalValue)
    {
        $this->totalValue = $totalValue;

        return $this;
    }

    public function getTotalValue()
    {
        return $this->totalValue;
    }
}

Following then with the file Client.orm.yml, which lists all the fields it contains in this table and the relationship with Budget:

CDG\PanelBundle\Entity\Client:
    type: entity
    table: client
    repositoryClass: CDG\PanelBundle\Entity\ClientRepository
    id:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
    fields:
        name:
            type: string
            length: 255
        phone:
            type: string
            length: 255
        email:
            type: string
            length: 255
        streetName:
            type: string
            length: 255
            column: street_name
        district:
            type: string
            length: 255
        number:
            type: string
            length: 255
        city:
            type: string
            length: 255
        zipCode:
            type: string
            length: 255
            column: zip_code
    oneToMany:
        budget:
            targetEntity: Budget
            mappedBy: clientId
    lifecycleCallbacks: {  }

Here, the Budget.orm.yml and its relationship with Client:

CDG\PanelBundle\Entity\Budget:
    type: entity
    table: budget
    repositoryClass: CDG\PanelBundle\Entity\BudgetRepository
    id:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
    fields:
        address:
            type: string
            length: 255
        installments:
            type: integer
        checkDays:
            type: integer
            column: check_days
        dateStart:
            type: datetime
            column: date_start
        dateCreated:
            type: datetime
            column: date_created
        totalValue:
            type: decimal
            column: total_value
            nullable: true
    manyToOne:
        clientId:
            targetEntity: Client
            inversedBy: budget
            joinColumn:
                name: client_id
                referencedColumnName: id
    lifecycleCallbacks: {  }

So far I believe everything is fine, but now the problem begins. It is necessary to UPDATE the field budget of Client previously selected in the form as soon as a new Budget is inserted in the database.

In my BudgetController.php, the function addAction() is this way below, and produces the following error:

Warning: spl_object_hash() expects Parameter 1 to be Object, array Given

public function addAction(Request $request)
{
    $form = $this->createForm(new BudgetType());
    $manager = $this->getDoctrine()->getManager();
    $Client = $manager->getRepository('PanelBundle:Client');
    $Budget = $manager->getRepository('PanelBundle:Budget');

    if ($request->getMethod() == 'POST') {
        $form->handleRequest($request);

        if ($form->isValid()) {
            $manager->persist($form->getData());
            $manager->flush();

            $ClientEntity = $manager->find('PanelBundle:Client', $form['client_id']->getData()->getId());
            $ClientEntity->setBudget($manager->getRepository('PanelBundle:Budget')->getLastId());

            $manager->persist($ClientEntity);
            $manager->flush();

            $this->addFlash('success', 'Novo orçamento adicionado');

            return $this->redirect($this->generateUrl('panel_budgets'));
        }
    }

    return $this->render('PanelBundle:Budget:add.html.twig', array(
        'clients' => $Client->findAll(),
        'form' => $form->createView()
    ));
}

Just to explain, the function getLastId() that appears above returns the ID of the last Budget added:

public function getLastId()
{
    return $this->createQueryBuilder('b')
                ->select('b.id')
                ->getQuery()
                ->getResult();
}

Describing the function quickly, it adds the data to the table budget and then, in the next part, I need to retrieve this Client selected and modify your budget. The problem is that the entity has the method __toString() that returns the name, which is displayed on the system home page, so no object will be returned and do not know how to do. In other attempts I got an error saying it is not possible to call the method setBudget() null.

Looking for other means, I worked out a role in the BudgetRepository.php which have two parameters to provide the id of Client to be updated and the id of Budget which was newly included in the bank, which produces the error:

[Semantical Error] line 0, col 34 near 'budget = ? 1 WHERE': Error: Invalid Pathexpression. Statefieldpathexpression or Singlevaluedassociationfield expected.

public function addBudgetToClient($clientId, $budgetId)
{
    $query = $this->createQueryBuilder('b');
    $query->update('PanelBundle:Client', 'c')
          ->set('c.budget', '?1')
          ->where($query->expr()->eq('c.id', '?2'))
          ->setParameter(1, $budgetId)
          ->setParameter(2, $clientId)
          ->getQuery()
          ->execute();
}

In the addAction(), I just inserted between the first flush() and setFlash() the following code snippet:

$Budget->addBudgetToClient($form['client_id']->getData()->getId(), $Budget->getLastId());

I appreciate any help, because it’s already a week on top of a problem.

ISSUE #1

On request, the BudgetType.php:

<?php

namespace CDG\PanelBundle\Form;

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

class BudgetType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('client_id', 'entity', array(
                    'class' => 'PanelBundle:Client',
                    'attr' => array(
                        'class' => 'form-control'
                    )
                ))
                ->add('address', 'text', array(
                    'attr' => array(
                        'class' => 'form-control'
                    )
                ))
                ->add('installments', 'choice', array(
                    'choices' => array(
                        '3' => '3x',
                        '4' => '4x'
                    ),
                    'attr' => array(
                        'class' => 'form-control',
                    )
                ))
                ->add('check_days', 'choice', array(
                    'choices' => array(
                        '30' => '30 dias',
                        '60' => '60 dias'
                    ),
                    'attr' => array(
                        'class' => 'form-control',
                    )
                ))
                ->add('date_start', 'date', array(
                    'attr' => array(
                        'format' => 'yyyy-MM-dd'
                    )
                ))
                ->add('total_value', 'money', array(
                    'attr' => array(
                        'class' => 'form-control',
                    )
                ));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'CDG\PanelBundle\Entity\Budget',
            'csrf_protection' => false,
        ));
    }

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

    public function serialize()
    {
        return serialize(array(
            'id' => $this->getId()
        ));
    }
    public function unserialize($serialized)
    {
        $data = unserialize($serialized);
        $this->id = $data['id'];
    }
}

Based on an example, I wrote the addAction() in another way, but this time the error occurs below, which is strange because my Client actually in its __construct() gives a new ArrayCollection() in budget, but even removing, the error persists.

Catchable Fatal Error: Argument 1 passed to Doctrine Common Collections Arraycollection::__Construct() must be of the type array, Object Given, called in /home/Gabriel/Documents/Casadogesso/vendor/Doctrine/Orm/lib/Doctrine/ORM/Unitofwork.php on line 555 and defined

public function addAction(Request $request)
{
    $Budget = new \CDG\PanelBundle\Entity\Budget();
    $form = $this->createForm(new BudgetType(), $Budget);
    $manager = $this->getDoctrine()->getManager();

    if ($request->getMethod() == 'POST') {
        $form->handleRequest($request);

        if ($form->isValid()) {
            $manager->persist($Budget);
            $manager->flush();

            $Client = $manager->find('CDG\PanelBundle\Entity\Client', $form['client_id']->getData()->getId());
            $Client->setBudget($Budget);

            $manager->persist($Client);
            $manager->flush();  // Stack Trace marca aqui.

            $this->addFlash('success', 'Novo orçamento adicionado');

            return $this->redirect($this->generateUrl('panel_budgets'));
        }
    }

    return $this->render('PanelBundle:Budget:add.html.twig', array(
        'clients' => $manager->getRepository('PanelBundle:Client')->findAll(),
        'form' => $form->createView()
    ));
}

ISSUE #2

Below the list of tables Client and Budget, as requested. The primary key of the Client and Budget is the field id, and in the Budget, the foreign key is client, which will store the ID of the client that budget belongs to. Please ignore "[1.]" and "" to the right side of the fields, I tried to make cardinality using Astah. :-)

inserir a descrição da imagem aqui

  • You can post the code of BudgetType, please?

  • @Rodrigorigotti Oh, hello! I updated the question along with another way I wrote the addAction(). Thank you for your interest, I hope that with all these details it becomes clearer. :-)

1 answer

0


The first thing I noticed is that there’s a mistake in catching the client_id of the form, besides being necessary a small Refactoring:

public function addAction(Request $request)
{
    $form = $this->createForm(new BudgetType());
    $manager = $this->getDoctrine()->getManager();
    $Client = $manager->getRepository('PanelBundle:Client');
    $Budget = $manager->getRepository('PanelBundle:Budget');

    if ($request->getMethod() == 'POST') {
        $form->handleRequest($request);

        if ($form->isValid()) {

            $BudgetEntity = $form->getData();
            $manager->persist($BudgetEntity);
            $manager->flush();

            // o client_id já retorna um objeto do tipo client
            $ClientEntity = $form->get('client_id')->getData();

            // a variável $Budget já contém o repositório de budget
            $ClientEntity->setBudget($Budget->getLast());

            $manager->persist($ClientEntity);
            $manager->flush();

            $this->addFlash('success', 'Novo orçamento adicionado');

            return $this->redirect($this->generateUrl('panel_budgets'));
        }
    }

    return $this->render('PanelBundle:Budget:add.html.twig', array(
        'clients' => $Client->findAll(),
        'form' => $form->createView()
    ));
}

(actually you don’t even need to fetch the last budget in the bank, just give a $ClientEntity->setBudget($BudgetEntity); if you do, the method getLast becomes unnecessary).

Also, in the budget repository, I would create a method getLast searching for the last budget entered (I don’t know user the query Builder, so I will solve with a DQL):

public function getLast()
{
    return $this
        ->getEntityManager()
        ->createQuery('
            SELECT b
            FROM PanelBundle:Budget b
            ORDER BY id DESC')
        ->setMaxResults(1)
        ->getSingleResult();
}

Try to implement these two changes without implementing the method addBudgetToClient and, if more difficulties arise, I will update the response. :)

PS: Do not create the relationship of budgets with the customer using an attribute $clientId, as this can lead you to think that the attribute just stores the customer id. My suggested improvement is to simply change the attribute to $client.

Suggested changes to the templates

After you posted the image of the models, I saw that part of your modeling is incorrect.

The bid is as follows: the Client entity has a 1-to-n relationship with the Budget entity (consequently, the Budget entity has an n-to-1 relationship with the Client entity).

What does that mean? That while a Budget has one and only one Client, a Client can have many Budgets.

This implies the following class change Client: rather than having a method setBudget (implying that Client has only one Budget - which is not true), it should have the methods addBudget, removeBudget and setBudgets (yes reflecting a 1-to-n relationship). Already in class Budget, the relationship is correct.

I suggest the following: remove all methods from the Client and Budget classes, and manage the methods with the following command:

app/console doctrine:generate:entities CDG\PanelBundle

Thus, the methods that will be present in both classes will reflect the relationships between them.

Another useful command that can help you:

app/console doctrine:schema:update --dump-sql

With this command, Doctrine will compare the table schema suggested in the application with the currently existing table schema, and show the divergences between the models. So, you can make any corrections to not have problems in the future.

  • Hello! Thank you Rodrigo but in both suggestions the same error is generated: Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in /home/gabriel/Documents/CasaDoGesso/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 555 and defined. My client has in his __construct() the assignment of ArrayCollection() to the budget that even removed the error persists.

  • Can you tell me when the flow happens? The budget is entered? The user is updated with the latest budget?

  • Stack Trace marks the line $manager->flush();, after persisting the $ClientEntity. Yes, it inserts a new budget but does not update the client field. I saved pictures from the page that gives error, if you want you can access mine Google Drive to see them! :-)

  • Now that I realize: if the client’s relationship to the budget is 1-to-n (and consequently the budget for customer is n-to-1), then it is not possible to give a setBudget the client. The method would be addBudget, since he may have multiple budgets. I think there should be two relationships between client and budget: all customer budgets, and the last budget - or even eliminate that last relationship and seek the last budget through a query in the bank.

  • Good afternoon! I seem to have been told about a addBudget(), but they had not properly explained the reason. What would this method be? A DQL to update the field budget? And this removal of relationships between entities? I confess that I did not understand. -(. It is very obscure for me all this. So many solutions and means I tried but always an error is generated. And as for events?

  • Update the post with a screenshot of the database modeling, please. It might just be these two tables. :)

  • I already modified Rodrigo! : ). The relation in Symfony2 of these two tables is correct according to the image?

  • I updated my answer, look there. :)

  • Ow! Looks like it worked! First I got confused because the command to update the database removed the field budget client, so I decided to test by making a loop in the view about the client.budget, and to my surprise he displayed the address (because I had to create a __toString() in the Budget) from that customer! I know it’s the magic of the relationship in Symfony2, but it’s curious how it lets you call a field that’s not on the table in the bank. I think I finally managed to overcome this problem. I would never have made it alone! :)

  • Oops! What a beauty! : ) Always make use of the Doctrine commands to generate the bank or methods, are a hand on the wheel!

  • Just one more tip: as a Budget reference the Client, then consider removing the attribute clientId and trade for client, so the objects become more consistent.

  • It’s already done. Thank you! The friend request is mine, but don’t worry I won’t bother you when you’re in trouble!:)

Show 7 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.