Laravel 4 Eloquent Query Builder-сложные соединения с переменной

Я пытаюсь реплицировать соединение таким образом, используя построитель запросов laravel:

LEFT JOIN content_userdata
ON content_id = content.id
AND user_id = $user_id

я обнаружил, что могу делать дополнительные "ons", используя следующую функцию в моей модели, которая расширяет Eloquent

public function scopeJoinUserData($query, $user_id)
{
    return $query->leftJoin('content_userdata', function($join)
    {
        $join->on('content_userdata_content_id', '=', 'content.content_id')->on('content_userdata_user_id', '=', 10);
    });
}

но это создает две проблемы. Во-первых, я не могу получить переменную $user_id в функцию, а во-вторых, даже если я жестко закодирую ее для целей тестирования, как я сделал выше (для int "10"), Laravel заключает ее в " значение, что она интерпретируется как имя столбца, когда его не должно быть, например:

left join `content_userdata`
on `content_id` = `content`.`id`
and `user_id` = `10`

Итак, теперь у меня две проблемы.

  1. я не могу получить $user_id в функцию join при использовании областей запросов
  2. даже если бы я мог, я не могу отправить переменную в соединение, так как она всегда интерпретирует ее как имя столбца

зачем мне это делать? Я понимаю, что один из ответов может заключаться в том, чтобы поместить его в "где". Однако я пытаюсь сделать это таким образом, как присоединиться может не обязательно возвращать какие-либо результаты (следовательно, левое соединение), так как таблица content_userdata содержит такие вещи, как рейтинг пользователей для части контента. Если я использую where, то результаты ни с чем в таблице content_userdata не будут возвращены, где, как будто я могу поместить его в соединение, они будут возвращены из-за левого соединения.

есть ли в любом случае, чтобы достичь этого в Laravel, и если нет, то каковы альтернативы, очевидно, полностью меняя канаву Laravel сверху, но единственная альтернатива, которую я могу придумать, - получить userdata в отдельном запросе.

5 ответов


вам нужно передать переменную в закрытие с помощью use ключевое слово-которое импортирует переменную в область. Пример:

public function scopeJoinUserData($query, $user_id)
{
    return $query->leftJoin('content_userdata', function($join) use ($user_id)
    {
        $join->on('content_userdata_content_id', '=', 'content.content_id')
             ->on('content_userdata_user_id',    '=', DB::raw($user_id));
    });
}

это проблема, связанная с синтаксисом PHP, а не ограничение Laravel!


в принятом ответе, просто добавляя кавычки вокруг DB::raw часть запроса не будет полностью защищать его от SQL-инъекции. Просто пройти несколько цитат в свой user_id и увидеть. Для параметризации вы можете сделать что-то вроде этого:

public function scopeJoinUserData($query, $user_id)
{
    return $query->leftJoin('content_userdata', function($join)
        {
            $join->on('content_userdata_content_id', '=', 'content.content_id')
                 ->on('content_userdata_user_id',    '=', DB::raw('?'));
        }
    ->setBindings(array_merge($query->getBindings(),array($user_id)));
}

обратите внимание в этом примере, что вам не нужно передавать переменную в закрытие. В качестве альтернативы вы можете попробовать написать эту часть совершенно сырой.

обновление: Тейлор добавил joinWhere, leftJoinWhere... если у вас есть функция join, просто используйте ->where и ->orWhere из-за закрытия.


мне удалось исправить это самостоятельно, внизу есть примечание, почему это не совсем оптимально, но вот как это сделать в любом случае:

public function scopeJoinUserData($query, $user_id)
{
    return $query->leftJoin('content_userdata', function($join) use ($user_id)
    {
        $join->on('content_userdata_content_id', '=', 'content.content_id')->on('content_userdata_user_id', '=', DB::raw('"'.$user_id.'"'));
    });
}

обратите внимание на использование "use ($user_id)", как предложил @Half Crazed.

DB:: raw() используется для обертывания $user_id в кавычки, даже если это целое число, а не строка. Это остановит Laravel автоматически используя", что заставляет MySQL интерпретировать его как имя столбца.

производительность: одна вещь, чтобы отметить что запросы MySQL могут быть значительно быстрее при использовании целого числа, а не строки, и будут интерпретировать его как строку, если она обернута в кавычки. Я не беспокоюсь об этом сейчас, но я подумал, что должен упомянуть об этом, если другие используют это как решение.


почему бы вам просто не использовать отношения? В этом весь смысл ан ОРМ вроде красноречив?

что-то вроде этого;

class User extends Eloquent {
    public function userdata()
    {
        return $this->hasOne('Userdata');
    }
}

$result= User::find(1)->userdata();

редактировать, чтобы показать, что вы можете делать все, что хотите с отношениями

Вариант 1:

$place = new Place;

$array = $place->with(array('users' => function($query)
{
    $query->where('user_id', $user_id);
}))->get();

var_dump($array->toArray());

Вариант 2:

$place = new Place;

$array = $place->with('users')->where('user_id', $user_id)->get();

var_dump($array->toArray());

оба дают разные результаты - но вы получите идею


ваша первая проблема: вы должны использовать синтаксис PHP для закрытия в качестве ответа пол. О вашей второй проблеме, я думаю, часть AND user_id = $user_id запроса не относится к предложению JOIN, а к предложению WHERE, поскольку оно зависит только от одной таблицы, а не от обеих в этом отношении соединения. Я думаю, вы должны использовать такой подзапрос:

public function scopeJoinUserData($query, $user_id)
{
    return $query->leftJoin(\DB:raw("(SELECT * FROM content_userdata WHERE user_id = {$user_id}) AS t"), function($join)
    {
        $join->on('t.content_id', '=', 'content.content_id');
    });
}

однако, как вы видите, давайте убедимся, что $user_id переменная безопасна, потому что мы используем \DB:raw метод.