Installation:
composer require tusimo/embed-relation
Add to composer.json if manually managing dependencies:
"require": {
"tusimo/embed-relation": "^0.1"
}
Model Integration: Add the traits to your Eloquent model:
use Tusimo\Eloquent\Traits\EmbedsRelation;
use Tusimo\Eloquent\Traits\CastAttributes;
Define Virtual Columns:
Configure $virtualColumnMaps to map JSON fields to virtual columns:
protected $virtualColumnMaps = [
'data' => [
'address' => 'home_address',
'follower_ids',
],
];
Cast Attributes:
Use the custom casts (integer_array, string_array, etc.) in $casts:
protected $casts = [
'data' => 'array',
'follower_ids' => 'integer_array',
];
First Use Case: Access embedded relations directly:
$user = User::find(1);
$user->address; // Accesses 'home_address' from JSON
$user->follower_ids; // Returns array of integers
Embedding JSON Data:
Store nested JSON in a single column (e.g., data) and access fields as properties:
$user->data['address']['city']; // Direct JSON access
$user->address; // Virtual column (if mapped)
Dynamic Virtual Columns:
Use $virtualColumnMaps to flatten JSON into model properties:
// Model
protected $virtualColumnMaps = [
'metadata' => [
'preferences' => 'user_prefs',
],
];
// Usage
$user->user_prefs; // Accesses `metadata.preferences`
Array Casting: Leverage custom casts for type safety:
protected $casts = [
'tags' => 'string_array',
'scores' => 'float_array',
];
// Usage
$user->tags[0]; // Returns string
$user->scores[0] + 1.0; // Works as float
Querying Embedded Data:
Use whereJsonContains or custom scopes for JSON filtering:
User::whereJsonContains('data->address', ['city' => 'New York'])
->get();
Mass Assignment: Update embedded data via mass assignment:
$user->update([
'data' => ['address' => ['city' => 'London']],
'follower_ids' => [1, 2, 3],
]);
Database Schema:
Ensure your JSON column is defined as JSON or TEXT in migrations:
Schema::table('users', function (Blueprint $table) {
$table->json('data')->nullable();
});
API Responses:
Use $appends to include virtual columns in API responses:
protected $appends = ['address', 'follower_ids'];
Validation: Validate JSON structure in Form Requests:
$this->validate($request, [
'data.address.city' => 'required|string',
]);
Relationships: Combine with Laravel’s built-in relationships for hybrid data:
public function posts() {
return $this->hasMany(Post::class);
}
// Access
$user->posts; // Eloquent relation
$user->data['recent_post']; // Embedded data
Outdated Package:
JSON Parsing Errors:
json()->safe or try-catch:
try {
$user->data['address'];
} catch (\JsonException $e) {
// Handle invalid JSON
}
Virtual Column Overrides:
// Bad: 'data' column and 'data' virtual column
protected $virtualColumnMaps = ['data' => ['nested']];
Casting Quirks:
*_array) may not handle null values gracefully. Add null checks:
public function getFollowerIdsAttribute($value) {
return $value === null ? [] : $value;
}
Database-Specific JSON Functions:
->>) may not work across all databases (MySQL, PostgreSQL, SQLite). Test in your environment.Log Virtual Columns: Add a temporary accessor to inspect mappings:
public function getDebugVirtualColumnsAttribute() {
return $this->virtualColumnMaps;
}
Check Casted Attributes:
Override getAttribute to debug casting:
public function getAttribute($key) {
\Log::debug("Fetching attribute: $key", ['value' => parent::getAttribute($key)]);
return parent::getAttribute($key);
}
Raw JSON Inspection:
Use toJson() to verify stored data:
$user->data->toJson(); // Dump raw JSON
Custom Virtual Mappings:
Extend EmbedsRelation trait to add dynamic mappings:
protected function getVirtualColumnMaps() {
$maps = parent::getVirtualColumnMaps();
$maps['dynamic_data'] = ['key' => 'dynamic_key'];
return $maps;
}
New Cast Types:
Add support for custom array casts in CastAttributes:
// In a service provider
Tusimo\Eloquent\Traits\CastAttributes::macro('customCast', function ($value) {
return json_decode($value, true);
});
Query Scopes: Create reusable scopes for embedded queries:
public function scopeWithAddressIn($query, $cities) {
return $query->whereJsonContains('data->address', ['city' => $cities]);
}
Event Listeners:
Listen for retrieved events to modify embedded data:
public static function boot() {
static::retrieved(function ($model) {
$model->normalizeEmbeddedData();
});
}
Testing:
Use refreshModel to reset virtual columns in tests:
public function testVirtualColumns() {
$user = User::factory()->create(['data' => json_encode(['address' => ['city' => 'Test']])]);
$user->refreshModel(); // Re-fetch virtual columns
$this->assertEquals('Test', $user->address);
}
How can I help you explore Laravel packages today?