...

Hi! I’m Starx

experienced Software Developer. And this is my blog
Start Reading About me
Blog Page
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);
                }
            }
    
        }
    }
    
Read more
April 8, 2016

javascript switch statement/case expression

RGdent’s Question:

I am using a switch statement to search for undefined values to manually change it. but I am having trouble using boolean expressions as I would when using an if statement.

Like: if statemant

if(Item1 == undefined)
{
  item1 ="No";
}
else if (Item2 == undefined)
{
  item2 = "No";
}

etc..

I tried this with the switch statement:

 switch (array) {
 case (item1 == undefined):
 item1 = "No";
 console.log('item1 result', item1 );
 break;
 case item2 == undefined:
 item2 = "No";
 console.log('item2 result', item2 );
 break;
 default:

 }

It does not run through the switch statement, except for when I remove == undefined and only use item1. then it works?

The switch cannot evaluate values of the array like that and that is why it does not run through the switch statement. You need to define which value of that array you want to switch.

Inside case statement you also cannot use expression, you have to use a value there as well.

So, if you are dead set on using switch for what you are trying to accomplish, you can do something like this:

item1 = array[1];
switch(item1) {
    case "undefined":
        // so on
    break;
}

But, based on your example you are probably trying to check if the values are set or not, for that if statements are still the best choice rather than switch.

$arr = []; // Your array
if(typeof $arr[0] == "undefined") {
    $arr[0] = "No";
}
Read more
April 5, 2016

MVC Design – views with content for a usergroup

H0mebrewer’s Question:

I’m writing my first MVC application in PHP. I don’t want to use any framework.

Now I’m searching for a good solution or best practise to display elements for users in views, only if they are allowed to interact with the function that a specific element calls.

The app loads a template file in the view.
Within the template file I show the data from the model, which is passed from the controller to the view.

From controller

public function showUserList() {
    $userList = $this->model->getUserList();
    $this->view->loadTemplate($userList, 'user_list.php');
}

From view:

class user_view  {
    public function loadTemplate($data, $file, $buffer= false){
        if($buffer === true){
            ob_start();
            include dirname(__FILE__).'/templates/'.$file;
            $c = ob_get_contents();
            ob_end_clean();
            return $c;
        }else{
            include dirname(__FILE__).'/templates/'.$file;
        }
    }
}

Template:

<table class="table" id="data-table">
    <thead>
        <tr>
        <th>Name</th>
        <th>Username</th>
        <th>E-Mail</th>
        <th>Group</th>
        <th>Action</th>
        </tr>
    </thead>
    <tbody>
    <?php
       if(is_array($data)){

          foreach($data as $key => $value){
          ?>
            <tr>
                <td><?php echo $data['name'].' '.$data['surname']; ?></td>
                    <td><?php echo $data['abbr']; ?></td>
                    <td><?php echo $data['email']; ?></td>
                    <td><?php echo $data['gr_name']; ?></td>
                    <td>

                    <div data-access="," onclick="$user_controller.showUserChangeView('<?php echo $data['id']; ?>')" class="btn btn-primary">edit</div>
                    <div data-access="checkAccess" onclick="$user_controller.deleteUser('<?php echo $data['id']; ?>')" class="btn btn-danger">delete</div>

                    </td>
                </tr>

          <?php
          }

       }
    ?>
    </tbody>
</table>
</div>

I want to display buttons or menuitmes only, if a user is permitted to call the function of the button or the menuitem.

My first approach ist to create a template file with all elements which could be displayed. Elements which should not be accessible for all users get an data attribute.

The view loads the template, a DOM parser pareses the file, checks if the user is permitted to call that function with a data attribute.
If the user is not permitted to call that function, the element will be removed.
This is not yet implemented because I’m not sure if this is that a good solution or how I can do it even better?

My suggestion is to implement either ACL (Access Control List) or RBAC (Role Based Access Control) depending on what you find useful. I prefer RBAC so I will give a minimalistic example of one.

Create a list of all possible actions a user can make and define roles which might have acess to them.

// List of permissions (the actual term to define resources in RBAC) for the users
$actions = array(
    'view' => array(ROLE_USER, ROLE_OWNER, ROLE_MANAGER, ROLE_ADMIN),
    'edit' => array(ROLE_OWNER, ROLE_ADMIN),
    'delete' => array(ROLE_OWNER, ROLE_ADMIN),
    'change_pass' => array(ROLE_OWNER, ROLE_MANAGER, ROLE_ADMIN)
)

Now when you display the button for these actions, you can check if current user has any of the role which allows him to access the actions. If answer to that is “yes” then show them otherwise don’t show them at all.

$currentUserRole = '..'; //Retrieve however you want
foreach($actions as $allowed_roles) {
    if(in_array($currentUserRole, $allowed_roels)) {
        echo "<div ...>"; // Basically show the action
    }
    // If not carry on to next action with out showing
}

(Note: Not showing only might not be enough for it be secured, so we must also the check then when executing the action as well)

Update: You would the put the above code in the following part:

<table class="table" id="data-table">
    <thead>
        <tr>
        <th>Name</th>
        <th>Username</th>
        <th>E-Mail</th>
        <th>Group</th>
        <th>Action</th>
        </tr>
    </thead>
    <tbody>
    <?php
       if(is_array($data)){

          foreach($data as $key => $value){
          ?>
            <tr>
                <td><?php echo $data['name'].' '.$data['surname']; ?></td>
                    <td><?php echo $data['abbr']; ?></td>
                    <td><?php echo $data['email']; ?></td>
                    <td><?php echo $data['gr_name']; ?></td>
                    <td>
                        $currentUserRole = '..'; //Retrieve however you want
                        foreach($actions as $allowed_roles) {
                            if(in_array($currentUserRole, $allowed_roels)) {
                               echo "<div ...>"; // Basically show the action
                            }
                            // If not carry on to next action with out showing
                        }
                    </td>
                </tr>

          <?php
          }

       }
    ?>
    </tbody>
</table>
</div>
Read more
April 1, 2016

How to create time attribute in SQL?

Agus Maloco’s Question:

I tried to create a table this way:

create table attendance (
      userId char(10) primary key not null, 
      name varchar(35) not null, 
      date_attendance date not null, 
      start_time timestamp 'HH24:MI:SS', 
      finish_time timestamp 'HH24:MI:SS'
);

Am I right about creating the time fields this way or there is some better option?

Timestamp will hold both date and time. There is data type time as well, which might be better suited for your use case.

CREATE TABLE attendance (
      userId char(10) primary key not null, 
      name varchar(35) not null, 
      date_attendance date not null, 
      start_time time,
      finish_time time
);
Read more
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.

Read more

How to make symfony forms support datetime-local input type?

Starx’s Question:

Most of the browsers are dropping support for datetime and also datetime-local as a valid input type. As of the time of writing this question, there are more support for support for datetime-local than datetime(which is almost non-existent).

If you building forms using Symfony’s form builder, it supports datetime but not datetime-local. So how would you make symfony form builder accept datetime-local input type and keep the rest of the functionality of the input type same?

One way this problem can be solved is, if we can change the text of input type to say datetime-local which can be done by overwriting the DateTimeType and using that.

<?php

namespace AppBundleComponentFormExtensionCoreType;

use SymfonyComponentFormFormInterface;
use SymfonyComponentFormFormView;

class DateTimeType extends SymfonyComponentFormExtensionCoreTypeDateTimeType
{
    /**
     * {@inheritdoc}
     */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['widget'] = $options['widget'];

        // Change the input to a HTML5 datetime input if
        //  * the widget is set to "single_text"
        //  * the format matches the one expected by HTML5
        //  * the html5 is set to true
        if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
            $view->vars['type'] = 'datetime-local';
        }
    }

}
Read more
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
});
Read more
March 18, 2016

Symfony2 form collection not calling addxxx and removexxx even if 'by_reference' =&gt; 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();
     // ....
}
Read more
March 4, 2016

JS/HTML – onresize doesn't fire

LastSecondsToLive’s Question:

I wanted to add the resize event to an element (I know you can’t add resize to a specific element, but it would be fine if the JS-function would be triggered on a resize of any kind – not the specific element).

I was thinking about this:

<div onresize="wrapperResize();"></div>

This somehow doesn’t work (Safari user here). This however works:

<div onclick="wrapperResize();"></div>

Currently wrapperResize() only holds a simple console.log(). What could I do about this?

PS: This works great (traditional checked), what do I even do different?

EXTRA: The div has a fixed height and 33% of the body element wide. Even though the event should fire, when the window is resized I thought this may be the cause.

I think you should set onresize handler on window and do it in javascript, not inline html.. Like so

window.onresize = yourFunction

You cannot attach a onresize event on a <div> container. It is only available for window for most of the browsers.

Read more
...

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