Métodos de modelo personalizado de Laravel

7 minutos de lectura

avatar de usuario
jeremy harris

Cada vez que agrego lógica adicional a los modelos Eloquent, termino teniendo que convertirlo en un static (es decir, menos que ideal) para llamarlo desde la fachada del modelo. Intenté buscar mucho sobre cómo hacer esto de la manera correcta y casi todos los resultados hablan sobre la creación de métodos que devuelven partes de una interfaz de Query Builder. Estoy tratando de descubrir cómo agregar métodos que puedan devolver cualquier cosa y ser llamados usando la fachada del modelo.

Por ejemplo, digamos que tengo un modelo llamado Car y quiero conseguirlos todos:

$cars = Car::all();

Genial, excepto por ahora, digamos que quiero ordenar el resultado en una matriz multidimensional por make para que mi resultado se vea así:

$cars = array(
  'Ford' => array(
     'F-150' => '...',
     'Escape' => '...',
  ),
  'Honda' => array(
     'Accord' => '...',
     'Civic' => '...',
  ),
);

Tomando ese ejemplo teórico, tengo la tentación de crear un método que pueda llamarse así:

$cars = Car::getAllSortedByMake();

Por un momento, olvidemos el terrible nombre del método y el hecho de que está estrechamente relacionado con la estructura de datos. Si hago un método como este en el modelo:

public function getAllSortedByMake()
{
   // Process and return resulting array
   return array('...');
}

Y finalmente llámelo en mi controlador, obtendré esta excepción:

El método no estático Car::getAllSortedByMake() no debe llamarse estáticamente, asumiendo que $this proviene de un contexto incompatible

TL;DR: ¿Cómo puedo agregar una funcionalidad personalizada que tenga sentido para estar en el modelo sin convertirlo en un método estático y llamarlo usando la fachada del modelo?


Editar:

Este es un ejemplo teórico. Tal vez una reformulación de la pregunta tendría más sentido. ¿Por qué ciertos métodos no estáticos como all() o which() disponible en la fachada de un modelo Eloquent, pero no se agregaron métodos adicionales al modelo? Esto significa que el __call se está utilizando el método mágico, pero ¿cómo puedo hacer que reconozca mis propias funciones en el modelo?

Probablemente un mejor ejemplo sobre la “clasificación” es si necesito ejecutar un cálculo o algoritmo en una pieza de datos:

$validSPG = Chemical::isValidSpecificGravity(-1.43);

Para mí, tiene sentido que algo así esté en el modelo, ya que es específico del dominio.

  • Comience por tener dos tablas de datos: manufacturers y modelsasi que manufacturers contiene “Ford”, “Honda”, etc y models con un manufacturer_id vinculando el model hacia manufacturer y que contiene “F-150”, “Escape”, “Accord”, “Civic”, etc.

    – Marcos Baker

    14 mayo 2014 a las 15:09


  • @MarkBaker Este fue un ejemplo teórico. Mi pregunta es más de un nivel fundamental, como por qué es all() accesible por la fachada? No es un método estático, lo que significa que el __call se está utilizando el método mágico. Debido a eso, ¿por qué es arbitraryMethodICreate() ¿Inaccesible?

    –Jeremy Harris

    14 mayo 2014 a las 15:30

  • ver Illuminate\Database\Eloquent\Model, all es un método estático

    –Jeff Lambert

    14 mayo 2014 a las 15:34

  • cilosis: la razón de eso es simple: all() es de hecho un método estático en el Modely __call no se llama en esta situación. Hay más métodos estáticos en el Model clase, y otros que puedes usar de la manera Model::method() son procesados ​​por __callStatic después __call métodos mágicos y pasó a la Eloquent Builder clase.

    – Jarek Tkaczyk

    14 mayo 2014 a las 15:34

  • Esto podría saludarte codezen.io/most-utility-laravel-collection-methods

    – Sapnesh Naik

    8 de mayo de 2020 a las 6:07

Mi pregunta es más de un nivel fundamental, como ¿por qué se puede acceder a all() a través de la fachada?

Si observa el Laravel Core, all() es en realidad una función estática

public static function all($columns = array('*'))

Tienes dos opciones:

public static function getAllSortedByMake()
{
    return Car::where('....')->get();
}

o

public function scopeGetAllSortedByMake($query)
{
    return $query->where('...')->get();
}

Ambos te permitirán hacer

Car::getAllSortedByMake();

  • Ahh, había oído hablar de los ámbitos en Eloquent, pero aún no los he usado. ¡Gracias! veo que all() es estático ahora (eso es lo que obtengo por no verificar). Siempre había escuchado que estos métodos no eran y solo usaba patrones de diseño para Aparecer de esa manera para la usabilidad mientras se mantiene la capacidad de prueba.

    –Jeremy Harris

    14 mayo 2014 a las 15:40


  • Sí, la mayoría de las funciones no son estáticas, solo algunas de las del modelo por alguna razón, por lo que estoy seguro de que Taylor tiene una buena razón, pero está por encima de mi conocimiento.

    – Laurence

    14 mayo 2014 a las 15:43

  • Tenga en cuenta que “los ámbitos siempre deben devolver una instancia del generador de consultas”. Así que si usas ->get() debe usar un método estático y no la variante de alcance. También tenga en cuenta que los ámbitos se pueden encadenar: Car::GetAllSortedByMake()->GetAllSortedByMake()->otherScopeYouHaveDefined()->where(...)->get().

    – aserrado

    3 de enero de 2016 a las 12:36


  • no use ámbitos porque la consulta no es la misma porque los ámbitos están aislados

    – fico7489

    7 ago. 2018 a las 6:00

En realidad, puede extender Eloquent Builder y poner métodos personalizados allí.

Pasos para extender el constructor:

1.Crear constructor personalizado

<?php

namespace App;

class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
    public function test()
    {
        $this->where(['id' => 1]);

        return $this;
    }
}

2. Agregue este método a su modelo base:

public function newEloquentBuilder($query)
{
    return new CustomBuilder($query);
}

3. Ejecute la consulta con métodos dentro de su generador personalizado:

User::where('first_name', 'like', 'a')
    ->test()
    ->get();

para el código anterior generado, la consulta mysql será:

select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null

PD:

primer lorenzo El ejemplo es un código más adecuado para su repositorio, no para el modelo, pero tampoco puede canalizar más métodos con este enfoque:

public static function getAllSortedByMake()
{
    return Car::where('....')->get();
}

segundo lorenzo el ejemplo es el evento peor.

public function scopeGetAllSortedByMake($query)
{
    return $query->where('...')->get();
}

Mucha gente sugiere usar ámbitos para extender el generador de laravel, pero esa es en realidad una mala solución porque los ámbitos están aislados por el generador elocuente y no obtendrá la misma consulta con los mismos comandos dentro y fuera del alcance. Propuse relaciones públicas para cambiar si los alcances deberían estar aislados, pero Taylor me ignoró.

Más explicación: por ejemplo, si tiene ámbitos como este:

public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
    $builder->where($column, $operator, $value, $boolean);
}

y dos preguntas elocuentes:

User::where(function($query){
    $query->where('first_name', 'like', 'a');
    $query->where('first_name', 'like', 'b');
})->get();

contra

User::where(function($query){
    $query->where('first_name', 'like', 'a');
    $query->whereTest('first_name', 'like', 'b');
})->get();

Las consultas generadas serían:

select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null

contra

select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null

a primera vista, las consultas parecen iguales, pero no las hay. Para esta consulta simple, tal vez no importe, pero para consultas complicadas sí, así que no use ámbitos para extender el generador 🙂

para un mejor código dinámico, en lugar de usar el nombre de clase de modelo “Car”,

simplemente use “estático” o “auto”

public static function getAllSortedByMake()
{
    //to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
    return static::where('...');

    //or return already as collection object
    return static::where('...')->get();
}

Métodos personalizados del modelo Laravel -> la mejor manera es usar rasgos

  • Paso #1: Crea un rasgo
  • Paso #2: Agrega el rasgo al modelo
  • Paso #3: Usa el método
User::first()->confirmEmailNow()

aplicación/Modelo/Usuario.php

use App\Traits\EmailConfirmation;

class User extends Authenticatable
{
    use EmailConfirmation;
    //...
}

app/Traits/EmailConfirmation.php

<?php
namespace App\Traits;

trait EmailConfirmation
{
    /**
     * Set email_verified_at to now and save.
     *
     */
    public function confirmEmailNow()
    {
        $this->email_verified_at = now();
        $this->save();
        return $this;
    }
}

¿Ha sido útil esta solución?