4

I have your standard contact form. (Name, email etc.)

When the user clicks "save" I want to capture what was entered via a plugin & have a CP page to show the people who have filled out the form. I'll get there eventually, but for now I am stuck actually inserting the record.

I am able to insert a record, but it's completely empty. So I know I'm getting close, just not passing off the right information to the right spot.

Model:

public function defineAttributes()
{
    return[
        'firstName' => [
          'type' => AttributeType::String,
          'required' => TRUE
        ]
     ....
    ];
}

Controller:

public function actionSaveContact() {
    $this->requirePostRequest();

    $model = new Model();

    $model->firstName = craft()->request->getPost('firstName');
    ....

    if ($model->validate())
    {
        craft->service->saveContact($model);
    }
}

Service:

public function saveContact(Model, $contact) {
    error_log(print_r($contact, true));

   // I know the data is showing, I can see it in the log
   // [_attributes:Craft\BaseModel:private] => Array(
   //     [firstName] => Bob   
   // )

   // Here is where I am hung up. I don't know what to do next.
   // $record = new Record();
   // $record->save($contact); This creates an empty record in my db
}

I feel really silly asking for help on what seems like such an elementary task. I'm close - just need a little shove in the right direction I think.

Thank you!

EDIT

Thank you to Mats Mikkel Rummelhoff for your suggestion. It pointed me in the right direction. It seems what I am encountering now has been found in the past? Now it seems I am only saving one field? Using this post I was able to "manually" collect all data and save to my DB. I'm not sure why though - all of the data seemed to be available.

Service

....
$attributes = array(
    'firstName' => $model->getAttribute( 'firstName' )
    ....
);    

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

if ( $record->save() )
{
    error_log('SUCCESS');
}
else
{
    ....
}

EDIT

Thank you so much for everyone's contribution(s). I'm going to keep updating this in the event that someone else comes across this same problem.

Going along with @Stuart Whitehead comments, right here is proof: "AttributeType::String does not apply any validation rules." Which is why that field was not getting populated.

Which is what I was setting as my firstName, lastName and so on.

I have also found this article: The Definitive Guide to Validation in Craft Which, is making more and more sense each time I go though it.

The tricky part (for me) is that there isn't a direct corolation between craft and Yii. Meaning that Yii doesn't have a defineAttributes() method where the rules are defined. Craft uses this method, and translates it (for lack of better words) into the Yii equivalent. Pretty cool, just increases the learning curve a bit.

Light bulbs are coming on!

EDIT

This is probably pretty elementry, but I have also discovered that the attribute type that you define in your model, must match the same attribute type in your record. For example:

Record

public function defineAttributes() {
    'firstName' => AttributeType::Name,
    'lastName' => AttributeType::Name,
    ....
}

Model

public function defineAttributes() {
    return [
        'firstName' => [
             'type' => AttributeType::Name,
             'required' => TRUE
         ],
         'lastName' => [
             'type' => AttributeType::Name,
             'required' => TRUE
         ],
         ....
    ];
}

Now with things starting to get in the correct way of doing things, gasp stuff is working. Now using @Mats Mikkel Rummelhoff's comment (having all fields validate now) I am able to use:

$record->setAttributes( $model->getAttributes() );
7

Stripping this back, your basic problem is saving data to the database from an unreliable source. This is a fundamentally insecure operation—we've got no idea if this user is genuine or attempting something malicious. That's why the underlying Yii framework is trying to protect your install of Craft, and which is the reason you're seeing this ‘buggy’ behaviour with setAttributes().

The Yii framework implements a ‘safe’ validator. A key point made in this helpful article is this:

Massive Assignment will only be made for fields which have passed some explicit validation rule.

This means that any attributes you define without an explicit validation rule will be considered unsafe. This will be the reason that only your email field was saving—it has an explicit validation rule (i.e. an email pattern).

How can you resolve this, then?

Set attributes explicitly

Firstly, you could explicitly set each attribute like you have been doing. Since this is not a massive assignment, the data is treated as safe and will save OK. Both $record->setAttribute() and $record->attribute() will work for this.

Define attributes with explicit validation

Another solution is to define explicit validation rules, and will allow massive assignment on these attributes, but explicit rules only really apply for some data types. What if you want a simple string?

Make existing attributes safe

A solution could be to add the safe rule to your attribute definition. If you follow the definition of attributes down the rabbit hole, you'll find that attribute configs are normalised by ModelHelper::normalizeAttributeConfig() (and subsequently ModelHelper::getRules()) which does not cater for this. Now where does that leave us?

It's possible to override (and extend) the inherited rules() method. Assuming you wanted to make all attributes safe and valid for massive assignment, I reckon something like this would work.

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;
}

You should then be able to massively set attributes with $record->setAttributes().

|improve this answer|||||
  • Thank you so much for this! It's pretty obvious I'm just starting my Craft/Yii journey, so I appreciate the time you took to post this. Coming from CodeIgniter, Yii is a tad different & is taking some getting used to. I'll get there eventually. Thank you again. – Damon Mar 27 '15 at 13:25
  • Not a problem :) The Craft and Yii documentation is pretty descriptive, however I've found Larry Ullman's ‘The Yii Book’ to be very informative and a good way to learn the framework. – Stuart Whitehead Mar 27 '15 at 18:46
3

I think your problem is that the record is not being populated – the save() method does take an array of attributes, but that would be the second parameter, as per the CActiveRecord docs.

Try this:

$record = new MyPluginRecord();
$record->setAttributes( $contact->getAttributes() );
$record->save();

EDIT:

The BaseRecord's setAttributes method seems a bit unreliable. If only some values (or none) are saved, you can try multiple calls to $record->setAttribut() instead:

$attributes = $contact->getAttributes();
foreach ( $attributes as $key => $value ) {
   $record->setAttribute( $key, $value );
}
$record->save();

EDIT 2:

I haven't had the time to test this, but if you're having trouble saving all attributes using $record->setAttributes( $model->getAttributes() ); you could also try flattening the values:

$record->setAttributes( $model->getAttributes( null, true ) );
|improve this answer|||||
  • This got me closer! I was only getting the email field populated using this method. However, when I logged out what was in the getAttributes() all of the data was there. Strange. Thanks to this article I Was at least able to follow along with what was happing because of your answer so thank you very much! – Damon Mar 26 '15 at 21:57
  • @dragonslovetacos Hm, it's really odd how $record->saveAttributes() seem wonky. I actually tested it myself and experienced the same – need to do multiple calls to setAttribute, rather than one clean setAttributes() call. I have no idea if this is intended behaviour or a bug. – Mats Mikkel Rummelhoff Mar 27 '15 at 8:32
  • Edited my answer to include the above caveat – Mats Mikkel Rummelhoff Mar 27 '15 at 8:39
  • Thank you for taking the time to post all of this - I was able to get it to work and I'm also going to go back and review the comments/articles posted above as well. Really want to develop "good" habits from the git-go. – Damon Mar 27 '15 at 13:26
  • No worries @dragonslovetacos! Happy to hear you got it singing. Did you have to call setAttribute() multiple times, or did flattening the model attributes work? Edit: Sorry, didn't notice the above answer before :) – Mats Mikkel Rummelhoff Mar 27 '15 at 13:28

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.