This package allows you to easily build Eloquent models and their relationships, through data settings via array or JSON passed to the model's factory.
Install the package via composer:
composer require brandonkerr/eloquent-from-settings
First, ensure that your model has a factory:
namespace App\Models;
// ...
use Illuminate\Database\Eloquent\Factories\HasFactory;
class User extends Authenticatable
{
use HasFactory;
// ...
}
Then simply add the FromSettings
trait to your factory:
namespace Database\Factories;
// ...
use Brandonkerr\EloquentFromSettings\Traits\FromSettings;
class UserFactory extends Factory
{
use FromSettings;
// ...
}
By default, the FromSettings
trait behaves strictly and will throw:
- a
MissingTraitException
if a keyed relationship does not use the FromSettings trait - an
UnknownKeyException
if a key cannot be matched to an attribute, relationship, or function
While this is the recommended behaviour to ensure data integrity, you have the freedom to change these settings.
If you would like to allow for a keyed relationship that does not use the FromSettings trait, you can either set the
$throwsMissingTraitException
property to false:
class FooFactory extends Factory
{
use FromSettings;
public bool $throwsMissingTraitException = false;
// ...
}
or for a customized logic, implement FromSettingsInterface
and complete the getThrowsMissingTraitException()
function:
class BarFactory extends Factory implements FromSettingsInterface
{
use FromSettings;
public function getThrowsMissingTraitException(): bool
{
return $this->someCustomSettingOrLogic;
}
// ...
}
If you would like to allow for unknown keys to be silently dropped, you can either set the $throwsUnknownKeyException
property to false:
class FooFactory extends Factory
{
use FromSettings;
public bool $throwsUnknownKeyException = false;
// ...
}
or for a customized logic, implement FromSettingsInterface
and complete the getThrowsUnknownKeyException()
function:
class BarFactory extends Factory implements FromSettingsInterface
{
use FromSettings;
public function getThrowsUnknownKeyException(): bool
{
return $this->someCustomSettingOrLogic;
}
// ...
}
Simply pass the desired settings via array to the factor's fromSettingsArray
function:
$data = [
"name" => "Brandon Kerr",
"books" => [
[
"title" => "How to Use Eloquent From Settings",
"reviews" => [
[
"reviewer" => "Jane Doe",
"score" => 80,
],
[
"reviewer" => "John Smith",
"score" => 45,
],
],
],
],
];
Author::factory()->fromSettingsArray(...$data)->create();
or via JSON to the factor's fromSettingsJson
function:
$json = '{
"name":"Brandon Kerr",
"books":[
{
"title":"How to Use Eloquent From Settings",
"reviews":[
{
"reviewer":"Jane Doe",
"score":80
},
{
"reviewer":"John Smith",
"score":45
}
]
}
]
}';
Author::factory()->fromSettingsJson($json)->create();
The end result will be
- an Author model with name Brandon Kerr
- a Book model whose Author is created above and title is How to Use Eloquent From Settings
- a Review for the Book created above with reviewer Jane Doe and a score of 80
- a Review for the Book created above with reviewer John Smith and a score of 45
This example covers Authors writing Books, which have Reviews. Full details can be found in the tests directory of this package.
Refer to the Models and Migrations directories under tests/Stubs for full details.
Column/Attribute | Type | Notes |
---|---|---|
id | unsigned bigint | Primary Key |
name | string | unique constraint added for use in a custom function test |
- an Author
HasMany
Books - an Author
HasManyThrough
Reviews (through Books)
Column/Attribute | Type | Notes |
---|---|---|
id | unsigned bigint | Primary Key |
title | string | title of the book |
author_id | unsigned bigint | Foreign Key to Author |
- a Book
BelongsTo
an Author - a Book
HasMany
Reviews
Column/Attribute | Type | Notes |
---|---|---|
id | unsigned bigint | Primary Key |
reviewer | string | name of the reviewer |
score | integer | the score (out of 100) |
book_id | unsigned bigint | Foreign Key to Book |
- a Review
BelongsTo
a Book
The AuthorFactory
and ReviewFactory
classes are completely standard (aside from using the FromSettings
trait), but
the BookFactory
has two custom functions to showcase additional functionality of this package:
/**
* Custom function to find or create the author, based on the given name
*
* @param string $name
* @return $this
*/
public function forAuthor(string $name): self
{
$author = Author::firstOrCreate([
"name" => $name
]);
return $this->state(["author_id" => $author->id]);
}
/**
* Custom function to add reviews with a perfect score
*
* @param string ...$names
* @return $this
*/
public function perfectReviews(string ...$names): self
{
return $this->has(
Review::factory()
->count(count($names))
->sequence(fn (Sequence $sequence) => [
"reviewer" => $names[$sequence->index],
"score" => 100,
])
);
}
Below is a commented example of an array of settings, where the author is the root of the data:
$data = [
// the author's name attribute
"name" => "Bob",
// the author's HasMany relationship with Book
"books" => [
// first book
[
// the book's title attribute
"title" => "Bob's First Book",
// the book's HasMany relationship with Review
"reviews" => [
// first review
[
// the review's reviewer attribute
"reviewer" => "Jane Doe",
// the review's score attribute
"score" => 80,
],
// second review
[
// the review's reviewer attribute
"reviewer" => "John Smith",
// the review's score attribute
"score" => 45,
],
],
],
// second book
[
// the book's title attribute
"title" => "Please Don't Review Me",
// NOTE no reviews
],
],
];
Then we simply call the Author's factory:
$author = Author::factory()->fromSettingsArray(...$data)->create();
The end result is
- One Author with name Bob, which has two books:
- The first book is titled Bob's First Book and has two reviews:
- one with reviewer Jane Doe and a score of 80, and
- one with reviewer John Smith and a score of 45.
- The second book is titled Please Don't Review Me and has zero reviews.
- The first book is titled Bob's First Book and has two reviews:
We aren't limited to Has__ relationships, like in the example above. We can also use a Book as the data root, and create an author for it:
$data = [
"title" => "My Book",
"author" => [
"name" => "Bob Jones",
],
];
Book::factory()->fromSettingsArray(...$data)->create();
The end result is one Author named Bob Jones, which has one Book titled My Book.
But what if we don't want to create a new author, and don't know the author's ID that we could use to set the book's
author_id value? You could create a custom function on the Book's factory that can find the author by their name, and
assign the author to the book. See the forAuthor()
function above for details.
Author::create([
"name" => "Bob Jones"
]);
// ...
$data = [
"title" => "My Book",
"forAuthor" => [
"name" => "Bob Jones",
],
];
$book = Book::factory()->fromSettingsArray(...$data)->create();
The end result is again one Author named Bob Jones, which has one Book titled My Book, but since the Author named Bob Jones already existed, it would not be created again, and it would not violate our unique name constraint on the Authors table.
We can also use a custom function to accept non key-value pair settings. See the perfectReviews()
function above for
details.
$data = [
"title" => "My Book",
"author" => [
"name" => "Bob Jones",
],
"perfectReviews" => [
[
"Jane Doe",
],
[
"John Smith",
],
],
];
The end result is once again one Author named Bob Jones, which has one Book titled My Book. However, this time the
Book has two Reviews: one with reviewer Jane Doe, and the other with reviewer John Smith, and both with a score of
100 (as set by the perfectReviews
function).
This package is open-sourced software licensed under the MIT license.