Serializing data provider on session

I have a model A that contains several model B. In _form for A there is a grid view listing all B, where each column contains input fields to edit them. Because I don’t want to save any changes made to any of the B models before the user explicitly saves the A model, but I still have to persist the state somewhere, I’m trying to serialize the data provider on the session and kind of use the data provider as the database until the user submits the form.

Here’s a wall of code… you probably don’t need to read too much of it before reading my problem below.

relevant snippet of views/a/_form.php:




    <?php

    /* @var $this AController */

    /* @var $model A */

    /* @var $state string */

    /* @var $form CActiveForm */

    ?>


    <?php

    $form = $this->beginWidget(

        'CActiveForm',

        array(

            'id'                   => 'form',

            'enableAjaxValidation' => true,

        ));

    ?>


    <div class='row'>

        <?php echo CHtml::hiddenField('state', $state); ?>

        <?php echo $form->hiddenField($model, 'id'); ?>

        <?php echo $form->errorSummary($model); ?>

    </div>


    <?php

    $dataProvider = new CActiveDataProvider('B');

    $dataProvider->setData(Yii::app()->user->getState($state));


    $this->widget(

        'zii.widgets.grid.CGridView',

        array(

            'id'             => 'grid-view',

            'dataProvider'   => $dataProvider,

            'selectableRows' => 0,

            'summaryText'    => '',

            'enableSorting'  => false,

            'ajaxUrl'        => Yii::app()->createUrl(

                $this->getId() . '/' . $this->getAction()->getId(),

                array('id' => $model->id, 'state' => $state)),

            'columns'        => array(

                array(

                    'name'        => 'prop_a',

                    'value'       => getInputA(),

                    'type'        => 'raw'

                ),

                //etc

            )

        ));

    ?>

    <div>

        <?php

        echo CHtml::ajaxLink(

            CHtml::image('images/add.png', Yii::t('a', 'Add')),

            Yii::app()->createUrl(

                $this->getId() . '/ajaxadd',

                array(

                    'id'    => $model->id,

                    'state' => $state

                )),

            array(

                'type'    => 'POST',

                'success' => 'js:$.fn.yiiGridView.update("grid-view")'

            ));

        ?>

    </div>


    <div class="row buttons">

        <?php

        echo CHtml::button(

            Yii::t('a', 'Save'),

            array(

                'onclick' => 'js:$("#form").submit();' // and hiding some divs etc first

            ));


        // a cancel button too

        ?>

        <div id='please_wait'></div>

    </div>

    

    <?php $this->endWidget(); ?>


    <?php

    /**

     * Gets HTML for the input field in column for 

     */

    function getInputA()

    {

        return function ($data)

        {

            // I have a controller action to generate HTML because I also use it to update input fields with ajax

            return AController::actionGetInputA(

                $data->id,

                $data->prop_a);

        };

    }

    ?>



relevant snippet of /controllers/AController.php:




    public function actionUpdate($id)

    {

        $model = $this->loadModel($id);

        $state = $this->getCurrentState();

        $this->handleRequest($model, $state);

        $this->render(

            'update',

            array(

                'model' => $model,

                'state' => $state

            ));

    }




    /**

     * @param integer $id ID of model A.

     */

    public function actionAjaxAdd($id)

    {

        $state = $this->getCurrentState();

        $provider = $this->getGridProvider($id, $state);

        $data = $provider->getData();

        $model = new B();

        $model->id = 'temp_' . time();

        array_push($data, $model);

        $provider->setData($data);

        $this->setGridProvider($id, $state, $provider);

    }




    /**

     * Handles an incoming request to create or update.

     *

     * @param B      $model The model to create or update.

     * @param string $state The current state.

     * @throws Exception If the model could not be saved.

     */

    private function handleRequest($model, $state)

    {

        // Line below initializes a data provider for the grid on the first request to create/update

        $provider = $this->getGridProvider($model->id, $state);


        /*

         * Some code that parses and saves models from $_POST when you submit

         */

    }




    /**

     * @param integer $id    ID of model A.

     * @param string  $state The current state.

     * @return CActiveDataProvider The provider stored on the user's session.

     */

    private function getGridProvider($id, $state)

    {

        if (!Yii::app()->user->hasState($state))

            $this->setGridProvider($id, $state);


        $provider = new CActiveDataProvider('B', array('pagination' => false));

        $provider->setData(Yii::app()->user->getState($state));


        return $provider;

    }




    /**

     * @param integer             $id       ID of model A.

     * @param string              $state    The current state.

     * @param CActiveDataProvider $provider The provider to set. Omit to initialize a new default provider.

     */

    private function setGridProvider($id, $state, $provider = null)

    {

        if (!isset($provider))

        {

            if (isset($id))

                $provider = B::model()->searchByRelationToA($id);

            else

                $provider = new CActiveDataProvider('B');

        }


        Yii::app()->user->setState($state, $provider->getData());

    }




    /**

     * Gets the current state specified by the request, or creates a new one if none was specified.

     *

     * @return string The current state.

     */

    private function getCurrentState()

    {

        if (isset($_POST['state']))

            $state = $_POST['state'];

        else if (isset($_GET['state']))

            $state = $_GET['state'];

        else

            $state = 'grid-provider-state-' . time();


        return $state;

    }



I realize it may be hard to understand, so I’ll try to explain the flow:

[list=1]

[*]actionUpdate loads the A model from DB and "generates" a new $state

[*]handleRequest initializes a new data provider containing the B models related to A and stores its data array on the session

[*]actionUpdate renders the update view which in turn renders _form with $model and $state

[*]_form creates a new data provider and loads the data array on the session using $state

[*]Grid is rendered as expected

[/list]

So far everything is working, but when I click the Add button which causes an ajax reload of the grid, or submit the page and get an error which reloads the page (i.e. whenever the page is rendered again with the same $state) I get the following exception:

Error 500: <h1>PHP Error [8]</h1>

<p>{closure}(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition &quot;B&quot; of the object you are trying to operate on was loaded before unserialize() gets called or provide a __autoload() function to load the class definition (C:\inetpub\wwwroot\protected\views\a\_form.php:134)</p>

Line 134 is:




    return AController::actionGetInputA(

                $data->id             <<< HERE

                $data->prop_a);



How/why is Yii’s auto loader failing?

Is there an easier way to accomplish what I’m trying to do? It’s important that nothing is saved in the db until the user explicitly submits the page.