Мы хотели бы расширить класс JoinClause, который Query \ Builder использует для формулирования предложений соединения.
Мы уже расширили построитель запросов BaseModel следующим образом: (По сути, CoreBaseModel - это просто модель с некоторыми дополнительными функциями.)
abstract class BaseModel extends CoreBaseModel
{
function newBaseQueryBuilder()
{
return new BaseModelQueryBuilder($this->getConnection());
}
}
Пример кода в BaseModelQueryBuilder:
class BaseModelQueryBuilder extends Builder
{
function findOverlapping($from, $till, $from_column, $till_column)
{
return $this
->orWhereBetween($from_column, [$from, $till])
->orWhereBetween($till_column, [$from, $till])
->orWhere(function($query) use($from_column, $till_column, $from, $till)
{
//Around
$query
->where($from_column, '<=' , $from)
->where($till_column, '>=', $till);
});
}
}
Это хорошо, потому что затем вы можете использовать функцию findOverlapping не только в каждом запросе, но и в каждом подзапросе этой модели.
Проблема в том, что это не работает для расширенных предложений соединения:
BaseModel::join('table_name', function($join)
{
$join->where(function($query)
{
$query->findOverlapping('2018-01-01', '2018-21-31', 'from', 'till');
});
});
Ошибка:
Call to undefined method Illuminate\Database\Query\JoinClause::findOverlapping()
Итак, я копался, пытаясь найти решение этой проблемы, и нашел функцию соединения на Illuminate\Database\Query\Builder
, которая выглядит так:
/**
* Add a join clause to the query.
*
* @param string $table
* @param \Closure|string $first
* @param string|null $operator
* @param string|null $second
* @param string $type
* @param bool $where
* @return $this
*/
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
{
$join = new JoinClause($this, $type, $table);
// If the first "column" of the join is really a Closure instance the developer
// is trying to build a join with a complex "on" clause containing more than
// one condition, so we'll add the join and call a Closure with the query.
if ($first instanceof \Closure) {
call_user_func($first, $join);
$this->joins[] = $join;
$this->addBinding($join->getBindings(), 'join');
}
// If the column is simply a string, we can assume the join simply has a basic
// "on" clause with a single condition. So we will just build the join with
// this simple join clauses attached to it. There is not a join callback.
else {
$method = $where ? 'where' : 'on';
$this->joins[] = $join->$method($first, $operator, $second);
$this->addBinding($join->getBindings(), 'join');
}
return $this;
}
Итак, я скопировал функцию на BaseModelQueryBuilder
, сделал копию класса Illuminate\Database\Query\JoinClause
и назначил ее первому оператору замещающей функции соединения:
$join = new BaseModelJoinClause($this, $type, $table)
Обратите внимание, что я не менял код в классе BaseModelJoinClause
по сравнению с исходным классом JoinClause
(кроме операторов use
и пространства имен c). Тем не менее, даже с этим тонким изменением (назначен только новый класс, который является точной копией оригинала) все запросы начинают давать сбой с такими ошибками, как: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax
.
Если у кого-то есть советы по решению этой проблемы или есть опыт расширения класса JoinClause на laravel, любая помощь приветствуется :) Если вы думаете, что вам нужна дополнительная информация, дайте мне знать.
Спасибо за отзыв, приятно видеть, что разработчики laravel позаботились об этих вещах :)
В последней версии 5.7.20 добавлен метод
Builder::newJoinClause()
для упрощения расширения классаJoinClause
: github.com/laravel/framework/pull/26948