Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 112 additions & 10 deletions src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Illuminate\Database\Eloquent\Concerns;

use Closure;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\StrictFluent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasOne;
Expand Down Expand Up @@ -47,12 +49,22 @@ trait HasRelationships
* Define a one-to-one relationship.
*
* @param string $related
* @param string $foreignKey
* @param string|Closure $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null, $localKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($foreignKey instanceof Closure) {
extract(
StrictFluent::withNullArray(['foreignKey', 'localKey'])
->applyClosure($foreignKey)
->getAttributes()
);
}

$instance = $this->newRelatedInstance($related);

$foreignKey = $foreignKey ?: $this->getForeignKey();
Expand All @@ -67,13 +79,23 @@ public function hasOne($related, $foreignKey = null, $localKey = null)
*
* @param string $related
* @param string $name
* @param string $type
* @param string|Closure $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($type instanceof Closure) {
extract(
StrictFluent::withNullArray(['type', 'id', 'localKey'])
->applyClosure($type)
->getAttributes()
);
}

$instance = $this->newRelatedInstance($related);

list($type, $id) = $this->getMorphs($name, $type, $id);
Expand All @@ -89,13 +111,23 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey =
* Define an inverse one-to-one or many relationship.
*
* @param string $related
* @param string $foreignKey
* @param string|Closure $foreignKey
* @param string $ownerKey
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($foreignKey instanceof Closure) {
extract(
StrictFluent::withNullArray(['foreignKey', 'ownerKey', 'relation'])
->applyClosure($foreignKey)
->getAttributes()
);
}

// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
Expand Down Expand Up @@ -125,13 +157,23 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $name
* @param string|Closure $name
* @param string $type
* @param string $id
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($name instanceof Closure) {
extract(
StrictFluent::withNullArray(['name', 'type', 'id'])
->applyClosure($name)
->getAttributes()
);
}

// If no name is provided, we will use the backtrace to get the function name
// since that is most likely the name of the polymorphic interface. We can
// use that to get both the class and foreign key that will be utilized.
Expand Down Expand Up @@ -211,12 +253,22 @@ protected function guessBelongsToRelation()
* Define a one-to-many relationship.
*
* @param string $related
* @param string $foreignKey
* @param string|Closure $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($foreignKey instanceof Closure) {
extract(
StrictFluent::withNullArray(['foreignKey', 'localKey'])
->applyClosure($foreignKey)
->getAttributes()
);
}

$instance = $this->newRelatedInstance($related);

$foreignKey = $foreignKey ?: $this->getForeignKey();
Expand All @@ -233,13 +285,23 @@ public function hasMany($related, $foreignKey = null, $localKey = null)
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|Closure|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($firstKey instanceof Closure) {
extract(
StrictFluent::withNullArray(['firstKey', 'secondKey', 'localKey'])
->applyClosure($firstKey)
->getAttributes()
);
}

$through = new $through;

$firstKey = $firstKey ?: $this->getForeignKey();
Expand All @@ -258,13 +320,23 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey
*
* @param string $related
* @param string $name
* @param string $type
* @param string|Closure $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($type instanceof Closure) {
extract(
StrictFluent::withNullArray(['type', 'id', 'localKey'])
->applyClosure($type)
->getAttributes()
);
}

$instance = $this->newRelatedInstance($related);

// Here we will gather up the morph type and ID for the relationship so that we
Expand All @@ -283,7 +355,7 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey =
* Define a many-to-many relationship.
*
* @param string $related
* @param string $table
* @param string|Closure $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
Expand All @@ -294,6 +366,16 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey =
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
$parentKey = null, $relatedKey = null, $relation = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($table instanceof Closure) {
extract(
StrictFluent::withNullArray(['table', 'foreignPivotKey', 'relatedPivotKey', 'parentKey', 'relatedKey', 'relation'])
->applyClosure($table)
->getAttributes()
);
}

// If no relationship name was passed, we will pull backtraces to get the
// name of the calling function. We will use that function name as the
// title of this relation since that is a great convention to apply.
Expand Down Expand Up @@ -329,7 +411,7 @@ public function belongsToMany($related, $table = null, $foreignPivotKey = null,
*
* @param string $related
* @param string $name
* @param string $table
* @param string|Closure $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
Expand All @@ -341,6 +423,16 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n
$relatedPivotKey = null, $parentKey = null,
$relatedKey = null, $inverse = false)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($table instanceof Closure) {
extract(
StrictFluent::withNullArray(['table', 'foreignPivotKey', 'relatedPivotKey', 'parentKey', 'relatedKey', 'inverse'])
->applyClosure($table)
->getAttributes()
);
}

$caller = $this->guessBelongsToManyRelation();

// First, we will need to determine the foreign key and "other key" for the
Expand Down Expand Up @@ -369,7 +461,7 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n
*
* @param string $related
* @param string $name
* @param string $table
* @param string|Closure $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
Expand All @@ -379,6 +471,16 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
$relatedPivotKey = null, $parentKey = null, $relatedKey = null)
{
// If the second argument is a Closure, we will ignore other arguments
// and set up the relationship with values provided in the Closure.
if ($table instanceof Closure) {
extract(
StrictFluent::withNullArray(['table', 'foreignPivotKey', 'relatedPivotKey', 'parentKey', 'relatedKey'])
->applyClosure($table)
->getAttributes()
);
}

$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();

// For the inverse of the polymorphic many-to-many relations, we will change
Expand Down
20 changes: 20 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,26 @@ public function getQualifiedRelatedPivotKeyName()
return $this->table.'.'.$this->relatedPivotKey;
}

/**
* Get the fully qualified "parent key" for the relation.
*
* @return string
*/
public function getQualifiedParentKeyName()
{
return $this->parent->getTable().'.'.$this->parentKey;
}

/**
* Get the fully qualified "local key" for the relation.
*
* @return string
*/
public function getQualifiedRelatedKeyName()
{
return $this->table.'.'.$this->relatedKey;
}

/**
* Get the intermediate table for the relationship.
*
Expand Down
65 changes: 65 additions & 0 deletions src/Illuminate/Support/StrictFluent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Illuminate\Support;

use Closure;
use InvalidArgumentException;

class StrictFluent extends Fluent
{
/**
* Instantiate StrictFluent with an array full of nulls.
*
* @param array $keys
* @return static
*/
public static function withNullArray($keys)
{
return new static(array_fill_keys($keys, null));
}

/**
* Execute a Closure using this object as its first parameter.
*
* @param Closure $closure
* @return $this
*/
public function applyClosure(Closure $closure)
{
call_user_func($closure, $this);

return $this;
}

/**
* {@inheritdoc}
*/
public function __set($key, $value)
{
$this->validateKey($key);

parent::__set($key, $value);
}

/**
* {@inheritdoc}
*/
public function __call($method, $parameters)
{
$this->validateKey($method);

return parent::__call($method, $parameters);
}

/**
* Ensure that the key already exists.
*
* @param string $key
*/
protected function validateKey($key)
{
if (! array_key_exists($key, $this->attributes)) {
throw new InvalidArgumentException("Key [$key] is not allowed.");
}
}
}
Loading