Home » Php » php – Symfony2 + Propel Collection undefined offset: 2

php – Symfony2 + Propel Collection undefined offset: 2

Posted by: admin July 12, 2020 Leave a comment

Questions:

We have created a collection using propel and Symfony2 forms. We can save the form without any problems and we can add a second option using the collection. If we then save and then try to add a 3rd collection in we get the following error:

Notice: Undefined offset: 2 

Stack Trace

in src/app/MyBundle/Model/om/BaseLabelsLabelsLinesMapsQuery.php at line 241  

$cton0 = $this->getNewCriterion(LabelsLabelsLinesMapsPeer::ID, $key[0], Criteria::EQUAL);
            $cton1 = $this->getNewCriterion(LabelsLabelsLinesMapsPeer::LABEL_ID, $key[1], Criteria::EQUAL);
            $cton0->addAnd($cton1);
            $cton2 = $this->getNewCriterion(LabelsLabelsLinesMapsPeer::LABEL_LINES_ID, $key[2], Criteria::EQUAL);
            $cton0->addAnd($cton2);
            $this->addOr($cton0);
        }

I have posted the relevant code below, however as there is quiet a substantial amount of code. We were wondering if anybody had experienced this same issue.

I have sent a bug report over with a different bit of code which created the same error however I received no reply. The bug report is here.

This is a snippet of the relevant schema:

<table name="labels_labels_lines_maps" isCrossRef="true">
    <column name="id"
            type="integer"
            required="true"
            autoIncrement="true"
            primaryKey="true"/>
    <column name="label_id"
            type="integer"
            primaryKey="true"/>
    <column name="label_lines_id"
            type="integer"
            primaryKey="true"/>
    <foreign-key foreignTable="labels" onDelete="cascade">
        <reference local="label_id" foreign="id"/>
    </foreign-key>
    <foreign-key foreignTable="labels_lines" onDelete="cascade">
        <reference local="label_lines_id" foreign="id"/>
    </foreign-key>
    <vendor type="mysql">
        <parameter name="Engine" value="InnoDB" />
        <parameter name="Charset" value="utf8" />
    </vendor>
</table>

<table name="labels_lines">
    <column name="id"
            type="integer"
            required="true"
            autoIncrement="true"
            primaryKey="true"/>
    <column name="placeholder_text"
            type="varchar"
            size="150"/>
    <column name="font_id"
            type="integer"/>
    <column name="font_size"
            type="integer"/> 
    <column name="x_axis"
            type="integer"/>  
    <column name="y_axis"
            type="integer"/>
    <column name="width"
            type="integer"/>      
    <column name="height"
            type="integer"/>       
    <column name="colour"
            type="varchar"
            size="20"/>        
    <column name="angle"
            type="integer"/> 
    <column name="is_volume"
            type="boolean"/>
    <column name="is_percentage"
            type="boolean"/>
    <column name="is_productof"
            type="boolean"/>
    <column name="is_type"
            type="boolean"/>
    <column name="is_occasion"
            type="boolean"/>        
    <foreign-key foreignTable="font" onDelete="cascade">
        <reference local="font_id" foreign="id"/>
    </foreign-key>
    <vendor type="mysql">
        <parameter name="Engine" value="InnoDB" />
        <parameter name="Charset" value="utf8" />
    </vendor>
</table>

<table name="occasion">
    <column name="id"
            type="integer"
            required="true"
            autoIncrement="true"
            primaryKey="true"/>
    <column name="occasion"
            type="varchar"
            size="200"/>

    <vendor type="mysql">
        <parameter name="Engine" value="InnoDB" />
        <parameter name="Charset" value="utf8" />
    </vendor>
</table>

<table name="font">
    <column name="id"
            type="integer"
            required="true"
            autoIncrement="true"
            primaryKey="true"/>
    <column name="name"
            type="varchar"
            size="100"/>
    <column name="location"
            size="300"/>
    <vendor type="mysql">
        <parameter name="Engine" value="InnoDB" />
        <parameter name="Charset" value="utf8" />
    </vendor>
</table>

Below is the view (without any styling):

{{ form_start(form) }}
{{ form_row(form._token) }}
<ul class="labelsliness list-group" data-prototype="{{ form_widget(form.labelsliness.vars.prototype)|e }}">
                    {% for lines in form.labelsliness %}
                        <li>{{ form(lines) }}</li>
                    {% endfor %}
                </ul>

                {{ form_row(form.save) }}

{{ form_end(form) }}

<script>
                    var $collectionHolder;

                    var $addLinesLink = $('<button class="add_line_link btn btn-primary">Add a line</button>');
                    var $newLinkLi = $('<li></li>').append($addLinesLink);

                    $(document).ready(function(){
                       $collectionHolder = $('ul.labelsliness');

                       $collectionHolder.append($newLinkLi);

                       $collectionHolder.data('index', $collectionHolder.find(':input').length);

                       $addLinesLink.on('click', function(e) {
                          e.preventDefault();

                          addLineForm($collectionHolder, $newLinkLi);
                       });
                    });

                    function addLineForm($collectionHolder, $newLinkLi) {
                        var prototype = $collectionHolder.data('prototype');

                        var index = $collectionHolder.data('index');

                        var newForm = prototype.replace('/__name__/g', index);

                        $collectionHolder.data('index', index + 1);

                        var $newFormLi = $('<li></li>').append(newForm);

                        $newFormLi.append('<button class="remove-line btn btn-danger">Remove</button>');

                        $newLinkLi.before($newFormLi);

                        $('.remove-line').click(function(e){
                           e.preventDefault();

                           $(this).parent().remove();

                           return false;
                        });
                    }
                </script>

The Form handling this:

public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder
               ->add("labelsliness", "collection", array(
                   "type" => new LabelsLinesType(),
                   "allow_add" => true,
                   "allow_delete" => true,
                   "by_reference" => false
               ))
               ->add("save", "submit");
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AJSharp\EPCBundle\Model\Labels',
        ));
    }

    public function getName()
    {
        return "label_form";
    }

Finally, below is the controller.

public function editAction(Request $request, $id = null)
    {

        $labels = LabelsQuery::create()->findPk($id);

        $form = $this->createForm(new EditLabelType(), $labels);

        $form->handleRequest($request);

        if ($form->isValid()) {

            $labels->save();
            return $this->redirect($this->generateUrl("_admin_labels"));
        }

        return $this->render("AppLabelBundle:Admin:edit.html.twig", array("form" => $form->createView()));
    }
How to&Answers:

Your schema is a little confusing to me. This bit below includes three primary keys with two of them as a foreign key and one of them as a unique identifier for the row:

<table name="labels_labels_lines_maps" isCrossRef="true">
    <column name="id"
            type="integer"
            required="true"
            autoIncrement="true"
            primaryKey="true"/>
    <column name="label_id"
            type="integer"
            primaryKey="true"/>
    <column name="label_lines_id"
            type="integer"
            primaryKey="true"/>
    <foreign-key foreignTable="labels" onDelete="cascade">
        <reference local="label_id" foreign="id"/>
    </foreign-key>
    <foreign-key foreignTable="labels_lines" onDelete="cascade">
        <reference local="label_lines_id" foreign="id"/>
    </foreign-key>
    <vendor type="mysql">
        <parameter name="Engine" value="InnoDB" />
        <parameter name="Charset" value="utf8" />
    </vendor>
</table>

I suspect a lot of your trouble may go away if you pick one thing and stick with it. Either remove id and have your primary composite key represent two foreign tables, which is perfectly valid, or change your PRIMARY KEYs to UNIQUE constraints on each of your foreign keys, leaving your primary key as id. This is also perfectly valid. Ultimately your decision will be based on your design requirements.

Another note: You may or may not want to do a heavyIndexing, particularly if you’re referencing individual columns in the primary key. A composite primary key creates an index against all three columns, not each individually. That may or may not be important in your project but I thought it would be worth pointing out.

Answer:

I’m not 100% sure on this, but I’m fairly certain it is because this line:

$labels = LabelsQuery::create()->findPk($id);

You’ll notice in the method findPkSimple in your BaseQuery class it is expecting that the variable key be an array with 3 values (indices 0, 1, 2)

The reason why I’m unsure is I don’t know if $id is just one value or an array as the function definition you have doesn’t restrict on type. (your editAction function)

I think, because you have three primary keys, the class that gets generated expects you to have three individual values when searching by primary key.

It might be more effective if you only have one primary key, and then an unique index on those three columns to ensure uniqueness (if that’s what you’re going for)