Laravel Excel (https://github.com/Maatwebsite/Laravel-Excel) turned 4 years last November and has reached almost 6 million Packagist downloads. A good time to reflect on 4,5 years of open source development.
On the 9th of November 2013 I made the first public commit to Laravel Excel. The code was an extremely simple wrapper around PHPExcel that we had used for our first Laravel 4.0 projects. In the spirit of open-source we decided to give something back to the community and put it on Github.
Laravel Excel 0.1 had a very simple syntax. It took away all the boilerplate code that was needed for creating exports with PHPExcel.
Excel::create('ExcelName')
->sheet('SheetName')
->with(array('data', 'data'))
->export('xls');
A few days later we added support for creating Excels from Blade-views.
Excel::loadView('folder.file', array('data'))
->export('xls');
This feature made the package popular very fast. We kept extending the package with tons of convenience methods.
Laravel Excel 1.0 was a first rewrite that cleaned-up the code a bit more. It also added a bunch more convenience methods.
Excel::create('Laravel Excel', function($excel) {
$excel->sheet('Excel sheet', function($sheet) {
$sheet->setOrientation('landscape');
});
})->export('xls');
Laravel Excel 2.0 brought the package to the new Laravel 5.0 release. It added some new concepts like “ExcelFile” and “NewExcelFile” injections. However I don’t think a lot of people have ever used it this way.
public function importUserList(UserListImport $import)
{
// get the results
$results = $import->get();
}
On the 3th of June 2016 we reached 1 million downloads on packagist and 6 million downloads in March 2018.
While having a lot of people using the library is a great feeling (we celebrated the 1 million downloads with a nice company diner! ), it can be a bit overwhelming too.
When so many people are using it, it means you are catering for a lot more needs than just your own. This created the necessity to add a lot of convenience methods that we didn’t use and never would use.
A lot of users also means that we receive a lot of Github issues, e-mails and tweets. If I had to classify the questions into categories, they would be as follows:
Answering these questions started to get frustrating and as a result I didn’t put a lot of effort into maintaining the software anymore. The package would only get the minimum necessary changes to support new Laravel versions.
The package was born out of a need to have a simple, elegant syntax for creating exports/imports into our own projects. However the current state of the package doesn’t cater for our needs anymore. 1% of the package caters for 100% of our needs.
We also quickly reached the maximum capabilities of the package, when creating more complex exports/imports. We would find ourselves creating custom solutions for these situations, instead of using the package.
Instead of celebrating another x million downloads, I would get a negative feeling of seeing another 5 daily issues pop up in my mailbox. It did anything but motivate me.
I took several attempts at “fixing” the package. With fixing I mean, trying to make it work for everybody's needs (feature, performance, …). At 10% of each rewrite I would realise that it’s not really possible and would lose motivation.
Another take I had on rewriting the package was to decouple everything. The idea was that the package would work the same for PHPExcel, PhpSpreadsheet, Spout, League/Csv and it would be as easy as switching the driver in the config.
Even though it’s perfectly possible to write something like this, it’s a lot of work. Hundreds of interfaces, hundreds of adapters, thousands of convenience methods. Hours of researching how everything works in the several packages.
Instead of the package becoming easier to use, it became cluttered, configuration heavy and more difficult to use.
Again after 10% of such rewrite, all motivation would be gone.
I asked myself the following questions:
My answers changed over time.
When originally writing the package, I answered them like this:
The more users started using the package, the more my answers would change.
I started caring more about the needs of others than our own needs.
Those needs didn’t directly match with our vision or priorities. This sometimes evoked negative reactions. A negative atmosphere doesn’t contribute to moving forward. As open source is based on a personal effort without commercial obligations, it would be nice if the users of the software would show a minimum of respect.
The following tweets really helped me greatly in understanding my responsibility and gave me a first peace of mind.
We’re very sorry for any inconvenience caused by our free, open source, and 100% optional software.
— Javan Makhmali (@javan) February 4, 2018
If your business depends on open source make sure that you either:
— Freek Van der Herten (@freekmurze) February 25, 2018
- can swap it out for an alternative
- can fork and maintain it yourself
Please don’t go complaining to the open source maintainers, they shouldn’t have to care about your business.https://t.co/weVOaXLSya
I continued my Open-Source Rehab by disabling my Github notifications and decided to not look at the package more than once a day. This gave me more peace of mind to think about what I want to do with the package.
Re-watching Taylor Otwell’s “Laravel — Lessons Learned” ( php[world] — 2015), gave me the inspiration I needed.
In his talk Taylor refers to “Superpower Syntax”:
While this was the original goal of the package, because of all the features that were added to it, it became the opposite of it.
Laravel Excel used to be an opinionated PHPExcel, but became an un-opinionated Excel for Laravel.
Another thing Taylor mentions is to not over-architect problems you don’t have. The less code is necessary to solve the problem, the better. (Kent Beck design rules)
I asked myself the following questions:
Mainly myself. It needs to add value to my own use-case.
My dream syntax would be, as least code as possible to create an export. The first thing I came up with was:
return Excel::download(new InvoicesExport(2018), 'invoices.xlsx');
I re-iterated over my idea and found out I could actually make it even nicer. By using Laravel’s Responsable interface
return new InvoicesExport(2018);
The easiest and most expressive way I could come up with to create an export of all my invoices of the current year was as follows:
use PhpOffice\PhpSpreadsheet\Shared\Date;
class InvoicesExport implements FromQuery, WithMapping, WithHeadings
{
use Exportable;
protected $year;
public function __construct(int $year)
{
$this->year = $year;
$this->fileName = 'invoices_' . $year . '.xlsx';
}
public function query()
{
return Invoice::query()
->whereYear('created_at', $this->year);
}
public function headings(): array
{
return [
'#',
'Date',
];
}
public function map($row): array
{
return [
$row->invoice_number,
Date::dateTimeToExcel($invoice->created_at),
];
}
}
In my controller I would start the export like:
return (new InvoicesExport(2018))->download('invoices.xlsx');
My goal for Laravel Excel 3.0 is to cater our own needs first and only add convenience methods that we need and would use ourselves, instead of re-inventing the PhpSpreadsheet “wheel”. The less code to solve the problem, the easier it should be to maintain.
3.0 will break full backwards compatibility with 2.1.
If a convenience method does not exist, there’s always the option to interact with PhpSpreadsheet directly or create a macro.
Writer::macro('setCreator', function (Writer $writer, string $creator) {
$writer->getProperties()->setCreator($creator);
});
Sheet::macro('setOrientation', function (Sheet $sheet, $orientation) {
return $sheet->getPageSetup()->setOrientation($orientation);
});
In your Export class, you can call either the macro or the native method by hooking into events:
class InvoicesExport implements WithEvents
{
public function registerEvents(): array
{
return [
BeforeExport::class => function(BeforeExport $event) {
// Macro
$event->writer->setCreator('Patrick');
// Or via magic __call
$event->writer
->getProperties()
->setCreator('Patrick');
},
AfterSheet::class => function(AfterSheet $event) {
// Macro
$event->sheet
->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
// Or via magic __call
$event->sheet
->getPageSetup()
->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
},
];
}
}
Laravel Excel 3.0 will be released the 15th of March 2018 and will mainly focus on exports.
2.1 will be supported till 15th of May 2018, as we won’t be able to support it for long because the parent package is abandoned. The package will remain installable via Packagist.
3.1 will focus on imports. There’s no planned release date for it yet.
Originally posted at: https://medium.com/maatwebsite/laravel-excel-lessons-learned-7fee2812551
I work at Maatwebsite on weekdays and I am a passionate filmmaker in my free time. Find me on Twitter & Github.