PHP ActiveRecord creating a child model via Has_Many association

1.7k Views Asked by At

I'm picking up PHP Active Record and dealing with associations. I have two related objects using a "Has_Many" and "Belongs_to" (parent/child) and trying to create a child record when creating a new parent record (in this case creating a "skin" for my "unit").

class Unit extends ActiveRecord\Model 
{  
   static $has_many = array(
       array('skins')
   );
}

class Skin extends ActiveRecord\Model 
{
    static $belongs_to = array(
       array('unit')
    ); 
}

I've found both of these threads here: http://www.phpactiverecord.org/boards/4/topics/153 Activerecord-association: create new object (find class)

So I my code currently looks like:

$unit = new Unit();
$unit->name = 'somename';
$unit->description = 'somedescription';

$skinArray = array('name' => $unit->name.' Default Skin');
$unit->create_skins($skinArray);
$unit->save();

The code above is not associating the new skin to the unit in the database or in code though it /is/ placing a new skin record in the database (with a unit_id of NULL). Using "build_skins" doesn't put a Skin record in the database.

I was hoping there was a way to add a "child" to the parent model via the model itself as some other ORM's do. The only way I can do this is to do it explicitly:

$unit = new Unit();
$skin = new Skin();

$unit->name = 'somename';
$unit->description = 'somedescription';
$unit->save();

$skin->unit_id = $unit->id;
$skin->name = $unit->name.' Default Skin';
$skin->save();

Perhaps that is the way it is supposed to be done in PHP ActiveRecord and my expectations are wrong. But I was hoping for a way to do it through the objects that didn't require saving the Parent to the DB with an explicit call first. For example the "Recess" PHP framework would have a simple call on the unit like such: $unit->addSkin($skin);

4

There are 4 best solutions below

1
On

Do you configurate database connection? Look at gihub examples.

<?php
require_once __DIR__ . '/../../ActiveRecord.php';

class Book extends ActiveRecord\Model
{
// explicit table name since our table is not "books"
static $table_name = 'simple_book';

// explicit pk since our pk is not "id"
static $primary_key = 'book_id';

// explicit connection name since we always want production with this model
static $connection = 'production';

// explicit database name will generate sql like so => db.table_name
static $db = 'test';
}

$connections = array(
'development' => 'mysql://invalid',
'production' => 'mysql://test:[email protected]/test'
);

// initialize ActiveRecord
ActiveRecord\Config::initialize(function($cfg) use ($connections)
{
    $cfg->set_model_directory('.');
    $cfg->set_connections($connections);
});

print_r(Book::first()->attributes());
?>

You have to init DB to do this ActiveRecord\Config::initialize.

3
On

I recommend implementing the method in an afterSave() hook inside your parent model class, or in a custom model method createChild($data).

However (e.g. Yii Framework's CActiveRecord) something like the following is nice:

class Unit extends ActiveRecord\Model 
{  
 ...
    public function afterSave() {
        if ($this->isNewRecord) {                
            $firstSkin = new Skin();
            $firstSkin->unitId = $this->id;
            $firstSkin->name = $this->name . ' Default Skin';
            $firstSkin->save();
        }
    }  
 ...   
}

If in fact the particular behavior is unique to the controller, then perhaps use the following alternative (data passed in from controller to create child):

class Unit extends ActiveRecord\Model 
{  
 ...
    public function createSkin($data) {
        $firstSkin = new Skin();
        $firstSkin->unitId = $this->id;
        $firstSkin->setAttributes($data);
        $firstSkin->save();
    }  
 ...   
}

Hope that helps. I'm a fan of ActiveRecord for quick scaffolding, although beware relying on it too "magically" for larger applications. Btw iz very nice in Rails too ;) where it originated.

3
On

Depending on your keys, should you not auto-increment (without NULL as the deafult value). I would take a look at the db key defualt values and adjust auto-increment or the default value of unit_id if it fits with your schema. You said the association is working with the two step save. Is the unit_id saved correctly in the two step save?

The docs are not very helpful as they recommend the explicit save you're trying to avoid.

@outrightmental has something interesting about setting unit->id with a hook, but wouldn't it need to be a before_create callback, you wouldn't want to insert records without the associations working. see this active record callbacks doc.

$after_create = array('validate association and assign unit_id if NULL'); 

or

$before_create = array('explcitly assign unit_id');

? the callback docs offer up other suggestions

0
On

I'm not really familiar with php active record, but in my experience (same problem was when I was using Doctrine and tried to add child views and there also was null, before I persisted parent and child to database). I think that phpactiverecord does not have this functionality, so the solution that I will suggest - create manually methods in Models, like Unit::addSkin(Skin $skin), that will have something like this inside:

$skin->unit_id = $this->id;
$skin->save();

Basically logic like that. But I cannot say that this aproach is good or even neutral practive. Just this is the way you can do such things in Doctrine.

Also, you tried Propel? If you like active record, I think you'll find it pretty interesting.