doctrine2 random

For some really good reasons or, doctrine2 do not implement the RAND() fonction to sort randomly your query results. Here some dirty ways to do it. Don’t use them it’s bad.

One solution is to shuffle your collection of rows using php

      public function getRandom($max = 10,$site) {
        $results = $this->createQueryBuilder('u')
                ->where('u.site = :site')
                ->setParameter('site', $site)
                ->orderBy('u.sort', 'DESC')
                ->setMaxResults($max)
                ->getQuery()
                ->getResult();
        shuffle($results);
        return $results;
    }

In this solution, you retrieve the last 10 rows and shuffle them after. Peformances are not too bad but you will always retrieves the same last rows from your table.

Another solution is to use the array_rand php fonction

      public function getRandom($site) {
        $results = $this->createQueryBuilder('u')
                ->where('u.site = :site')
                ->setParameter('site', $site)
                ->orderBy('u.sort', 'DESC')
                ->getQuery()
                ->getResult();
        $result2 = array_rand($results);
        return $result2;
    }

In this case you fetch all rows from your table, this could be slow and memory consuming…

If you need to retrieve only one row, you can use somethinfg like this

 public function getOneRandom()
{
$em = $this->getEntityManager();
$max = $em->createQuery('
SELECT MAX(q.id) FROM questions q
')
->getSingleScalarResult();
return $em->createQuery('
SELECT q FROM questions q
WHERE q.id >= :random
ORDER BY q.id ASC
')
->setParameter('random',rand(0,$max))
->setMaxResults(1)
->getSingleResult();
}

This solution can only be used if you want to retrieve any of the tables rows, if you add a filtrer you may return an empty result.

This solution is not dirty it just use 2 queries instead of one. the tips is to use the offset. And you can use a filter !!!

$qCount = Doctrine::getTable('Questions')
     ->createQuery()
     ->select('count(*)')
     ->where('site = :site')
     ->setParameter('site', $site)
     ->fetchOne(array(), Doctrine::HYDRATE_NONE);
$question = Doctrine::getTable('Questions')
     ->createQuery()
     ->select('*')
     ->where('site = :site')
     ->setParameter('site', $site)
     ->limit(1)
     ->offset(rand(0, $qCount[0] - 1))
     ->fetchOne();

And you still can use native queries : http://docs.doctrine-project.org/en/latest/reference/native-sql.html

Fatal error : Declaration of {{path}}::validate() must be compatible with that of Sonata\AdminBundle\Admin\AdminInterface::validate()

You are trying to setup a custom validator on Sonata and you get this error :

Fatal error: Declaration of Adin\AdminBundle\Admin\AnnonceAdmin::validate() must be compatible with that of Sonata\AdminBundle\Admin\AdminInterface::validate() in /home/www/arlogis/src/Adin/AdminBundle/Admin/AnnonceAdmin.php on line 250

Don’t panic, just add this at the begining of your class

use Sonata\AdminBundle\Validator\ErrorElement;

in our example it’s in this file : /AdminBundle/Admin/AnnonceAdmin.php

Doctrine2 override save method

With Doctrine2 you cannot override save method but you can execute preUpdate() method.
See below how to implement it.

On your entity class, for example AdminBundle/Entity/Annonce.php, add the HasLifecycleCallbacks annontation

/**
 * @ORM\Entity(repositoryClass="Tripix\AdminBundle\Repository\AnnonceRepository")
 * @ORM\HasLifecycleCallbacks
 */

And your method, with the annotation

/** @ORM\PreUpdate() */
        public function preUpdate()
        {
                $this->created_at = new \DateTime("now");
        }

That’s all, it should works.

A complete example, just in case :

<?php
namespace Tripix\AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;


/**
 * @ORM\Entity(repositoryClass="Tripix\AdminBundle\Repository\AnnonceRepository")
 * @ORM\HasLifecycleCallbacks
 */
class Annonce
{

/** @ORM\PreUpdate() */
        public function preUpdate()
        {
                $this->created_at = new \DateTime("now");
        }

    public function __construct()
    {
        $this->created_at = new \DateTime("now");
        $this->visuels = new ArrayCollection();
    }

Symfony2 sonata custom CRUD template

It is possible to change templates use by default.

First, override the controller action method, in your AdminBundle/Controller/SecteurAdminController.php file

 /**
     * return the Response object associated to the list action
     *
     * @return Response
     */
    public function listAction()
    {
        if (false === $this->admin->isGranted('LIST')) {
            throw new AccessDeniedException();
        }

        $datagrid = $this->admin->getDatagrid();
        $formView = $datagrid->getForm()->createView();

        // set the theme for the current Admin Form
        $this->get('twig')->getExtension('form')->setTheme($formView, $this->admin->getFilterTheme());

        //custom code from here
        $total = 0;
        $enable = 0;
        $new = 0;
        $site = $this->admin->site;

        $repository = $this->getDoctrine()->getRepository('TripixAdminBundle:Annonce');
        $total = $repository->getNombreAnnonceTotal($site);
        $enable = $repository->getNombreAnnonceEnabled($site);
        $new = $repository->getNombreAnnonceNew($site);



        return $this->render('TripixAdminBundle:CRUD:list_secteur.html.twig', array(
            'action'   => 'list',
            'form'     => $formView,
            'datagrid' => $datagrid,
            'total'     => $total,
            'enable'    => $enable,
            'new'       => $new,
        ));
    }

The import thing on previous code is the render() fonction, the first parameter is the template to use.
In our example, the template file will be /AdminBundle/Ressources/view/CRUD/list_secteur.html.twig

You can use the /AdminBundle/Ressources/view/CRUD/base_secteur.html.twig file as example for your custom template.

Parent function to override on the controller are here : /vendor/bundles/Sonata/AdminBundle/Controller/CRUDController.php

Symfony2 Sonata add custom column on page list

To add a custom column on a page list, just follow these steps. In the example, we will add a “Nombre d’annoce(s)” column.

On your admin class file (ex: /AdminBundle/Admin/SecteurAdmin.php), on your configureListFields() fonction, add a new entry on the listMapper object

    /**
     * @param \Sonata\AdminBundle\Datagrid\ListMapper $listMapper
     * @return void
     */
    protected function configureListFields(ListMapper $listMapper) {
        $listMapper
                ->addIdentifier('titre')
                ->add('Tri', 'string', array('template' => 'TripixAdminBundle:Admin:list_tri.html.twig'))
                ->add('Nb', 'string', array('label' => 'Nombre d\'annonce(s)', 'template' => 'TripixAdminBundle:Admin:list_nb_annonce.html.twig'))
                ->add('_action', 'actions', array(
                    'actions' => array(
                        'edit' => array(),
                        'delete' => array(),
                    )
                ))
        ;
    }

This new entry said to use the TripixADminBundle:Admin:list_tri.html.twig template.

Now, create your template file (list_nb_annonce.html) in the /AdminBundle/ressources/view/admin/ directory.

{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}

{% block field%}
{% if admin.datagrid.results|length > 1 %}
 <div>
        <strong>{{ object.getNombreAnnonce}}</strong>
    </div>
{% endif %}
{% endblock %}

In our example, getNombreAnnonce() is not defined, let’s do it
In the /adminBundle/Entity/Secteur.php file, add

   public function getNombreAnnonce()
    {
      return count($this->getAnnonces());
    }

That’s all.

must be compatible with that of Sonata\AdminBundle\Admin\AdminInterface::validate() error message

You are using sonata and want to validate some fields.
You innocently add the validate function to your Admin file

function validate(ErrorElement $errorElement, $object)
{
}

Then, you get this error :

Fatal error: Declaration of Adin\AdminBundle\Admin\AnnonceAdmin::validate() must be compatible with that of Sonata\AdminBundle\Admin\AdminInterface::validate() in /home/www/arlogis/src/Adin/AdminBundle/Admin/AnnonceAdmin.php on line 251 

You checked to AdminInterface file the declaration is the same.

To fix it, you need to use additionnal namespace :

use Sonata\AdminBundle\Validator\ErrorElement;
use Symfony\Component\Validator\ValidatorInterface;

Symfony validator custom error message

If you need to customize your validator error messages, you can use the setMessage() function.
On your Form class (ex: /lib/form/doctrine/DemandeForm.class.php)

//sfValidatorInteger
$this->validatorSchema['produit_ean']->setMessage('invalid', 'ean should be integer');
$this->validatorSchema['produit_ean']->setMessage('min', 'ean must be at least %min%');
$this->validatorSchema['produit_ean']->setMessage('max', 'ean must be at least %max%');

//sfValidatorDate
$this->validatorSchema['date']->setMessage('bad_format', 'Format must be dd/mm/YYYY');
$this->validatorSchema['date']->setMessage('max', 'Date should be after than 01/01/2013');
$this->validatorSchema['date']->setMessage('min', 'Date should be before than 01/02/2013');

//required
$this->validatorSchema['date']->setMessage('required', 'Date is mandatory');

Symfony2 twig add new function file_exists

An important function is missing on twig : file_exists.

I will show you here how to define a new function for twig.

On your namespace/bundle, add a Twig/Extension directory and create your class file :
/src/Adin/ArlogisBundle/Twig/Extension/FileExistsExtension.php

<?php

namespace Adin\ArlogisBundle\Twig\Extension;

class FileExistsExtension extends \twig_Extension
{
        /**
         *Return the function registered as twig extension
         *
         *@return array
         */
        public function getFunctions()
        {
                return array(
                        'file_exists' => new \Twig_Function_Function('file_exists'),
                        );
        }

        public function getName()
        {
                return 'adin_file_exists';
        }
}
?>

You then have to register your service, add a in your /Adin/ArlogisBundle/Ressources/config/services.xml file

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
       <service id="adin.twig.tripix_extension" class="Adin\ArlogisBundle\Twig\Extension\FileExistsExtension">
        <tag name="twig.extension" />
        </service>
    </services>
</container>

It’s ready, you can use it on your twig template

{% if file_exists('/var/www/image.jpg') %}
    File exists
{% else %}
    File not exists
{% endif %}

Be carefull, you need to specify the absolute path of the file. You can create a twig global variable root_path.
app/config/config.yml

# Twig Configuration
twig:
    globals:
        root_path: %kernel.root_dir%/../web

Then on twig template

{% if file_exists({{root_path}}'/var/www/image.jpg') %}
    File exists
{% else %}
    File not exists
{% endif %}