Thursday, January 21, 2016

Using multiple databases with Symfony2 and Doctrine2

I was looking for documentation on how to use multiple databases with Symfony and all the results were talking about having 2 connections with 2 entity managers and adding mappings for each bundle ...too complicated for a simple thing.

But I kept on searching and I found this article:  https://techpunch.co.uk/development/using-multiple-databases-with-symfony2-and-doctrine2  which is saying:

"
You will need to use only connection that spans all of your databases, if you want to build a relationship between entities then they must use the same connection. Do to this you will need a user that has access to all of the databases the you wish to access. Setup this user as per usual within your Symfony2 application, for the database name just select one of the databases, it doesn't matter which one.
No extra Doctrine config is needed to get this working, ....

The key to getting multiple databases to work is within your entity classes, you need to specify the table name of the entity with a prefix of the name of the database to which the table belongs. Here is an example using annotations:

<?php
namespace Demo\UserBundle\Entity;

use DoctrineORMMapping as ORM;

/**
 * Demo\UserBundle\Entity\User
 *
 * @ORMTable(name="users.User")
 */
class User implements
{
  /* ... */

"
I tried this solution on my project and worked fine.

You may want to look also at Doctrine's  Master-Slave Connection:  http://blog.alejandrocelaya.com/2014/04/18/configure-multiple-database-connections-in-doctrine-with-zend-framework-2-2/

Monday, January 18, 2016

Useful JavaScript libraries

Below are some JS libraries I used and they work :) :

1. Sending AJAX form with attachments (which works on IE9 )


I found this JQuery pugin  http://malsup.com/jquery/form/ with decent documentation which meet the requirements.


Quick Start Guide

1. Add a form to your page. Just a normal form, no special markup required: 
<form id="myForm" action="comment.php" method="post"> 
    Name: <input type="text" name="name" /> 
    Comment: <textarea name="comment"></textarea> 
    <input type="submit" value="Submit Comment" /> 
</form>
 
2. Include jQuery and the Form Plugin external script files and a short script to initialize the form when the DOM is ready: 
<html> 
<head> 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script> 
    <script src="http://malsup.github.com/jquery.form.js"></script> 
 
    <script> 
        // wait for the DOM to be loaded 
        $(document).ready(function() { 
            // bind 'myForm' and provide a simple callback function 
            $('#myForm').ajaxForm(function() { 
                alert("Thank you for your comment!"); 
            }); 
        }); 
    </script> 
</head> 

2. Date picker with options (which works on IE9 )

The answer is Datepicker from JQuery UI: https://jqueryui.com/datepicker/
You may want to stop the user to insert data directly in the input field manually, this can be archived by setting 'readonly' parameter to 'true'. The problem is that the field looks disabled all the time, you may encounter some users thinking that they should not click on that input field.
Solution: make the input field readonly when the user clicks on it and after closing the Datepicker the input field will be made again writable ('readonly' set to 'false').

 beforeShow: function(input, inst) {
                             $(input).attr('readonly', true);
 },
 onClose: function(dateText, inst) {
                        $(this).attr('readonly', false);

 }


3. HTML 5 Placeholder working on  IE9 (make sure not to send default values when submitting the form)


https://github.com/parndt/jquery-html5-placeholder-shim

Usage

Just include the jquery.html-placeholder-shim.js script into your document head like so:
<head>
  <script type='text/javascript' src='jquery.js'></script>
  <script type='text/javascript' src='jquery.html5-placeholder-shim.js'></script>
</head>
The script will automatically execute itself on the $(document).ready event and can be re-executed at any time (for example, to add placeholders to text boxes created during dynamic changes to the page) by running $.placeholder.shim();.

HTML5 placeholder Example:

<input type="search" placeholder="search the internets" name="query" />

4. Searchable Drop down list

 http://harvesthq.github.io/chosen/
Also Bootstrap theme for Chosen:  https://github.com/dbtek/chosen-bootstrap


If you form is dynamically loaded you can use it like this:

<script>
        $( document ).ajaxComplete(function() {
            $.placeholder.shim();
            $("#select_input").chosen();
        });
  </script>



Monday, January 11, 2016

CPANAGeneratorBundle

I was looking for a CRUD generator able to do more than the one from Sensio Labs, this is how I found PUGXGeneratorBundle.

PUGXGeneratorBundle is extending SensioGeneratorBundle and very important has decent documentation. Some of the  functionalities I like from it:
  • support for form themes (customizable)
  • default templates suitable with Boostrap and Font Awesome
  • nice "check" icons for boolean fields (when using Font Awesome)
  • support for pagination (requires KnpPaginatorBundle)
  • support for filters (requires LexikFormFilterBundle)
  • support for sorting 
Of course I wanted something more so I made my own flavor of CRUD Generator.

CPANAGeneratorBundle adds to the Show view of an entity the associated objects from Bidirectional relations.
Example: there are 2 entities: Author and Book found in One-to-Many BIDIRECTIONAL relation. In 'Author' entity there is a property called 'books' of type ArrayCollection. In the author/show view after the fields related to Author there will be listed the Books associated. Also CPANAGeneratorBundle is adding buttons for Add book, view and edit.
Author
Last name: Herbert
First name: Frank
Nationality: American
Id: 1
Books
Add book
Title: Dune Chronicles
Genre : Science Fiction
Id:1
view edit
Title: Dune Mesiah
Genre: Science Fiction
Id: 2
view edit

You can find it on Github:  https://github.com/cristianpana86/GeneratorBundle
and on Packagist for installing via Composer:  https://packagist.org/packages/cpana/generator-bundle

Friday, January 8, 2016

Symfony custom constaint: check total size of several form attachements


The cookbook link on how to create a custom constraint is this one:
http://symfony.com/doc/master/cookbook/validation/custom_constraint.html


Request: total size of form attachments should not be bigger than a given value.

Implementation:

I've created a directory 'Validator' under my Bundle where I added two classes:
- FilesTotalSize which extends class Constaint
- FilesTotalSizeValidator extends ConstaintValidator

FilesTotalSize:


<?php
namespace Bundle\Validator;

use Symfony\Component\Validator\Constraint;

/** @Annotation */
class FilesTotalSize extends Constraint
{
    public $message;
   
    public function validatedBy()
    {
        return get_class($this).'Validator';
    }

    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }
}


The name of the Validation class will be constraint class concatenated with 'Validator';

Because this constraint is checking values from several fields is considered a class validation (as opposite to field validation which can be seen in the documentation examples ).
For this reason getTargets will return CLASS_CONSTRAINT.

The actual check of the condition is made in the validator class. The variable '$value' will give access to the object to be validated. 'attachement1', 'attachement2' and 'attachement3' are the names of the fields declared in the Entity used for file uploading (file). The size is expressed in bytes, so for 3MB I have 3 millions of bytes. The method  'addViolation' receive as parameter the error message to be displayed.
.
FilesTotalSizeValidator:

<?php
namespace  Bundle\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;


class FilesTotalSizeValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        $size_att1=0;
        $size_att2=0;
        $size_att3=0;
       
        if (property_exists($value, 'attachement1')){
            if(!is_null($value->getAttachement1())){
                $size_att1= $value->getAttachement1()->getClientSize();
            }
        }
       
        if (property_exists($value, 'attachement2')){
            if(!is_null($value->getAttachement2())){
                $size_att2= $value->getAttachement2()->getClientSize();
            }
        }
       
        if (property_exists($value, 'attachement3')){
            if(!is_null($value->getAttachement3())){
                $size_att3= $value->getAttachement3()->getClientSize();
            }
        }
       
        $totalSize=0;
        $totalSize=(int)$size_att1 +(int)$size_att2 +(int)$size_att3;

        if ($totalSize > 3145728) {  //check if total size of files is bigger than 3MB
            $this->context->addViolation('Maximum size is 3MB!');
        }
    }
}



Usage:

In the entity class:

use MyBundle\Validator\FilesTotalSize;

/**
 * @ORM\Entity
 * @FilesTotalSize
 */


class EntityName {

Doctrine Single Table Inheritance

I have a request to develop 3 forms to collect information from users. They share some  of the information like: name, address, dateamount and some fields are different, each form will have some attachments (3 or 2) which represent different types of documents. More than that on the 'amount ' field there are different validations to make, depending on which form is applied.

How should I implement this? After some research the Doctrine Single Table Inheritance seems to be the answer:

Official documentation: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html
Useful blog: https://blog.liip.ch/archive/2012/03/27/table-inheritance-with-doctrine.html

Because the objects (corresponding to the forms) to be saved in the database have a lot in common  they can all be kept in just one table. The table needs to allow NULL for the fields which are not common for all forms.

In Symfony I will create a base entity class which will be extended by child entities (one child for each form). The base entity can be "abstract' as it is not supposed to be directly used (the child entity classes  will be used just as regular entities ).
Because I want to have different validation on the 'amount' field (and I want to keep them in  entity) I will move the 'amount' from the base class to child classes.
Also in the table there will be a column which will keep the type of the record (object typeA from formA, object typeB from formB, etc ). The equivalent of this column in Doctrine language is DiscriminatorColumn.

        @ORM\DiscriminatorColumn: indicates which column will be used as discriminator (i.e. to store the type of item). You don’t have to define this column in the entity, it will be automagically created by Doctrine.

Exaple:

Base entity class:


<?php

namespace Bundle\Entity;

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

/**
 * Base entity class
 *
 * @ORM\Table(name="table")
 * @ORM\Entity
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="refund_type", type="string")
 * @ORM\DiscriminatorMap( {"typeA_in_database" = "ChildEntityA", "
typeB_in_database" = "ChildEntityB" } )
 */
abstract class Base
{

 --------------------------------------
---------------------------------------
Child entity class

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use DDRCBundle\Validator\FilesTotalSize;

/**
 * @ORM\Entity
 * Child
 *
 */
class Child extends Base




 

Monday, November 30, 2015

Debugging Symfony with XDebug

Many times the information from the Symfony Debugger is enough to figure what's the problem, but sometimes I need to debug using XDebug. When I tried to debug my code using XDebug (with Notepadd++ and DBGp plugin)  I noticed that my breakpoints were ignored.. hmm weird.
 Because of the caching done by Symfony those breakpoints are never reached because that code is not executed. In order to overcome this issue the app_dev.php should be changed.

Source:  https://www.adayinthelifeof.nl/2014/12/31/debugging-symfony-components/

    // CHANGE: Change from bootstrap.php.cache to autoload.php
    $loader = require_once __DIR__.'/../app/autoload.php';
    Debug::enable();
     
    require_once __DIR__.'/../app/AppKernel.php';
     
    $kernel = new AppKernel('dev', true);
    // CHANGE: Comment the next line to disable cache loading
    //$kernel->loadClassCache();
    $request = Request::createFromGlobals();
A more complex solution can be found here:
https://gist.github.com/johnkary/601b4071a4b923b22ac2

I wrote  on how to use Notepad++ with XDebug  on this post.

Tuesday, November 24, 2015

Symfony and Doctrine - One-to-Many Bidirectional associations

As you could saw in the two previous posts I am using 2 entities, Ads and SpecificItems, found in a One-to-Many relationship unidirectional. One Ad can have none or several SpecificItems.
What makes it unidirectional is the fact that the Many side(specific items) contain "ads" objects, but the "ads" do not know which are their specific items.

Example of specific items :  2012 (year of manufacturing  of a car etc. SpecificItems holds only the value, the type name (Year of manufacturing, Color etc) is hold in a different class (entity).

Because of the unidirectional relation at this moment I am retrieving the data with 2 different queries, first query get the ads, the second one retrieve the specific items which have ads in the first query result. Bot quires return data as arrays and not as object (as I selected only some fields).

$ads = $em->getRepository('CPANAClassifiedsBundle:Ads')
->findAllAdsMyPagination($currentPage, $limit);
        
 $allSpecificItems = $em->getRepository('CPANAClassifiedsBundle:SpecificItems')

->findItemsForAdsList($ads);

And in the view I am combining the two arrays.

I feel that I am not using really the Doctrine's capabilities by retrieving arrays  instead of actual objects. It is also more natural for an Ad to contain the list of specific items.



Official documentation Doctrine associations:
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html

1. First step - modify the entities

 In the One side of the One to Many, in the Ads entity, I've added $items, which is a collection object, it will hold all the associated SpecificItems objects.


use Doctrine\Common\Collections\ArrayCollection;
........

/**
     * @var integer
     *
     * @ORM\OneToMany(targetEntity="CPANA\ClassifiedsBundle\Entity\SpecificItems", mappedBy="idAds")
     * @ORM\JoinColumn(name="items_id", referencedColumnName="id")
     */

    private $items;

    public function __construct() {
        $this->items = new ArrayCollection();
    }

 
    public function getItems()
    {
        return $this->items;
    }
  
    public function addItem(\CPANA\ClassifiedsBundle\Entity\SpecificItems $item) {
        $this->items[] = $item;
        $item->setIdAds($this);
        return $this;
    }
  
    public function removeItems($item) {
        $this->items->removeElement($item);
    }


In the Many side (SpecificItems entity) I make sure to specific the inversedBy clause:

 /**
     * @var \CPANA\ClassifiedsBundle\Entity\Ads
     *
     * @ORM\ManyToOne(targetEntity="CPANA\ClassifiedsBundle\Entity\Ads", inversedBy="items")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="id_ads", referencedColumnName="id_ads")
     * })
     */

    private $idAds;


  2. Modify the Repository

Make sure to add all the classes in the SELECT statement otherwise they will be loaded lazy resulting in a large number of  SELECTS into the database (N+1 queries issue in ORMs). I am using the pagination capabilities from Doctrine.
 
 public function findAllAdsCustom($currentPage = 1, $limit)
    {
        $query= $this->getEntityManager()
            ->createQuery(
                'SELECT  a,p,u,cat,c,ca,city,type
                FROM CPANAClassifiedsBundle:Ads a
                LEFT JOIN a.items p
                LEFT JOIN a.idUser u
                LEFT JOIN a.idCategory cat
                LEFT JOIN a.idCurrency c
                LEFT JOIN a.idCityArea ca
                LEFT JOIN ca.idCity city
                LEFT JOIN p.idSpecificItemType type
                ORDER BY a.idAds'
               
            );
           
        $paginator = $this->paginate($query, $currentPage, $limit);

        return $paginator;
    }
   
    public function paginate($dql, $page, $limit)
    {
        $paginator = new Paginator($dql);
        $paginator->setUseOutputWalkers(false);
          
        $paginator->getQuery()
            ->setFirstResult($limit * ($page - 1)) // Offset
            ->setMaxResults($limit); // Limit

        return $paginator;
    }

3. The controller

No need to call a special query for retrieving the SpecificItems, evrything will be now return in the all_ads array which will contain objects of the Ads class. And these Ads objects contain now the $items collection.
 
 public function showAllAction($currentPage)
    {
        $limit = 1;  // limit variable holds the number of ads per page
        $em = $this->getDoctrine()->getManager();
        $ads = $em->getRepository('CPANAClassifiedsBundle:Ads')->findAllAdsCustom($currentPage, $limit);
      
        $totalAds = $ads->count();
        $maxPages = ceil($totalAds/$limit);
        $thisPage = $currentPage;
      
      
        if (!$ads) {
            throw $this->createNotFoundException('Unable to find ads.');
        }
      
        return $this->render('CPANAClassifiedsBundle:Pub\Ads:show_all.html.twig', array(
            'all_ads' => $ads,
            'maxPages'=>$maxPages,
            'thisPage' => $thisPage,
        ) );
    }


4. The view

 {% extends 'CPANAClassifiedsBundle::layout.html.twig' %}

{% block leftcolumn %} {{ render(controller('CPANA\ClassifiedsBundle:Pub/Categories:index')) }} {% endblock %}

{% block content %}

<table style="width:100%">
    {% for ad in all_ads %}  
        <tr><hr></tr>
        <tr><h1> {{ ad.title }}</h1></tr>
        <tr><p><b>Posted by: </b> {{ ad.idUser.name }}</p></tr>
        <tr><p><b>Category: </b> {{ ad.idCategory.categoryName }}</p></tr>
        <tr><p><b>Area: </b> {{ ad.idCityArea.cityAreaName }}</p></tr>
        <tr><p><b>City: </b> {{ ad.idCityArea.idCity.cityName }}</p></tr>
        <tr><h2>Description</h2></tr>
        <tr><p>{{ ad.description }} </p></tr>
      
        {% for item in ad.items %}
            <tr><p><b> {{ item.idSpecificItemType.itemTypeName  }} </b> - {{ item.itemValue }} </p> </tr>
        {% endfor %}
      
        <tr><p><b>Price: </b> {{ ad.price }} -- <b> {{ ad.idCurrency.currencyName }} </b> </p></tr>
      
      
    {% endfor %}

</table>

    {% if maxPages > 1 %}
        <ul>
            {%if thisPage > 1 %}
                <li >
                    <a href="{{ path('cpana_classifieds_public_ads_show_all', {currentPage: thisPage-1 < 1 ? 1 : thisPage-1}) }}">«</a>
                </li>
            {% endif %}
     
            {# Render each page number #}
            {% for i in 1..maxPages %}
                <li>
                    <a href="{{ path('cpana_classifieds_public_ads_show_all', {currentPage: i}) }}">{{ i }}</a>
                </li>
            {% endfor %}

            {# `»` arrow #}
            {%if thisPage < maxPages %}
                <li>
                    <a href="{{ path('cpana_classifieds_public_ads_show_all', {currentPage: thisPage+1 <= maxPages ? thisPage+1 : thisPage}) }}">»</a>
                </li>
            {% endif %}
        </ul>
    {% endif %}

{% endblock %}


5. See the results

 When loading the "Show all ads" I can see them nicely paginated and also having the associated SpecificItems listed. In total 3 queries were executed to obtain the result, same as before when I had One-To-Many unidirectional relationship between ads and specific items.