Skip to content

Instantly share code, notes, and snippets.

@n00ge
Created October 3, 2013 20:44
Show Gist options
  • Save n00ge/6816816 to your computer and use it in GitHub Desktop.
Save n00ge/6816816 to your computer and use it in GitHub Desktop.
Dynamic relationships in Eloquent with Laravel 4
<?php
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class BaseEloquent extends Eloquent
{
protected $associations = array();
protected static $SINGULAR_COLLECTION_TYPES = array(
'belongsTo',
'hasOne',
'morphTo',
);
// monkey-patched but a pull request has been submitted to update this:
// https://github.com/laravel/framework/pull/2378
public function belongsTo($related, $foreignKey = null, $relation = null)
{
if (is_null($relation))
{
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
if (is_null($foreignKey))
{
$foreignKey = snake_case($relation).'_id';
}
$instance = new $related;
$query = $instance->newQuery();
return new BelongsTo($query, $this, $foreignKey, $relation);
}
// monkey-patched but a pull request has been submitted to update this:
// https://github.com/laravel/framework/pull/2378
public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $name = null)
{
if (is_null($name)) {
$caller = $this->getBelongsToManyCaller();
$name = $caller['function'];
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey();
if (is_null($table))
{
$table = $this->joiningTable($related);
}
$query = $instance->newQuery();
return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $name);
}
public function getAssociations()
{
return $this->associations;
}
public function getLinksAttribute()
{
$links = array();
foreach ($this->associations as $association => $details) {
$snakeCase = snake_case($association);
$links[$snakeCase] = $this->getLinkValue($association, $details);
}
return $links;
}
public function __call($method, $args)
{
if ($this->isAssociationMethod($method)) {
return $this->getAssociation($method);
}
return parent::__call($method, $args);
}
public function __get($key)
{
$association = $this->getAssociationFromKey($key);
if (isset($association)) return $association;
return parent::__get($key);
}
private function getValidator()
{
return Validator::make($this->attributes, $this->getRules());
}
private function getRules()
{
$replace = ($this->getKey() > 0) ? $this->getKey() : '';
$rules = $this->rules;
foreach ($rules as $key => $rule) {
$rules[$key] = str_replace(':id', $replace, $rule);
}
return $rules;
}
private function getLinkValue($association, $details)
{
$details = $this->getAssociationDetails($association);
$associationType = $this->getAssociationType($details);
if ($associationType == 'belongsTo') {
$id = $this->getBelongsToProperty($association, $details);
if (isset($id)) return $this->getAttribute($id);
}
$isSingular = $this->isSingularCollection($association, $associationType);
$results = $this->$association()->getResults(array('id'));
if ($isSingular) {
return empty($results) ? null : $results->getKey();
}
return $this->getIdsFromResults($results);
}
private function getBelongsToProperty($association, $details)
{
$belongsToDetails = $this->getBelongsToDetails($association, $details);
return $belongsToDetails[1];
}
private function isSingularCollection($association, $associationType)
{
return in_array($associationType, self::$SINGULAR_COLLECTION_TYPES);
}
private function getIdsFromResults($results)
{
$ids = array();
foreach ($results as $result) {
$ids[] = $result->id;
}
return $ids;
}
private function isAssociationMethod($method)
{
$hasAssociations = is_array($this->associations);
return $hasAssociations && array_key_exists($method, $this->associations);
}
private function getAssociation($association)
{
$associationDetails = $this->getAssociationDetails($association);
$associationType = $this->getAssociationType($associationDetails);
$typeDetails = $this->getAssociationTypeDetails(
$association,
$associationType,
$associationDetails[$associationType]
);
$association = $this->getAssociationWithDetails(
$associationType,
$typeDetails
);
return $association;
}
private function getAssociationDetails($association)
{
return $this->associations[$association];
}
private function getAssociationType($details)
{
$keys = array_keys($details);
return $keys[0];
}
private function getAssociationTypeDetails($association, $type, $details)
{
switch ($type) {
case 'belongsTo':
return $this->getBelongsToDetails($association, $details);
break;
case 'belongsToMany':
return $this->getBelongsToManyDetails($association, $details);
break;
case 'morphTo':
return $this->getMorphToDetails($association, $details);
break;
default:
return (array) $details;
break;
}
}
private function getBelongsToDetails($association, $details)
{
if (is_array($details) && count($details) > 2) return $details;
$foreignKey = snake_case($association) . '_id';
$related = $this->fetchKeyValue(0, (array) $details);
return array($related, $foreignKey, $association);
}
private function getBelongsToManyDetails($association, $details)
{
$details = (array) $details;
$related = $this->fetchKeyValue(0, $details);
$table = $this->fetchKeyValue(1, $details);
$foreignKey = $this->fetchKeyValue(2, $details);
$otherKey = $this->fetchKeyValue(3, $details);
return array(
$related,
$table,
$foreignKey,
$otherKey,
$association
);
}
private function getMorphToDetails($association, $details)
{
if (is_array($details) && count($details) >= 1) return $details;
$details = (array) $details;
$name = snake_case($association);
$type = $this->fetchKeyValue(1, $details);
$id = $this->fetchKeyValue(2, $details);
return array($name, $type, $id);
}
private function fetchKeyValue($key, $array)
{
return array_key_exists($key, $array) ? $array[$key] : null;
}
private function getAssociationWithDetails($type, $details)
{
return call_user_func_array(
array($this, $type),
$details
);
}
private function getAssociationFromKey($key)
{
if ( ! array_key_exists($key, $this->associations)) return;
$camelKey = camel_case($key);
$relations = $this->$camelKey()->getResults();
return $this->relations[$key] = $relations;
}
}
<?php
class PreviewImage extends BaseEloquent
{
protected $table = 'preview_images';
protected $appends = array('links');
protected $associations = array(
'sampleModel' => array(
'hasOne' => 'SampleModel'
),
);
}
<?php
class SampleModel extends BaseEloquent
{
protected $table = 'sample_models';
protected $appends = array('links');
protected $associations = array(
'previewImage' => array(
'belongsTo' => 'PreviewImage'
),
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment