Comprobar si existe la relación PerteneceaMuchos – Laravel

7 minutos de lectura

Dos de mis tablas (clientes y productos) tienen una relación ManyToMany usando blongToMany de Laravel y una tabla dinámica. Ahora quiero comprobar si un determinado cliente tiene un determinado producto.

Podría crear un modelo para verificar en la tabla dinámica, pero dado que Laravel no requiere este modelo para el método pertenecen a muchos, me preguntaba si hay otra forma de verificar si existe una determinada relación sin tener un modelo para la tabla dinámica.

Creo que la forma oficial de hacer esto es hacer:

$client = Client::find(1);
$exists = $client->products->contains($product_id);

Es algo derrochador en el sentido de que hará el SELECT consulta, obtener todos los resultados en un Collection y finalmente hacer un foreach sobre el Collection para encontrar un modelo con la ID que pasa. Sin embargo, no requiere modelar la tabla dinámica.

Si no le gusta el desperdicio de eso, puede hacerlo usted mismo en SQL/Query Builder, lo que tampoco requeriría modelar la tabla (ni requeriría obtener el Client modelo si aún no lo tiene para otros fines:

$exists = DB::table('client_product')
    ->whereClientId($client_id)
    ->whereProductId($product_id)
    ->count() > 0;

  • +1. Esta es la forma oficial y debe marcarse como la mejor respuesta.

    – Arda

    14 de octubre de 2014 a las 9:18

  • @DavidMIRV, ¿está recibiendo ese error del ejemplo de código superior? Si es así, ¿está seguro de que está llamando a la relación como un atributo? Si lo hace como una llamada de método (es decir, $exists = $client->products()->contains($product_id);) entonces sí dirá que contains no existe en BelongsToMany. Sin embargo, si lo llama como un atributo, BelongsToMany se convierte silenciosamente en una colección, que lo hace tener contains().

    – alexrussell

    23 de abril de 2015 a las 15:33

  • @GustavoStraube este método funciona, pero cargará una gran cantidad de datos en la memoria solo para verificar la existencia de la relación.

    – Alexei Mezenin

    3 de enero de 2018 a las 19:37

  • @AlexeyMezenin sí, estoy de acuerdo contigo. En mi caso, sin embargo, ya estoy cargando los modelos relacionados de todos modos. Por lo tanto, no se cargan datos adicionales. Gracias por la información, de todos modos. Probablemente ayudará a otras personas a llegar a esta respuesta.

    – Gustavo Straube

    3 de enero de 2018 a las 20:08

  • Advertencia: esto hidratará todos los modelos recuperados en la consulta, ocupando más memoria que una consulta sin procesar realizada directamente con la biblioteca DB. Puede ser un problema si tienes muchos modelos.

    – henriroyat

    7 dic 2021 a las 0:37

avatar de usuario
Mouagip

La pregunta es bastante antigua, pero esto puede ayudar a otros que buscan una solución:

$client = Client::find(1);
$exists = $client->products()->where('products.id', $productId)->exists();

No hay “despilfarro” como en la solución de @alexrussell y la consulta también es más eficiente.

  • me sale error SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: cuando pruebo eso en mi modelo :/

    usuario4748790

    13/09/2016 a las 21:02

  • @George Entonces intente where('products.id', $productId).

    – Mouagip

    14 de septiembre de 2016 a las 9:29


  • No crea que necesita un alias de tabla allí, la identificación es suficiente. esto se determina en la relación de productos. La solución más limpia hasta ahora en mi humilde opinión.

    – Johan

    9 de marzo de 2018 a las 11:34

  • @Johan Necesitas el alias si, por ejemplo client tiene una identificación también. Eso es bastante común.

    – Mouagip

    9 de marzo de 2018 a las 13:36

  • Esta solución funciona de manera más estable. Tuve un problema cuando estaba iterando demasiado rápido con $client->products->contains($product_id);

    – Alex por dentro

    10 de junio de 2019 a las 15:37

La solución de Alex está funcionando, pero cargará un Client modelo y todo lo relacionado Product modelos de DB a la memoria y solo después de eso, verificará si existe la relación.

Una mejor manera Eloquent de hacerlo es usar whereHas() método.

1. No necesita cargar el modelo de cliente, solo puede usar su ID.

2. Tampoco necesita cargar todos los productos relacionados con ese cliente en la memoria, como lo hace Alex.

3. Una consulta SQL a la base de datos.

$doesClientHaveProduct = Product::where('id', $productId)
    ->whereHas('clients', function($q) use($clientId) {
        $q->where('id', $clientId);
    })
    ->count();

avatar de usuario
nielsstampe

Actualizar: No tomé en cuenta la utilidad de verificar relaciones múltiples, si ese es el caso, entonces @deczo tiene una respuesta mucho mejor a esta pregunta. Ejecutar solo una consulta para verificar todas las relaciones es la solución deseada.

    /**
     * Determine if a Client has a specific Product
     * @param $clientId
     * @param $productId
     * @return bool
     */
    public function clientHasProduct($clientId, $productId)
    {
        return ! is_null(
            DB::table('client_product')
              ->where('client_id', $clientId)
              ->where('product_id', $productId)
              ->first()
        );
    }

Puede poner esto en su modelo Usuario/Cliente o puede tenerlo en un ClientRepository y usarlo donde lo necesite.

if ($this->clientRepository->clientHasProduct($clientId, $productId)
{
    return 'Awesome';
}

Pero si ya definió la relación pertenece a muchos en un modelo de Client Eloquent, podría hacer esto, dentro de su modelo de Cliente, en su lugar:

    return ! is_null(
        $this->products()
             ->where('product_id', $productId)
             ->first()
    );

Los métodos de @nielsiano funcionarán, pero consultarán DB para cada par de usuario/producto, lo cual es un desperdicio en mi opinión.

Si no desea cargar todos los datos de los modelos relacionados, esto es lo que haría para un usuario unico:

// User model
protected $productIds = null;

public function getProductsIdsAttribute()
{
    if (is_null($this->productsIds) $this->loadProductsIds();

    return $this->productsIds;
}

public function loadProductsIds()
{
    $this->productsIds = DB::table($this->products()->getTable())
          ->where($this->products()->getForeignKey(), $this->getKey())
          ->lists($this->products()->getOtherKey());

    return $this;
}

public function hasProduct($id)
{
    return in_array($id, $this->productsIds);
}

Entonces simplemente puedes hacer esto:

$user = User::first();
$user->hasProduct($someId); // true / false

// or
Auth::user()->hasProduct($someId);

Solo se ejecuta 1 consulta, luego trabaja con la matriz.


La forma más fácil sería usando contains como sugirió @alexrussell.

Creo que esto es una cuestión de preferencia, por lo que, a menos que su aplicación sea bastante grande y requiera mucha optimización, puede elegir con lo que le resulte más fácil trabajar.

  • Gran respuesta @deczo y sorprendente uso de los ayudantes integrados de Laravel para hacer que las consultas sean dinámicas con respecto a claves/tablas. Tiendo a luchar por esto yo mismo, ¡pero esto lo lleva a un nuevo nivel! 😀

    – alexrussell

    6 de julio de 2014 a las 9:22

  • Gracias, todavía hay que jugar mucho con Eloquent para saber cuál de esos métodos devuelve solo una clave. col_id y que calificó/prefijó table.col_id. Desafortunadamente, Eloquent carece de consistencia con bastante frecuencia.

    – Jarek Tkaczyk

    6 de julio de 2014 a las 9:53

  • Sí, debo decir, con el mayor respeto posible por Taylor, cuanto más profundizas en Laravel, más inconsistencias irritantes encuentras y un código que no es demasiado claro (y sin comentarios/documentación utilizable). Pero bueno, ¡no es como si pudiera hacerlo mejor!

    – alexrussell

    6 de julio de 2014 a las 12:01

Hola a todos) Mi solución para este problema: creé una clase propia, extendida desde Eloquent, y extiendo todos mis modelos desde ella. En esta clase escribí esta función simple:

function have($relation_name, $id) {
    return (bool) $this->$relation_name()->where('id','=',$id)->count();
}

Para hacer una verificación de la relación existente, debe escribir algo como:

if ($user->have('subscribes', 15)) {
    // do some things
}

De esta manera genera sólo un SELECCIONE contar (…) consulta sin recibir datos reales de las tablas.

  • Gran respuesta @deczo y sorprendente uso de los ayudantes integrados de Laravel para hacer que las consultas sean dinámicas con respecto a claves/tablas. Tiendo a luchar por esto yo mismo, ¡pero esto lo lleva a un nuevo nivel! 😀

    – alexrussell

    6 de julio de 2014 a las 9:22

  • Gracias, todavía hay que jugar mucho con Eloquent para saber cuál de esos métodos devuelve solo una clave. col_id y que calificó/prefijó table.col_id. Desafortunadamente, Eloquent carece de consistencia con bastante frecuencia.

    – Jarek Tkaczyk

    6 de julio de 2014 a las 9:53

  • Sí, debo decir, con el mayor respeto posible por Taylor, cuanto más profundizas en Laravel, más inconsistencias irritantes encuentras y un código que no es demasiado claro (y sin comentarios/documentación utilizable). Pero bueno, ¡no es como si pudiera hacerlo mejor!

    – alexrussell

    6 de julio de 2014 a las 12:01

Para verificar la existencia de una relación entre 2 modelos, todo lo que necesitamos es una sola consulta en la tabla dinámica sin combinaciones.

Puedes lograrlo usando el incorporado newPivotStatementForId método:

$exists = $client->products()->newPivotStatementForId($product->id)->exists();

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad