Antes de comenzar permite que te pregunte algo.
¿Cómo simplificarías esto para que este separado del controlador?
namespace App\Http\Controllers; use App\User; use Illuminate\Http\Request; use App\Reports\UserExcelReport; use Maatwebsite\Excel\Facades\Excel; class ReportController extends Controller { public function downloadExcelReport(Request $request) { Excel::create('users_report', function($excel) { // Set the file properties $excel->setTitle('Registered users') ->setCreator('Web App') ->setCompany('Client Company') ->setDescription('Fictional Users in Company'); //Create sheet $excel->sheet('Actual users', function($sheet) { //set general font style $sheet->setStyle(array( 'font' => array( 'name' => 'Calibri', 'size' => 15, 'bold' => false ) )); //set background to headers $sheet->cells('A1:E1', function($cells) { $cells->setBackground('#000000') ->setFontColor('#ffffff'); //set other properties }); $users = User::all(); $sheet->fromModel($users); }); })->download('xlsx'); } }
Si lo piensas un momento, es posible que intentes inyectar la clase Excel en el constructor del controlador y después de eso agregues un método que contenga la lógica que crea el archivo de Excel, para obtener algo como lo siguiente.
namespace App\Http\Controllers; use App\User; use Illuminate\Http\Request; use App\Reports\UserExcelReport; use Maatwebsite\Excel\Excel; class ReportController extends Controller { /** * @var Excel excel */ private $excel; public function __construct(Excel $excel) { $this->excel = $excel; } public function downloadExcelReport(Request $request) { $this->downloadReport(); } public function downloadReport() { $this->excel->create('users_report', function($excel) { // Set the file properties $excel->setTitle('Registered users') ->setCreator('Web App') ->setCompany('Client Company') ->setDescription('Fictional Users in Company'); //Create sheet $excel->sheet('Actual users', function($sheet) { //set general font style $sheet->setStyle(array( 'font' => array( 'name' => 'Calibri', 'size' => 15, 'bold' => false ) )); //set background to headers $sheet->cells('A1:E1', function($cells) { $cells->setBackground('#000000') ->setFontColor('#ffffff'); //set other properties }); $users = User::all(); $sheet->fromModel($users); }); })->download('xlsx'); } }
Pero si observas por un momento te das cuenta de que esto no soluciona absolutamente ¡nada!. A pesar de que el código parece ser más sencillo la lógica que crea el archivo sigue incrustada en el controlador.
Posiblemente este puede ser un panorama poco probable en tu vida, pero reorganizar el código para que sea más reutilizable no solo es necesario, es además una actividad muy común y estoy seguro que tarde o temprano vas a requerir reorganizar la forma en la que exportas datos a Excel.
La buena noticia es que, con solo tres pasos vas a lograr al final de este articulo un resultado como el siguiente.
class ReportController extends Controller { /** * @var Excel excel */ private $excel; public function __construct(UserExcelReport $excel) { $this->excel = $excel; } public function downloadExcelReport(Request $request) { $this->excel->download(); } }
¿sigues con curiosidad?, que te parece si comenzamos con el primer paso.
Paso 1: Cómo inyectar Laravel Excel en el controlador
Con el ejemplo anterior te diste cuenta que inyectar la clase Excel, y agregar un nuevo método no te aporta una solución como esperabas.
¿Entonces cómo puedo resolverlo?
Es posible que no lo creas, pero la respuesta está en la documentación y para beneficio tuyo y mío; Patrick Brouwers creo algo que llama NewExcelFile Injection, que nos permite inyectar la creación de archivos de Excel en los métodos del controlador y cuenta con una clase que se llama NewExcelFile.
Déjame mostrarte que contiene esta clase.
use Illuminate\Foundation\Application; use Maatwebsite\Excel\Excel; abstract class NewExcelFile extends File { /** * @param Application $app * @param Excel $excel */ public function __construct(Application $app, Excel $excel) { parent::__construct($app, $excel); $this->file = $this->createNewFile(); } /** * Get file * @return string */ abstract public function getFilename(); //other methods }
Dicho esto lo primero que harás, será extender la clase NewExcelFile como se muestra en el siguiente código.
use Maatwebsite\Excel\Files\NewExcelFile; class UserExcelReport extends NewExcelFile { /** * Get file * @return string */ public function getFilename() { return 'users_report'; } }
Con esto ya puedes inyectar la clase UserExcelReport en lugar de la clase Excel y tendrás un archivo con el nombre de users_report listo para ser usado.
Ahora que ya tienes la clase, no queda más que decidir donde la vas a usar. En este momento de seguro estas decidiendo entre el constructor y el método, pero si aceptas mi recomendación que te parece si lo haces nuevamente en el constructor.
Así que remplaza la clase Excel por UserExcelReport como sigue.
namespace App\Http\Controllers; use App\User; use Illuminate\Http\Request; use App\Reports\UserExcelReport; class ReportController extends Controller { /** * @var UserExcelReport excel */ private $excel; public function __construct(UserExcelReport $excel) { $this->excel = $excel; } public function downloadExcelReport(Request $request) { // Set the file properties $this->excel->setTitle('Registered users') ->setCreator('Web App') ->setCompany('Client Company') ->setDescription('Fictional Users in the Company'); //Create sheet $this->excel->sheet('Actual users', function($sheet) { //set general font style $sheet->setStyle(array( 'font' => array( 'name' => 'Calibri', 'size' => 15, 'bold' => false ) )); //set background to headers $sheet->cells('A1:E1', function($cells) { $cells->setBackground('#000000') ->setFontColor('#ffffff'); //set other properties }); $users = User::all(); $sheet->fromModel($users); })->download('xlsx'); } }
Es posible que pienses que esto no cambia mucho de lo que hiciste anteriormente, pero… si observas, te darás cuenta que ahora lo único que queda en el método downloadExcelReport es la lógica que requiere el archivo de Excel después de crearse.
En el siguiente paso esto lo veras de formas mas evidente.
Paso 2: Cómo separar la lógica del controlador
Es posible que tu mente te este diciendo en este momento: debe de haber alguna forma de mover toda esa lógica que te queda a otro lugar y dejar más despejado tu controlador.
Y eso es correcto, así que para mover la lógica que nos falta, vamos a usar un contrato que se llama ExportHandler y que ya nos proporciona Laravel Excel.
interface ExportHandler { /** * Handle the export * @param $file * @return mixed */ public function handle($file); }
El contrato tiene un solo método llamado handle y lo que recibe es una instancia de UserExcelReport. En este método vas a meter toda la lógica que tienes en el controlador, así que implementa ExportHandle creando una clase UserExcelReportHandler y que debe de quedar como te muestro en el siguiente ejemplo.
use Maatwebsite\Excel\Files\ExportHandler; use App\User; class UserExcelReportHandler implements ExportHandler { /** * Handle the export * @param $report * @return mixed * @internal param $file */ public function handle( $report ) { // Set the file properties $report->setTitle('Registered users') ->setCreator('Web App') ->setCompany('Client Company') ->setDescription('Fictional Users in Company'); //Create sheet $report->sheet('Actual users', function($sheet) { //set general font style $sheet->setStyle(array( 'font' => array( 'name' => 'Calibri', 'size' => 15, 'bold' => false ) )); //set background to headers cells $sheet->cells('A1:E1', function($cells) { $cells->setBackground('#000000') ->setFontColor('#ffffff'); //set other properties }); //Fill sheet $users = User::all(); $sheet->fromModel($users); })->download('xlsx'); } }
Paso 3: Como realizar los últimos ajustes
En este punto ya removiste toda la lógica de tu controlador al Handler y estas a un paso de terminar para dormir tranquilo.
Ocupar el cambio que acabas de realizar será tan sencillo como llamar a un solo método que ya tienes disponible en la clase UserExcelReport.
Puedes ver este cambio en el siguiente ejemplo.
class ReportController extends Controller { /** * @var UserExcelReport excel */ private $excel; public function __construct(UserExcelReport $excel) { $this->excel = $excel; } public function downloadExcelReport(Request $request) { $this->excel->handleExport(); } }
El método handleReport hace una llamada de forma dinámica a nuestro Handler, pero aún falta algo, la llamada a handleReport digamos que no es muy legible en el sentido de que no es muy expresiva así que te mostrare cómo podemos hacer el ajuste final.
Abre la clase UserExcelReport y agrega un nuevo método; digamos que le pones el nombre download (Lo sé, soy muy original con los nombres).
En el nuevo método solo requieres llamar a handleExport como puedes ver en el siguiente código.
use Maatwebsite\Excel\Files\NewExcelFile; class UserExcelReport extends NewExcelFile { /** * Get file * @return string */ public function getFilename() { return 'users_report'; } public function download() { return $this->handleExport(); } }
Por ultimo en el controlador realiza los ajustes que te muestro.
class ReportController extends Controller { /** * @var UserExcelReport excel */ private $excel; public function __construct(UserExcelReport $excel) { $this->excel = $excel; } public function downloadExcelReport(Request $request) { $this->excel->download(); } }
¿Qué te parece? es seguro que más expresivo, fácil de leer y lo mejor es que lograste el objetivo de separar como se genera el Excel de tu controlador.
Conclusión
Para finalizar puedes realizar algunos cambios adicionales para reducir aún más la dependencia que tiene el controlador con la clase UserExcelReport inyectando en su lugar a NewExcelFile. De esta forma el controlador solo dependerá de la clase abstracta y esto puede servirte para inyectar otras implementaciones de NewExcelFiles.
Espero que te hayas divertido siguiendo este tutorial. Si tienes alguna duda, crees que no fui claro, tienes otra forma de hacer las cosas o quieres discutir el tema; por favor deja tus comentarios aquí o mándame un mensaje en el slack de Laraveles.
Por último, si te gusto este articulo y te ha servido ¡comparte!