Laravel Excel — Lessons Learned

Laravel Excel — Lessons Learned

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.

Some history

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.

Version 0.*

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.

Version 1.*

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');

Version 2.*

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();
}

The Support conundrum

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.

New features

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.

Support questions

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:

  • Genuine bugs / performance issues.
  • Not understanding how the package works / have not read documentation.
  • Not understanding how the underlying package works.
  • Asking the same question as previous people already asked.
  • Asking for free commercial support.

Trouble in Paradise

Activity slowdown

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.

Using the packages in own projects

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.

Motivation

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.


Trying to fix it

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.

TheRightWay™

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.


Making Paradise a bit more Paradise again

I asked myself the following questions:

  • Why open-source?
  • Why writing this package?
  • Why maintaining this package?

My answers changed over time.

0 downloads

When originally writing the package, I answered them like this:

  • To give something back to the community.
  • To have a simple solution across all our projects.
  • Because we are actively using the package ourselves.

1 million downloads

The more users started using the package, the more my answers would change.

  • Because 1 million people are using it.
  • Because 1 million people are using it.
  • Because 1 million people are using it.

I started caring more about the needs of others than our own needs.

Open Source Rehab

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.

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.


Laravel — Lessons Learned

Re-watching Taylor Otwell’s “Laravel — Lessons Learned” ( php[world] — 2015), gave me the inspiration I needed.

Superpower Syntax

In his talk Taylor refers to “Superpower Syntax”:

  • Take an un-opinionated thing and give it more opinions.
  • Make people’s lives easier.

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.

Less code is better

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)


Retrospective

I asked myself the following questions:

Who needs to like this package?

Mainly myself. It needs to add value to my own use-case.

What does my dream syntax look like?

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');

Can I make it even easier and nicer to use?

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);

How can I make myself liking (and using) it?

  • Ease-of-use
  • Expressive syntax
  • As less restrictions as possible

Laravel Excel 3.0

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');

Goals

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);
            },
        ];
    }
}

Roadmap

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

Patrick Brouwers
By Patrick Brouwers

I work at Maatwebsite on weekdays and I am a passionate filmmaker in my free time. Find me on Twitter & Github.