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();
     // ....
}
November 6, 2013

Copy table row data to a form in html

Joel Paxton’s Question:

I have a page which has a form/table.

I want to be able to click on a button at the end of a row, or the row itself and copy this data into another form on a separate html page, which can then be edited.

I know it probably has something to do with JQuery, however I have little to no experience with that.

If you require more details, I will happily provide.

EDIT:

Here is what it looks like now (it’s a table which has retrieved data from an xml file using SimpleXML):

<form name ="editEvent" method="post" action="editEvent.php">
    <table border="1">
        <tr bgcolor="#FFA500">
            <th>ID #</th>
            <th>Name</th>
            <th>Start Time</th>
            <th>End Time</th>
            <th>Category</th>
            <th>Description</th>
            <th>Location</th>
            <th>Picture Path</th>
            <th>Edit/Delete</th>
        </tr>   <tr>
          <td>1
          <td>Climbing</td>
          <td>09:00</td>
          <td>09:30</td>
          <td>Physical</td>
          <td>Description of what is going on</td>
          <td>where it is</td>
          <td>a photo link</td>
          <td><input type="submit" name="edit" class ="box" value="Edit/Delete"/></td>
      </tr> 
    </table>

I want it to end up in a table like this:

<tr>
        <td><input type="text" name="name" placeholder="Enter new event name..."/></td>
        <td><input type="text" name="time" placeholder="Enter event start time..."/></td>
        <td><input type="text" name="endtime" placeholder="Enter event end time..."/></td>
        <td><input type="text" name="category"/></td>
        <td><input type="text" name="description" placeholder="Enter a description of the event..."/></td>
        <td><input type="text" name="loc"/></td>
        <td><input type="text" name="picturePath" placeholder="Enter link to picture..."/></td>
        <td><input type="submit" name="create" class="box" value="Create"/></td>
    </tr>

Honestly, any help or even pointers in the right direction would be appreciated. I really don’t know what to do here. I’ve tried searching these forums and Google, but all I found is stuff on SQL and databases. I just want to transfer some HTML table row data on one page to a HTML form on another to be edited.

You need some methods of identification on the columns like class name. For eg: <td class="name">Climbing</td> then you can attach an event handler on the td of the row and fetch all the data and populate the form.

$("td").on('click', function() {
   var tr = $(this).parent('tr');
   var name = tr.find(".name").text();
   // Grab other values like this

   // and later populate the form
});

However, Instead of copying the data, an efficient solution would be to hold the primary key of the row in one of the td or on one of the data attributes and use it to fetch the record from the database and then fill up the form.

May 29, 2013

How to get value of radio button with dynamically generated name

Daniel Young’s Question:

I have a whole bunch of radio buttons with dynamically generated names like below:

        <input type="radio" id="Red<?php echo $wineID; ?>" name="category<?php echo $wineID; ?>" value="Red" <?php if ($category == "Red") { ?>checked="true"<?php } ?>>
          <label for="Red<?php echo $wineID; ?>">Red</label>

        <input type="radio" id="White<?php echo $wineID; ?>" name="category<?php echo $wineID; ?>" value="White" <?php if ($category == "White") { ?>checked="true"<?php } ?>>
          <label for="White<?php echo $wineID; ?>">White</label>

        <input type="radio" id="Sparkling<?php echo $wineID; ?>" name="category<?php echo $wineID; ?>" value="Sparkling" <?php if ($category == "Sparkling") { ?>checked="true"<?php } ?>>
          <label for="Sparkling<?php echo $wineID; ?>">Sparkling</label>

I need to get the selected value and add it into my dataString for an ajax call to update my database. How can this be done using jQuery?

You can use attribute selector to get the element

$('input[name="category<?php echo $wineID; ?>:selected"')

However this uses a PHP inline script, so it will only work if rendered at the page load.

Or easiest would be:

console.log($(":radio:selected").val());
April 21, 2013

HTML File Upload action – can this be hacked to spam endless file uploads

Adam’s Questions:

I have a question about hacking file uploads. Below shows the kind of setup I’m using and my concern is around the action that gives the full path to the upload script:

<form action="http://www.mydomain.com/scripts/php/photo_processing.php?page=join method="post" enctype="multipart/form-data">
  <input type="file" name="file" class="fileProfile"><br>
</form>

Can someone use the full path to send repetitive files constantly and then fill a web server disk space etc? eg: can you send files using this path outside the website and/or in a way that allows automated constant uploads?

Note: the php file has the following at the top – it is set to only this domain name – needed because of AWS Cloudfront POST limitation

header("Access-Control-Allow-Origin: http://www.mydomain.com");

No, they are basically prevented by cross domain policy. Unless the mydomain.com gives you access to it.

Proper Form Input Sanitation

Undermine2k’s Questions:

I have form fields i’m gathering from my form using

 var dataString = $("form").serialize();

I am then sending this over to my controller as dataType “html”

The var_dump for my dataString looks like this (so far, but it will contain email address, select options, etc)

array(3) {
  ["username"]=>
  string(5) "mikey"
  ["firstname"]=>
  string(4) "tes%"
  ["lastname"]=>
  string(6) "tester" }

my question is as follows: What is the proper method of form sanitation i should be using before I send data to my model? I know I need to strip special characters and the like, is there some prepackaged class I should be using?

Do I need to break my data up like

  $username =  trim(Array[0]) ; 

Enable XSS Filtering on application/config/config.php

$config['global_xss_filtering'] = TRUE;
February 24, 2013

POST variable is not being saved correctly

Question by user2078757

Im using method post to send a mutliple input text form, i draw information from the database to after re insert the information which is inside input text:

echo "<CENTER><TABLE BORDER='0'>";
echo "<FORM METHOD='POST'>";
$sele_players = "SELECT nombre FROM JUGADORES WHERE NOM_EQUIPO='Aston villa'";
        $sele_players = mysql_query( $sele_players , $link );

        while( $row = @mysql_fetch_assoc( $sele_players ) )
        {
            $row['nombre'] = addslashes( $row['nombre'] );
            echo "<TR><TD ALIGN='CENTER'>".$row['nombre']."</TD>";
            echo "<TD><INPUT TYPE='TEXT' NAME='{$row['nombre']}'></TD></TR>";
        }

        echo "<TR><TD COLSPAN='2' ALIGN='CENTER'><INPUT TYPE='submit' NAME='send2' VALUE='INSERTAR' style='width:200px; height:60px' ></TD></CENTER></TR>";

ok here i get the names of players from database, then i use them for insert inside input text as his name, to after pick with array $_POST:

    if( !empty( $_POST['send2'] ) )
    {

        foreach($_POST as $jugador => $points)
        {
            $jugador = str_replace( "__" ,". ", $jugador );
            $jugador = str_replace( "_" ," ", $jugador );

            if( $points == "" )
            {
                $points = "NULL";
            }

            $inser_jornada = "INSERT INTO JORNADA VALUES( '{$_GET['jornada']}','{$_GET['equipo']}', '$jugador', '$points', now() );";

So there is no problem with most of names, excluding N’Zogbia name or apostrophe names which is shown in $_POST array as ‘N’, i have tried adding slashes before send it through from but doesnt work, so i dont know how to get the complete name in post array, thats the main problem.

THanks forwarded!!

Answer by Starx

There are many things to point out here. But instead of that, I will try my best to be helpful.

Add your database entries using mysql_real_escape_string($variableName) to enter the content to the database. It will automatically escape such quotes and make it a little SQL Injection proof.

May 5, 2012

Get all array keys that is not equal to X

Question by Oyvind Andersson

I have several files uploaded with a form that is processed by a PHP script. There are several “categories” that I need to have handles separate ways. In other words, I need some of the files in the $_FILES array to be stored someplace, and some others stored in other places.

So what I need is all files (array keys) that are not $_FILES[‘filename_form’] to be stored / used separably.

The reason why I need this is because the amount of files being uploaded are dynamic, but always either $_FILES[‘filename_type1’] or $_FILES[‘filename_type2_*] where * is a number that increments when more files are input from the form (dynamically)

Might be a very simple solution, either I’m too tired or maybe I’ve been staring too much at the code, but all I know is I need a solution before tomorrow morning, so it’s time sensitive =P

Appreciate all the help I can get.
Thank you!

Answer by Zombaya

$files = array();
$filenames = array();
foreach($_FILES as $name => $fileInfo)
{
    if($name != 'filename_form')
    {
        $files[$name] = $fileInfo;
        $filenames[] = $name;
    }
}

Answer by Starx

Here is an alternate way

$keys = array_keys($thearray);
$filteredArray = array_filter($keys, 'fiterKeys');

function filterKeys($item) {
    if($item != 'valuetocheck') return true;
    else return false;
}
April 29, 2012

Javascript confirmbox + form inside it

Question by Waldema

I’m having a PHP/MySQL giftlist and people can click “reserve”-button to reserve one piece of gift. Thing is that in that list there is gift which has been wished like 10 times. So to reserve all 10 pieces of certain gift one has to click the reserve-button 10 times and javascript always confirm if one is willing to reserve one piece of that gift.

What I’m now after is that change that javascript confirm to confirm+form which will send data to action.php.

In HTML it would look like this:

<p>How many piece of gift are you willing to reserve?</p>
<form action="action.php" method="post">
    <input type="text" name="giftammount" />
</form>

And when confirm returns true it will submit the answer and action.php will do the magic for MySQL table.

So, is this even possible technically? Not that i’ve tried, i’ve spent several hours wondering this but can’t figure this out.

Answer by codeM0nK3Y

If you’re using jQuery UI then the dialog widget could be just what you need:

Use a modal dialog to require that the user enter data during a multi-step process. Embed form markup in the content area, set the modal option to true, and specify primary and secondary user actions with the buttons option.

Take a look at the jQuery UI page for an example and basic code

Answer by Starx

Javascript confirm box and a form are two totally different things.

One way you can solve this, is creating a modal box and simulating a confirm box + confirm dialog effect.

Few other functions like alert(), confirm() and prompt() only take text but not HTML

April 26, 2012

Can not prevent form submit when I add one line

Question by tosin

I am stacked. My code can not prevent defaut action of submit. This was working fine until I add this.doSomething();.

Why this happens? Do I have to use preventDefault?

working code: http://jsfiddle.net/WwW5R/

HTML:

<div id="container">
    <form action="" method="post" id="input">
            <input type="submit">
    </form>
</div>

JavaScript:

$(function() {
    var ReserveUI = function($el) {
        this.$form = {
            input: $el.find('#input')
        };
        this._eventify();
    };

    ReserveUI.prototype.doSomething = function() {
        return false;
    }

    ReserveUI.prototype._eventify = function() {
        this.$form.input.submit(function() {
            this.doSomething(); //if comment out this line, it works
            return false;
        });
    };
    var UI = new ReserveUI($("#container"));
});

thanks for reading:)

Answer by Alnitak

In your submit callback function, this no longer refers to your object, but to the element itself.

It’s therefore causing an exception because the element has no doSomething property, and your return false is skipped.

Instead, write this:

ReserveUI.prototype._eventify = function() {
    var self = this;
    this.$form.input.submit(function(e) {
        e.preventDefault(); // cancels event even if subsequent lines fail
        self.doSomething();
    });
};

See http://jsfiddle.net/xgGGx/1/ for a working example showing that it’s just the scope issue causing the bug.

This is what script debugging tools are for – the reported error should have made the fault reasonably obvious…

Answer by Starx

This is a scope mismatch.

this.$form.input.submit(function() { //here "this" is ReserveUI
    this.doSomething(); //here "this" is input button
    return false;
});

And since there is no doSomething() on input button, the script breaks thus no longer executing the portion to return false.

Here is a way you can get around this

ReserveUI.prototype._eventify = function() {
    var $this = this; //create a reference to the object
    this.$form.input.submit(function() {
        $this.doSomething(); //Now call the object method
        return false;
    });
};

Demo

April 25, 2012

Seach.php is not working and not showing any results

Question by Erik

I’m trying to figue out how to make my search.php script work with mySQL. I can’t get the information to show up. Not sure where the problem is.

PAGE 1:

<form action="search_result.php" method="GET">
    <input type="text" name="reg" />
    <input type="submit" value="Search" />
</form>

PAGE 2:

<?php
$host="localhost";
$username="XXXXXXXXXXX";
$password="XXXXXXXXXXX";
$db_name="XXXXXXXXXXXX";
$tbl_name="reg_add";
mysql_connect("$host", "$username", "$password") or die("cannot connect");
mysql_select_db("$db_name")or die("cannot select DB");


$record = $_POST['record']; // if coming from e.g. a form
$result=mysql_query(" SELECT * FROM reg_add WHERE reg='" . mysql_real_escape_string($record) . "'");


$row = mysql_fetch_assoc($result);
$first_name = $row['first_name'];
$last_name = $row['last_name'];
$reg = $row['reg'];
?>

<input  name="reg" value="<? echo "$record" ?>">

<input  name="first_name" value="<? echo "$first_name" ?>">

<input  name="last_name" value="<? echo "$last_name" ?>">

Answer by Adriaan

You form is method GET and in your PHP you use this:

$record = $_POST['record']; // if coming from e.g. a form

How are you gonna get the POST[‘record’] if your form has the method GET?

I guess you should or change your form to:

method="POST" 

or change your $record in php to:

$record = $_GET['record'];

Try this version:

You form:

    <form action="search_result.php" method="POST">
    <input type="text" name="reg" id="reg" />
    <input type="submit" name="Submit" id="Submit" value="Search" />
</form>

search_result.php :

<?php

$host       ="localhost";
$username   ="XXXXXXXXXXX";
$password   ="XXXXXXXXXXX";
$db_name    ="XXXXXXXXXXXX";
$tbl_name   ="reg_add";

/* Connect to MySQL database */
mysql_connect("$host", "$username", "$password") or die("Error connecting to database");
mysql_select_db("$db_name")or die("Error selecting database");

$error = '';

if (isset($_POST['Submit'])) {


    if (!empty($_POST['reg'])) {

        $record = $_POST['reg']; // if coming from e.g. a form

        $query = mysql_query("SELECT * FROM reg_add WHERE reg='" . mysql_real_escape_string($record) . "'");
        $result = mysql_num_rows($query);

        if ($result != 0) {

            $row = mysql_fetch_array($query);

            $first_name = $row['first_name'];
            $last_name = $row['last_name'];
            $reg = $row['reg'];

        } else {

            $error = 'No result have been found!';

        }

    } else {

        $error = 'You have not entered the search field, <a href="javascript:history.back(1)">Go back</a>.';

    }
}

if (!empty($error)) { echo $error; } 
?>


<input  name="reg" value="<? echo $record; ?>">

<input  name="first_name" value="<? echo $first_name; ?>">

<input  name="last_name" value="<? echo $last_name; ?>">

Answer by Starx

Syntax of mysql_result() is wrong. According to the manual, it should be

string mysql_result ( resource $result , int $row [, mixed $field = 0 ] )

SO the correct way to use it would be like

mysql_result($result, 1, "first_name");
...

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