laracasts / TestDummy

Easy factories for PHP integration testing.
https://laracasts.com/lessons/whats-new-in-testdummy
MIT License
457 stars 80 forks source link

Support overriding relationship attributes #88

Closed adamwathan closed 9 years ago

adamwathan commented 9 years ago

This PR adds support for overriding relationship attributes.

TL;DR

Given the following factories:

$factory('Post', [
    'title' => 'Post Title'
]);

$factory('Comment', [
    'post_id' => 'factory:Post',
    'body' => $faker->word
]);

...you can override a Post title using the following syntax:

$comment = Factory::create('Comment', [
    'post_id.title' => 'override'
]);

The column name was chosen as the attribute prefix to allow separately overriding relationships that use the same factory, as it's the only reliable unique identifier for the relationship. See below for an example.

More fun

Nested relationships

This PR supports relationships of arbitrary nesting level. Given these factories:

$factory('Person', [
    'name' => $faker->name
]);

$factory('Post', [
    'author_id' => 'factory:Person',
    'title' => 'Post Title'
]);

$factory('Comment', 'comment_for_post_by_person', [
    'post_id' => 'factory:Post',
    'body' => $faker->word
]);

...you can override nested relationship attributes like so:

$comment = Factory::create('comment_for_post_by_person', [
    'body' => 'Overridden Comment Body',
    'post_id.title' => 'Overridden Post Title',
    'post_id.author_id.name' => 'Overridden Author Name',
]);

Multiple relationships using the same factory

If you have an object that has two relationships to the same type of model but under different keys, you can still override their attributes separately.

Consider these factories:

$factory('Message', [
    'contents' => $faker->sentence,
    'sender_id' => 'factory:Person',
    'receiver_id' => 'factory:Person',
]);

$factory('Person', [
    'name' => $faker->name
]);

Since I've opted to use the column name as the identifier for the relationship, you can easily override those attributes separately like so:

$message = Factory::create('Message', [
    'sender_id.name' => 'Adam',
    'receiver_id.name' => 'Jeffrey',
]);

Note: This is the reason for removing the relationshipIds caching that was happening previously. There is no way to treat the relationships as independent when using the ID caching. I think the performance hit is negligible.

Crappy parts

I needed to add the filterArrayKeys helper because PHP < 5.6 doesn't allow filtering an array by key and it's an annoying enough operation that duplicating it would be unfortunate. It would be nice to just create an array_filter_keys function and autoload it in some sort of helpers file because it definitely feels out of place in the Builder class, but I didn't want to make that decision alone.

adamwathan commented 9 years ago

@devinfd Here for your review!

devinfd commented 9 years ago

This is perfect! All my tests are passing after updating them to use the new syntax. Using the column name as the prefix feels a bit weird but given the multiple relationships using the same factory issue it completely make sense. I'm sure there are some tweaks that may need to be made but so far this is working beautifully.

+1 Well done!

JeffreyWay commented 9 years ago

Looks awesome, Adam! Thanks so much.

All tests are green, so I'll likely merge this later today. :)

devinfd commented 9 years ago

@JeffreyWay Any chance of pulling this in?