2021-08-27 06:46:27 -04:00
< ? php
namespace Illuminate\Database\Eloquent\Relations\Concerns ;
use Closure ;
use Illuminate\Database\Eloquent\Builder ;
use Illuminate\Database\Query\JoinClause ;
use Illuminate\Support\Arr ;
use InvalidArgumentException ;
trait CanBeOneOfMany
{
/**
* Determines whether the relationship is one - of - many .
*
* @ var bool
*/
protected $isOneOfMany = false ;
/**
* The name of the relationship .
*
* @ var string
*/
protected $relationName ;
/**
* The one of many inner join subselect query builder instance .
*
* @ var \Illuminate\Database\Eloquent\Builder | null
*/
protected $oneOfManySubQuery ;
/**
* Add constraints for inner join subselect for one of many relationships .
*
* @ param \Illuminate\Database\Eloquent\Builder $query
* @ param string | null $column
* @ param string | null $aggregate
* @ return void
*/
abstract public function addOneOfManySubQueryConstraints ( Builder $query , $column = null , $aggregate = null );
/**
* Get the columns the determine the relationship groups .
*
* @ return array | string
*/
abstract public function getOneOfManySubQuerySelectColumns ();
/**
* Add join query constraints for one of many relationships .
*
2022-03-14 16:22:30 -04:00
* @ param \Illuminate\Database\Query\JoinClause $join
2021-08-27 06:46:27 -04:00
* @ return void
*/
abstract public function addOneOfManyJoinSubQueryConstraints ( JoinClause $join );
/**
* Indicate that the relation is a single result of a larger one - to - many relationship .
*
* @ param string | array | null $column
2022-03-14 16:22:30 -04:00
* @ param string | \Closure | null $aggregate
2021-08-27 06:46:27 -04:00
* @ param string | null $relation
* @ return $this
*
* @ throws \InvalidArgumentException
*/
public function ofMany ( $column = 'id' , $aggregate = 'MAX' , $relation = null )
{
$this -> isOneOfMany = true ;
$this -> relationName = $relation ? : $this -> getDefaultOneOfManyJoinAlias (
$this -> guessRelationship ()
);
$keyName = $this -> query -> getModel () -> getKeyName ();
$columns = is_string ( $columns = $column ) ? [
$column => $aggregate ,
$keyName => $aggregate ,
] : $column ;
if ( ! array_key_exists ( $keyName , $columns )) {
$columns [ $keyName ] = 'MAX' ;
}
if ( $aggregate instanceof Closure ) {
$closure = $aggregate ;
}
foreach ( $columns as $column => $aggregate ) {
if ( ! in_array ( strtolower ( $aggregate ), [ 'min' , 'max' ])) {
throw new InvalidArgumentException ( " Invalid aggregate [ { $aggregate } ] used within ofMany relation. Available aggregates: MIN, MAX " );
}
$subQuery = $this -> newOneOfManySubQuery (
$this -> getOneOfManySubQuerySelectColumns (),
$column , $aggregate
);
if ( isset ( $previous )) {
$this -> addOneOfManyJoinSubQuery ( $subQuery , $previous [ 'subQuery' ], $previous [ 'column' ]);
2022-03-14 16:22:30 -04:00
}
if ( isset ( $closure )) {
2021-08-27 06:46:27 -04:00
$closure ( $subQuery );
}
if ( ! isset ( $previous )) {
$this -> oneOfManySubQuery = $subQuery ;
}
if ( array_key_last ( $columns ) == $column ) {
$this -> addOneOfManyJoinSubQuery ( $this -> query , $subQuery , $column );
}
$previous = [
'subQuery' => $subQuery ,
'column' => $column ,
];
}
$this -> addConstraints ();
2022-03-14 16:22:30 -04:00
$columns = $this -> query -> getQuery () -> columns ;
if ( is_null ( $columns ) || $columns === [ '*' ]) {
$this -> select ([ $this -> qualifyColumn ( '*' )]);
}
2021-08-27 06:46:27 -04:00
return $this ;
}
/**
* Indicate that the relation is the latest single result of a larger one - to - many relationship .
*
* @ param string | array | null $column
* @ param string | null $relation
* @ return $this
*/
public function latestOfMany ( $column = 'id' , $relation = null )
{
return $this -> ofMany ( collect ( Arr :: wrap ( $column )) -> mapWithKeys ( function ( $column ) {
return [ $column => 'MAX' ];
2022-03-14 16:22:30 -04:00
}) -> all (), 'MAX' , $relation );
2021-08-27 06:46:27 -04:00
}
/**
* Indicate that the relation is the oldest single result of a larger one - to - many relationship .
*
* @ param string | array | null $column
* @ param string | null $relation
* @ return $this
*/
public function oldestOfMany ( $column = 'id' , $relation = null )
{
return $this -> ofMany ( collect ( Arr :: wrap ( $column )) -> mapWithKeys ( function ( $column ) {
return [ $column => 'MIN' ];
2022-03-14 16:22:30 -04:00
}) -> all (), 'MIN' , $relation );
2021-08-27 06:46:27 -04:00
}
/**
* Get the default alias for the one of many inner join clause .
*
* @ param string $relation
* @ return string
*/
protected function getDefaultOneOfManyJoinAlias ( $relation )
{
return $relation == $this -> query -> getModel () -> getTable ()
? $relation . '_of_many'
: $relation ;
}
/**
* Get a new query for the related model , grouping the query by the given column , often the foreign key of the relationship .
*
* @ param string | array $groupBy
* @ param string | null $column
* @ param string | null $aggregate
* @ return \Illuminate\Database\Eloquent\Builder
*/
protected function newOneOfManySubQuery ( $groupBy , $column = null , $aggregate = null )
{
$subQuery = $this -> query -> getModel ()
2022-03-14 16:22:30 -04:00
-> newQuery ()
-> withoutGlobalScopes ( $this -> removedScopes ());
2021-08-27 06:46:27 -04:00
foreach ( Arr :: wrap ( $groupBy ) as $group ) {
$subQuery -> groupBy ( $this -> qualifyRelatedColumn ( $group ));
}
if ( ! is_null ( $column )) {
2022-03-14 16:22:30 -04:00
$subQuery -> selectRaw ( $aggregate . '(' . $subQuery -> getQuery () -> grammar -> wrap ( $subQuery -> qualifyColumn ( $column )) . ') as ' . $subQuery -> getQuery () -> grammar -> wrap ( $column . '_aggregate' ));
2021-08-27 06:46:27 -04:00
}
$this -> addOneOfManySubQueryConstraints ( $subQuery , $groupBy , $column , $aggregate );
return $subQuery ;
}
/**
* Add the join subquery to the given query on the given column and the relationship ' s foreign key .
*
* @ param \Illuminate\Database\Eloquent\Builder $parent
* @ param \Illuminate\Database\Eloquent\Builder $subQuery
* @ param string $on
* @ return void
*/
protected function addOneOfManyJoinSubQuery ( Builder $parent , Builder $subQuery , $on )
{
$parent -> beforeQuery ( function ( $parent ) use ( $subQuery , $on ) {
$subQuery -> applyBeforeQueryCallbacks ();
$parent -> joinSub ( $subQuery , $this -> relationName , function ( $join ) use ( $on ) {
2022-03-14 16:22:30 -04:00
$join -> on ( $this -> qualifySubSelectColumn ( $on . '_aggregate' ), '=' , $this -> qualifyRelatedColumn ( $on ));
2021-08-27 06:46:27 -04:00
$this -> addOneOfManyJoinSubQueryConstraints ( $join , $on );
});
});
}
/**
* Merge the relationship query joins to the given query builder .
*
2022-03-14 16:22:30 -04:00
* @ param \Illuminate\Database\Eloquent\Builder $query
2021-08-27 06:46:27 -04:00
* @ return void
*/
protected function mergeOneOfManyJoinsTo ( Builder $query )
{
$query -> getQuery () -> beforeQueryCallbacks = $this -> query -> getQuery () -> beforeQueryCallbacks ;
$query -> applyBeforeQueryCallbacks ();
}
/**
* Get the query builder that will contain the relationship constraints .
*
* @ return \Illuminate\Database\Eloquent\Builder
*/
protected function getRelationQuery ()
{
return $this -> isOneOfMany ()
? $this -> oneOfManySubQuery
: $this -> query ;
}
/**
* Get the one of many inner join subselect builder instance .
*
* @ return \Illuminate\Database\Eloquent\Builder | void
*/
public function getOneOfManySubQuery ()
{
return $this -> oneOfManySubQuery ;
}
/**
* Get the qualified column name for the one - of - many relationship using the subselect join query ' s alias .
*
* @ param string $column
* @ return string
*/
public function qualifySubSelectColumn ( $column )
{
return $this -> getRelationName () . '.' . last ( explode ( '.' , $column ));
}
/**
* Qualify related column using the related table name if it is not already qualified .
*
* @ param string $column
* @ return string
*/
protected function qualifyRelatedColumn ( $column )
{
2022-03-14 16:22:30 -04:00
return str_contains ( $column , '.' ) ? $column : $this -> query -> getModel () -> getTable () . '.' . $column ;
2021-08-27 06:46:27 -04:00
}
/**
* Guess the " hasOne " relationship ' s name via backtrace .
*
* @ return string
*/
protected function guessRelationship ()
{
return debug_backtrace ( DEBUG_BACKTRACE_IGNORE_ARGS , 3 )[ 2 ][ 'function' ];
}
/**
* Determine whether the relationship is a one - of - many relationship .
*
* @ return bool
*/
public function isOneOfMany ()
{
return $this -> isOneOfMany ;
}
/**
* Get the name of the relationship .
*
* @ return string
*/
public function getRelationName ()
{
return $this -> relationName ;
}
}