Symfony2 + Propel Collection undefined offset: 2
Asked Answered
C

2

8

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()));
    }
Chestonchest answered 13/10, 2015 at 14:49 Comment(2)
and your BaseLabelsLabelsLinesMapsQuery.php code is?Goldfarb
@Goldfarb as there are a few number of lines in the script I've copied it into pastebin. pastebin.com/WXgfN7yf . Please note that this code is completely generated by Propel.Chestonchest
G
1

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.

Gunther answered 28/10, 2015 at 21:27 Comment(1)
Hi there, thank you for the mention of heavyIndexing. I've actually never used it so I will have a look into this. I was originally going on the following schema (propelorm.org/documentation/cookbook/symfony2/mastering-symfony2-forms-with-propel.html#many-to-many-relations) However, as you have pointed out. Id isn't actually in the many to many schema. I beleive this could possibly be the root cause of the issue. I will have a look and come back to you.Chestonchest
B
0

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)

Brion answered 28/10, 2015 at 19:38 Comment(2)
Just to clarify that $id is a single id and not an array. As for having one primary key. I have taken the following from here (propelorm.org/documentation/cookbook/symfony2/mastering-symfony2-forms-with-propel.html#many-to-many-relations) which shows the multiple primary keys. However id is not actually on this map in th example.Chestonchest
I think that example might be either out of date, or just completely wrong. In their example they have a primary key that is also a foreign key.Brion

© 2022 - 2024 — McMap. All rights reserved.