2

I am tearing my hair out with this!

So, I have a variable file setup as follows

 <?php
namespace Craft;

class TrackrVariable
{
    public function startTrace()
    {
        if(!isset($_COOKIE['trackr_sess'])):
            $sess = StringHelper::randomString(40, TRUE);
            $expire = time()+60*60*24*120; // 120 days
            setcookie('trackr_sess', $sess, $expire);
            $_COOKIE['trackr_sess'] = $sess;
        else:
            $sess = $_COOKIE['trackr_sess'];
        endif;

        $trace = new Trackr_TraceModel();
        $trace->entry_id = 1;
        $trace->sess_id = $sess;
        craft()->trackr_trace->saveTrace($trace);
    }
}

...and I am calling the function in my template using:

{{ craft.trackr.startTrace() }}

I create the model and set the attributes and hand off to a service. The model looks like this:

<?php
namespace Craft;

class Trackr_TraceModel extends BaseModel
{
   public function rules()
    {
        // Are there any existing rules?
        $rules = parent::rules();

        // Make all attributes safe
        $rules[] = array(implode(',', array_keys($this->getAttributes())), 'safe');

        // Send it on outta here
        return $rules;
    }

    protected function defineAttributes()
    {
        return [
            'entry_id' => [
                 'type' => AttributeType::Number,
                 'required' => TRUE
             ],
             'sess_id' => [
                 'type' => AttributeType::String,
                 'required' => TRUE
             ]
        ];
    }
}

and the service looks like:

<?php
namespace Craft;

class Trackr_TraceService extends BaseApplicationComponent
{

    protected $traceRecord;

    public function __construct($traceRecord = null)
    {
        $this->traceRecord = $traceRecord;
        if(is_null($this->traceRecord))
        {
            $this->traceRecord = Trackr_TraceRecord::model();
        }   
    }

    public function saveTrace(Trackr_TraceModel &$trace)
    {

        if ($id = $trace->getAttribute('id')) :
            if ($this->traceRecord->findByPk($id) === NULL):
                throw new Exception(Craft::t('Can\'t find trace with ID "{id}"', array('id' => $id)));
            endif;
        else:
            $this->traceRecord->create();
        endif;

        if ($trace->validate()):

            $dt = DateTimeHelper::currentTimeForDb();
            $attributes = array(
                'entry_id' => $trace->entry_id,
                'sess_id' => $trace->sess_id,
                'dateCreated' => $dt,
                'dateUpdated' =>$dt,
                'uid' => StringHelper::UUID(),
                'id' => 0
            );    
            foreach($attributes as $k => $v) $this->traceRecord->setAttribute($k,$v);

            if($this->traceRecord->save()):
                // update id on model (for new records)
                $trace->setAttribute('id', $this->traceRecord->getAttribute('id'));
                return TRUE;
            else:
                $model->addErrors($this->traceRecord->getErrors());
                return FALSE;
            endif;

        endif;
    }
}

In my mind, the service should be accepting the model, assigning it's attributes to the record and saving the record to the database. Currently, $record->save() is returning TRUE and therefore passing the conditional but no record is created in the dataset, no errors, no trace of any issues whatsoever!

At first I though it might be a 'safe validation' issue as outlined in this answer but, as far as I am aware, I am following all of the advice there.

All I want to be able to do is create a random string, store it in a cookie, as use that cookie to track resources that users view by storing the entry IDs in a table.

If I print out the record and the model separately, the data is being assigned to the attributes just fine, and the cookie bit works.

Please help! Dan

EDIT

The Record file looks like this:

<?php
namespace Craft;

class Trackr_TraceRecord extends BaseRecord
{
    public function getTableName()
    {
        return 'trackr_trace';
    }

    protected function defineAttributes()
    {
        return array(
            'entry_id' => AttributeType::Number,
            'sess_id' => AttributeType::String
        );
    }
    public function defineIndexes()
    {
        return array(
            array('columns' => array('sess_id'), 'unique' => TRUE),
        );
    }

    /**
     * Create a new instance of the current class.
     *
     * @return BaseRecord
     */
    public function create()
    {
        $class = get_class($this);
        $record = new $class();
        return $record;
    }
}
4
  • I think the issue is $record = $this->traceRecord;. Try doing all of your record related operations on $this->traceRecord directly instead of copying it to $record.
    – Brad Bell
    Aug 14 '15 at 17:16
  • Thanks Brad but unfortunately this didn't make any difference, I have updated the example code in this post to reflect the changes.
    – Dan Lake
    Aug 17 '15 at 9:16
  • Are you able to share your record code at all? Aug 18 '15 at 10:29
  • Added the record into the original question
    – Dan Lake
    Aug 18 '15 at 12:38
2

I’m seeing a few issues with this:

  • Service classes aren’t created with any arguments*, so the $traceRecord argument in your __construct() method is pointless.

  • Setting $this->traceRecord to Trackr_TraceRecord::model() is really weird. When you call ::model() you’re getting a “meta” version of your record class, which should only be used to call things like find(), and those functions will return the actual record instances. (It’s basically just an awkward workaround for the fact that Yii 1 didn’t require PHP 5.3+, so they couldn’t rely on Late Static Bindings support, which would have made it possible to do things like MyServiceClass::find() directly - as you can in Yii 2.) Point is, you shouldn’t be storing a reference to the meta record returned by ::model().

  • $trace->getAttribute('id') implies that your Trackr_TraceModel class has an id attribute, but it doesn’t, unless you didn’t post all of your model attributes code.

  • Calling if ($this->traceRecord->findByPk($id) === NULL) works as a conditional statement, but it looks like your logic is assuming that if a record was actually found, that $this->traceRecord would be set to that new record. That won’t actually be the case unless you write $this->traceRecord = ....

  • Records don’t have a create() method. I’m not sure why that’s not throwing a PHP error for you. Did you add that method yourself? If you want to create a new record instance, you should be typing $this->traceRecord = new Trackr_TraceRecord();

I’d suggest you remove your service’s __construct() method, and also its protected $traceRecord property. Then update its saveTrace() method to the following:

public function saveTrace(Trackr_TraceModel &$trace)
{

    if ($id = $trace->getAttribute('id'))
    {
        $traceRecord = Trackr_TraceRecord::model()->findById($id);

        if (!$traceRecord)
        {
            throw new Exception(Craft::t('Can\'t find trace with ID "{id}"', array('id' => $id)));
        }
    }
    else
    {
        $traceRecord = new Trackr_TraceRecord();
    }

    if ($trace->validate())
    {
        $dt = DateTimeHelper::currentTimeForDb();
        $attributes = array(
            'entry_id' => $trace->entry_id,
            'sess_id' => $trace->sess_id,
            'dateCreated' => $dt,
            'dateUpdated' =>$dt,
            'uid' => StringHelper::UUID(),
            'id' => 0
        );

        foreach($attributes as $k => $v)
        {
            $traceRecord->setAttribute($k,$v);
        }

        if ($traceRecord->save())
        {
            // update id on model (for new records)
            $trace->setAttribute('id', $traceRecord->getAttribute('id'));
            return true;
        }
        else
        {
            $model->addErrors($traceRecord->getErrors());
            return false;
        }
    }
}

* Unless you’re creating an instance of your class in some other way besides calling craft()->trackr_trace, but you shouldn’t be.

1
  • Brandon, you are a Gentleman and a Scholar! This worked perfectly. Reading through your explanation makes perfect sense. I'm still a noob to Yii coming from Laravel I'm still learning the nuances. Thank you again!
    – Dan Lake
    Aug 18 '15 at 12:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.