June 8, 2016

How to restrict modification to selected properties of an entity while persisting?

Let’s suppose we have an entity class and a generic FormType corresponding to the entity

class Entry {
    protected $start_time;
    protected $end_time;
    protected $confirmed; // Boolean

    // ... Other fields and getters and setters
}

On the CRUD of this entity, you might not want to allow any modification on start_time or end_time if the entity has been confirmed or in the above case when $confirmed === true

Here are couple of ways this can be solved:

  1. Using the EntityManager you can retrieve original data of the entity. $em->getUnitOfWork()->getOriginalEntityData($entity) returns an array with old data of an entity which can be used to reset the data.

    if($form->isSubmitted() && $editForm->isValid()) {
        // The form was submitted
        if($confirmed === true) { // if the entity was confirmed previously
            $oldData = $em->getUnitOfWork()->getOriginalEntityData($entity);
            $entity->setStartTime($oldData['start_time']);
            $entity->setEndTime($oldData['end_time']);
        }
        // After this you can happily persist the data
        $em->persist($entity);
        $em->flush();
    }
    
  2. Using Form Events

    The following is a Symfony 3 Solution, try this solution for Symfony 2.

    class EntityType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            // ....
            // ....
            // Your form fields
    
            $builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPreSetData'));
        }
    
    
        public function onPreSetData(FormEvent $event) {
            /** @var YourEntity $entity */
            $entity = $event->getData();
            $form = $event->getForm();
    
            if($entity instanceof YourEntity) {
                if ($entity->getTimesheetEntry()->getTimeApproved() === true) {
                    $config = $form->get('start_time')->getConfig();
                    $options = $config->getOptions();
                    $options['disabled'] = true;
                    $form->add('start_time', get_class($config->getType()->getInnerType()), $options);
    
                    $config = $form->get('end_time')->getConfig();
                    $options = $config->getOptions();
                    $options['disabled'] = true;
                    $form->add('end_time', get_class($config->getType()->getInnerType()), $options);
                }
            }
    
        }
    }
    
March 31, 2016

Update schema for create longtext field on MySQL data base on symfony

Ehsan’s Question:

I want to update MySQL field from text to longtext using Doctrine schema.

Now my code is like this:

/**
 *@var string
 *@ORMColumn(name="head_fa", type="string", length=1000, nullable=true)
 */
private $head_fa;

/**
 *@var string
 *@ORMColumn(name="head_en", type="string", length=1000, nullable=true)
 */
private $head_en;

/**
 *@var string
 *@ORMColumn(name="body_fa", type="text", length=1000, nullable=true)
 */
private $body_fa;

/**
 *@var string
 *@ORMColumn(name="body_en", type="text", length=1000, nullable=true)
 */
private $body_en;

and the problem is when i change this field to this code

/**
 *@var string
 *@ORMColumn(name="head_fa", type="string", length=1000, nullable=true)
 */
private $head_fa;

/**
 *@var string
 *@ORMColumn(name="head_en", type="string", length=1000, nullable=true)
 */
private $head_en;

/**
 *@var string
 *@ORMColumn(name="body_fa", type="text", nullable=true)
 */
private $body_fa;

/**
 *@var string
 *@ORMColumn(name="body_en", type="text", nullable=true)
 */
private $body_en;

and run “php app/console doctrine:schema:update –force” command on console it said that “Nothing to update – your database is already in sync with the current entity metadata.” How to change this field to longtext on mysql database.

I do the same on different part of the project.
this is the code

/**
 * @ORMColumn(name="body", type="text", nullable=true)
 */
protected $body;

and after executing the “php app/console doctrine:schema:update –force” command on terminal this field is changed to longtext on MySQL database.

If you don’t specify a length parameter, it will automatically the column as LONGTEXT in MySQL.

March 28, 2016

listen to kernel.request event on Silex?

Lhassan Baazzi’s Question:

I want to listen to kernel.request event on Silex microframework http://silex.sensiolabs.org/documentation

How ?

You can access the dispatcher service as $app['dispatcher'] and you can see here how to use it. But I think you should use Silex’s before event instead as it’s called on kernel.request as well (here is a good expample how to use it).

UPDATE:

The links above are outdated. Before filters moved to a new middlewares section and here is how you can write one:

$app->before(function (Request $request) {
    // do what you want ...

    // if you want you can return a response so the controller won't be called
    // return new Response($content);

    // or just return null
});

There is a on($eventName, $callback, $priority = 0) method on silex application which you can use to listen to any event. On this particular case it will be as following.

$app->on(SymfonyComponentHttpKernelKernelEvents::REQUEST, function (SymfonyComponentHttpKernelEventGetResponseEvent $event) use ($app) {
    // Your actions
});
March 18, 2016

Symfony2 form collection not calling addxxx and removexxx even if 'by_reference' => false

Nick Denry’s Question:

I have the Customer entity and two one-to-many relations CustomerPhone and CustomerAddress.

The Customer entity has addPhone/removePhone and addAddress/removeAddress “adders”.

CustomerType collections options has ‘by_reference’ => false for both collections.

Entity functions addPhone/removePhone and addAddress/removeAddress not called after form submitted, so CustomerPhone and CustomerAddress have no parent id after persist.

Why could addPhone/removePhone and addAddress/removeAddress not called on form submit?

UPD 1.

After @Baig suggestion now I have addPhone/removePhone “adders” called, but addAddress/removeAddress not. Can’t get why because they are identical.

 # TestCustomerBundle/Entity/Customer.php

 /**
 * @var string
 *
 * @ORMOneToMany(targetEntity="CustomerPhone", mappedBy="customerId", cascade={"persist"}, orphanRemoval=true)
 */
private $phone;

/**
 * @var string
 *
 * @ORMOneToMany(targetEntity="CustomerAddress", mappedBy="customerId", cascade={"persist"}, orphanRemoval=true)
 */
private $address;

Same file “adders”

# TestCustomerBundle/Entity/Customer.php
/**
 * Add customer phone.
 *
 * @param Phone $phone
 */
public function addPhone(CustomerPhone $phone) {
    $phone->setCustomerId($this);
    $this->phone->add($phone);

    return $this;
}

/**
 * Remove customer phone.
 *
 * @param Phone $phone customer phone
 */
public function removePhone(CustomerPhone $phone) {
    $this->phone->remove($phone);
}
/**
 * Add customer address.
 *
 * @param Address $address
 */
public function addAddress(CustomerAddress $address) {
    $address->setCustomerId($this);
    $this->address->add($address);

    return $this;
}

/**
 * Remove customer address.
 *
 * @param Address $address customer address
 */
public function removeAddress(CustomerAddress $address) {
    $this->address->remove($address);
}

Relations:

# TestCustomerBundle/Entity/CustomerPhone.php
/**
 * @ORMManyToOne(targetEntity="Customer", inversedBy="phone")
 * @ORMJoinColumn(name="customer_id", referencedColumnName="id")
 **/
private $customerId;

#TestCustomerBundle/Entity/CustomerAddress.php
/**
 * @ORMManyToOne(targetEntity="Customer", inversedBy="address")
 * @ORMJoinColumn(name="customer_id", referencedColumnName="id")
 **/
private $customerId;

CustomerType form:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('name')
        ->add('phone', 'collection', array(
            'type' => new CustomerPhoneType(),
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'options' => array('label' => false)
        ))
        ->add('address', 'collection', array(
            'type' => new CustomerAddressType(),
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'options' => array('label' => false)
        ))
        ->add('submit', 'submit')
    ;
}

Controller.

# TestCustomerBundle/Controller/DefaultController.php

public function newAction(Request $request)
    {
        $customer = new Customer();
        // Create form.
        $form = $this->createForm(new CustomerType(), $customer);
        // Handle form to store customer obect with doctrine.
        if ($request->getMethod() == 'POST')
        {
            $form->bind($request);
            if ($form->isValid())
            {
                /*$em = $this->get('doctrine')->getEntityManager();
                $em->persist($customer);
                $em->flush();*/
                $request->getSession()->getFlashBag()->add('success', 'New customer added');
            }
        }
        // Display form.
        return $this->render('DeliveryCrmBundle:Default:customer_form.html.twig', array(
            'form' => $form->createView()
        ));
    }

UPD 2.
Test if addAddress called.

/**
     * Add customer address.
     *
     * @param Address $address
     */
    public function addAddress(Address $address) {
        jkkh; // Test for error if method called. Nothing throws.
        $address->setCustomerId($this);
        $this->address->add($address);        
    }

UPD 3.

CustomerAddressType.php

<?php

namespace DeliveryCrmBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolverInterface;

class CustomerAddressType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('street')
            ->add('house')
            ->add('building', 'text', ['required' => false])
            ->add('flat', 'text', ['required' => false])
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'DeliveryCrmBundleEntityCustomerAddress'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'delivery_crmbundle_customeraddress';
    }
}

CustomerPhoneType.php

<?php

namespace DeliveryCrmBundleForm;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolverInterface;

class CustomerPhoneType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('number')
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'DeliveryCrmBundleEntityCustomerPhone'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'phone';
    }
}

This answer corresponds to Symfony 3, but I am sure this applies to Symfony 2 as well. Also this answer is more as a reference than addressing OP’s issue in particular (which I am not to clear)

On ..Symfony/Component/PropertyAccess/PropertyAccessor.php the method writeProperty is responsible for calling either setXXXXs or addXXX & removeXXXX methods.

So here is order on which it looks for the method:

  1. If the entity is array or instance of Traversable (which ArrayCollection is) then pair of

    • addEntityNameSingular()
    • removeEntityNameSingular()

      Source for reference:

      if (is_array($value) || $value instanceof Traversable) {
          $methods = $this->findAdderAndRemover($reflClass, $singulars);
      
          if (null !== $methods) {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
              $access[self::ACCESS_ADDER] = $methods[0];
              $access[self::ACCESS_REMOVER] = $methods[1];
          }
      }
      
  2. If not then:

    1. setEntityName()
    2. entityName()
    3. __set()
    4. $entity_name (Should be public)
    5. __call()

      Source for reference:

      if (!isset($access[self::ACCESS_TYPE])) {
          $setter = 'set'.$camelized;
          $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
      
          if ($this->isMethodAccessible($reflClass, $setter, 1)) {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
              $access[self::ACCESS_NAME] = $setter;
          } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
              $access[self::ACCESS_NAME] = $getsetter;
          } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
              $access[self::ACCESS_NAME] = $property;
          } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
              $access[self::ACCESS_NAME] = $property;
          } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
              // we call the getter and hope the __call do the job
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
              $access[self::ACCESS_NAME] = $setter;
          } else {
              $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
              $access[self::ACCESS_NAME] = sprintf(
                  'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '.
                  '"__set()" or "__call()" exist and have public access in class "%s".',
                  $property,
                  implode('', array_map(function ($singular) {
                      return '"add'.$singular.'()"/"remove'.$singular.'()", ';
                  }, $singulars)),
                  $setter,
                  $getsetter,
                  $reflClass->name
              );
          }
      }
      

To answer OP’s issue, based on the above mentioned information, the PropertyAccessor class of symfony is not able to read your addXX and removeXX method properly. The potential reason might be that is not identified as array or ArrayCollection which has to be done from the constructor of the entity

public function __construct() {
     $this->address = new ArrayCollection();
     // ....
}
December 2, 2015

Count Rows in Doctrine QueryBuilder

Acyra’s Question:

I’m using Doctrine’s QueryBuilder to build a query, and I want to get the total count of results from the query.

$repository = $em->getRepository('FooBundle:Foo');

$qb = $repository->createQueryBuilder('n')
        ->where('n.bar = :bar')
        ->setParameter('bar', $bar);

$query = $qb->getQuery();

//this doesn't work
$totalrows = $query->getResult()->count();

I just want to run a count on this query to get the total rows, but not return the actual results. (After this count query, I’m going to further modify the query with maxResults for pagination.)

Something like:

$qb = $entityManager->createQueryBuilder();
$qb->select('count(account.id)');
$qb->from('ZaysoCoreBundle:Account','account');

$count = $qb->getQuery()->getSingleScalarResult();

For people who are using only Doctrine DBAL and not the Doctrine ORM, they will not be able to access the getQuery() method because it doesn’t exists. They need to do something like the following.

$qb = new QueryBuilder($conn);
$count = $qb->select("count(id)")->from($tableName)->execute()->fetchColumn(0);
May 14, 2012

Save multiple form

Question by Kioko Kiaza

Having this Entity for example:

<?php

namespace GitekHotelBundleEntity;

use DoctrineORMMapping as ORM;

/**
 * GitekHotelBundleEntityProduct
 *
 * @ORMTable()
 * @ORMEntity(repositoryClass="GitekHotelBundleEntityProductRepository")
 */
class Product
{
    /**
     * @var integer $id
     *
     * @ORMColumn(name="id", type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     *
     * @ORMColumn(name="name", type="string", length=100)
     */
    private $name;

How to build a Form to save like 10 products on a row? I want a form with a “+” button and dinamically add line and submit all products in a row.

any help or clue?
thanks in advance

Answer by Farhan Ahmad

You could use Javascript(jQuery) to add the form elements to your page dynamically when you click the “Add” button. Something like this:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
                    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>

    <script type="text/javascript">
        $(document).ready(function() {
            $('#btnAdd').click(function() {
                var num     = $('.clonedInput').length;
                var newNum  = new Number(num + 1);

                var newElem = $('#input' + num).clone().attr('id', 'input' + newNum);

                newElem.children(':first').attr('id', 'name' + newNum).attr('name', 'name' + newNum);
                $('#input' + num).after(newElem);
                $('#btnDel').attr('disabled','');

                if (newNum == 5)
                    $('#btnAdd').attr('disabled','disabled');
            });

            $('#btnDel').click(function() {
                var num = $('.clonedInput').length;

                $('#input' + num).remove();
                $('#btnAdd').attr('disabled','');

                if (num-1 == 1)
                    $('#btnDel').attr('disabled','disabled');
            });

            $('#btnDel').attr('disabled','disabled');
        });
    </script>
</head>

<body>

<form id="myForm">
    <div id="input1" style="margin-bottom:4px;" class="clonedInput">
        Name: <input type="text" name="name1" id="name1" />
    </div>

    <div>
        <input type="button" id="btnAdd" value="add another name" />
        <input type="button" id="btnDel" value="remove name" />
    </div>
</form>

</body>
</html>

Reference
Then when the form is posted, you can walk through the $_POST array.

Answer by Starx

That is very least amount of code, to see your working process.

But basically such tasks are solved, by storing all the information on the session as a multidimensional array and finally inserting them one by one on the last point.

...

Please fill the form - I will response as fast as I can!