Renderer   C
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 375
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 21
dl 0
loc 375
rs 5.159
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 4 1
A isExport() 0 4 1
A isHtml() 0 4 1
F execute() 0 262 26
A calculateColumnWidth() 0 20 2
C setPrinting() 0 51 7
A setHeaderFooter() 0 7 1
1
<?php
2
/**
3
 * Output as an excel file.
4
 */
5
6
namespace ZfcDatagrid\Renderer\PHPExcel;
7
8
use PHPExcel;
9
use PHPExcel_Cell;
10
use PHPExcel_Cell_DataType;
11
use PHPExcel_Style_Alignment;
12
use PHPExcel_Style_Border;
13
use PHPExcel_Style_Color;
14
use PHPExcel_Style_Fill;
15
use PHPExcel_Worksheet_PageSetup;
16
use Zend\Http\Headers;
17
use Zend\Http\Response\Stream as ResponseStream;
18
use ZfcDatagrid\Column;
19
use ZfcDatagrid\Renderer\AbstractExport;
20
21
class Renderer extends AbstractExport
22
{
23
    public function getName()
24
    {
25
        return 'PHPExcel';
26
    }
27
28
    public function isExport()
29
    {
30
        return true;
31
    }
32
33
    public function isHtml()
34
    {
35
        return false;
36
    }
37
38
    public function execute()
39
    {
40
        $options       = $this->getOptions();
41
        $optionsExport = $options['settings']['export'];
42
43
        $optionsRenderer = $this->getOptionsRenderer();
44
45
        $phpExcel = new PHPExcel();
46
47
        // Sheet 1
48
        $phpExcel->setActiveSheetIndex(0);
49
        $sheet = $phpExcel->getActiveSheet();
50
        $sheet->setTitle($this->translate($optionsRenderer['sheetName']));
51
52
        if (true === $optionsRenderer['displayTitle']) {
53
            $sheet->setCellValue('A' . $optionsRenderer['rowTitle'], $this->getTitle());
54
            $sheet->getStyle('A' . $optionsRenderer['rowTitle'])
55
                ->getFont()
56
                ->setSize(15);
57
        }
58
59
        /*
60
         * Print settings
61
         */
62
        $this->setPrinting($phpExcel);
63
64
        /*
65
         * Calculate column width
66
         */
67
        $this->calculateColumnWidth($sheet, $this->getColumnsToExport());
68
69
        /*
70
         * Header
71
         */
72
        $xColumn = 0;
73
        $yRow    = $optionsRenderer['startRowData'];
74
        foreach ($this->getColumnsToExport() as $col) {
75
            /* @var $column Column\AbstractColumn */
76
            $sheet->setCellValueByColumnAndRow($xColumn, $yRow, $this->translate($col->getLabel()));
77
78
            $sheet->getColumnDimension(PHPExcel_Cell::stringFromColumnIndex($xColumn))->setWidth($col->getWidth());
79
80
            ++$xColumn;
81
        }
82
83
        /*
84
         * Data
85
         */
86
        $yRow = $optionsRenderer['startRowData'] + 1;
87
        foreach ($this->getData() as $row) {
88
            $xColumn = 0;
89
            foreach ($this->getColumnsToExport() as $col) {
90
                /* @var $col Column\AbstractColumn */
91
92
                $value = $row[$col->getUniqueId()];
93
                if (is_array($value)) {
94
                    $value = implode(PHP_EOL, $value);
95
                }
96
97
                /* @var $column Column\AbstractColumn */
98
                $currentColumn = PHPExcel_Cell::stringFromColumnIndex($xColumn);
99
                $cell          = $sheet->getCell($currentColumn . $yRow);
100
101
                switch (get_class($col->getType())) {
102
103
                    case Column\Type\Number::class:
104
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_NUMERIC);
105
                        break;
106
107
                    case Column\Type\DateTime::class:
108
                        /* @var $dateType Column\Type\DateTime */
109
                        $dateType = $col->getType();
110
111
                        if (! $value instanceof \DateTime && is_scalar($value)) {
112
                            $value = \DateTime::createFromFormat($dateType->getSourceDateTimeFormat(), $value);
113
                            if ($value instanceof \DateTime) {
114
                                $value->setTimezone(new \DateTimeZone($dateType->getSourceTimezone()));
115
                            }
116
                        }
117
118
                        if ($value instanceof \DateTime) {
119
                            // only apply this if we have a date object (else leave it blank)
120
                            $value->setTimezone(new \DateTimeZone($dateType->getOutputTimezone()));
121
                            $cell->setValue(\PHPExcel_Shared_Date::PHPToExcel($value));
0 ignored issues
show
Documentation introduced by
$value is of type object<DateTime>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
122
123
                            if ($dateType->getOutputPattern()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dateType->getOutputPattern() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
124
                                $outputPattern = $dateType->getOutputPattern();
125
                            } else {
126
                                $outputPattern = \PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME;
127
                            }
128
129
                            $cell->getStyle()
130
                                ->getNumberFormat()
131
                                ->setFormatCode($outputPattern);
132
                        }
133
                        break;
134
135
                    default:
136
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
137
                        break;
138
                }
139
140
                $columnStyle = $sheet->getStyle($currentColumn . $yRow);
141
                $columnStyle->getAlignment()->setWrapText(true);
142
143
                /*
144
                 * Styles
145
                 */
146
                $styles = array_merge($this->getRowStyles(), $col->getStyles());
147
                foreach ($styles as $style) {
148
                    /* @var $style Column\Style\AbstractStyle */
149
                    if ($style->isApply($row) === true) {
150
                        switch (get_class($style)) {
151
152
                            case Column\Style\Bold::class:
153
                                $columnStyle->getFont()->setBold(true);
154
                                break;
155
156
                            case Column\Style\Italic::class:
157
                                $columnStyle->getFont()->setItalic(true);
158
                                break;
159
160
                            case Column\Style\Color::class:
161
                                $columnStyle->getFont()
162
                                    ->getColor()
163
                                    ->setRGB($style->getRgbHexString());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Style\AbstractStyle as the method getRgbHexString() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\AbstractColor, ZfcDatagrid\Column\Style\BackgroundColor, ZfcDatagrid\Column\Style\Color. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
164
                                break;
165
166
                            case Column\Style\BackgroundColor::class:
167
                                $columnStyle->getFill()->applyFromArray([
168
                                    'type'  => \PHPExcel_Style_Fill::FILL_SOLID,
169
                                    'color' => [
170
                                        'rgb' => $style->getRgbHexString(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Style\AbstractStyle as the method getRgbHexString() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\AbstractColor, ZfcDatagrid\Column\Style\BackgroundColor, ZfcDatagrid\Column\Style\Color. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
171
                                    ],
172
                                ]);
173
                                break;
174
175
                            case Column\Style\Align::class:
176
                                switch ($style->getAlignment()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Style\AbstractStyle as the method getAlignment() does only exist in the following sub-classes of ZfcDatagrid\Column\Style\AbstractStyle: ZfcDatagrid\Column\Style\Align. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
177
                                    case Column\Style\Align::$RIGHT:
178
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
179
                                        break;
180
                                    case Column\Style\Align::$LEFT:
181
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
182
                                        break;
183
                                    case Column\Style\Align::$CENTER:
184
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
185
                                        break;
186
                                    case Column\Style\Align::$JUSTIFY:
187
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
188
                                        break;
189
                                    default:
190
                                        //throw new \Exception('Not defined yet: "'.get_class($style->getAlignment()).'"');
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
191
                                        break;
192
                                }
193
194
                                break;
195
196
                            case Column\Style\Strikethrough::class:
197
                                $columnStyle->getFont()->setStrikethrough(true);
198
                                break;
199
200
                            case Column\Style\Html::class:
201
                                // @todo strip the html?
202
                                break;
203
204
                            default:
205
                                throw new \Exception('Not defined yet: "' . get_class($style) . '"');
206
                                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
207
                        }
208
                    }
209
                }
210
211
                ++$xColumn;
212
            }
213
214
            ++$yRow;
215
        }
216
217
        /*
218
         * Autofilter, freezing, ...
219
         */
220
        $highest = $sheet->getHighestRowAndColumn();
221
222
        // Letzte Zeile merken
223
224
        // Autofilter + Freeze
225
        $sheet->setAutoFilter('A' . $optionsRenderer['startRowData'] . ':' . $highest['column'] . $highest['row']);
226
        $freezeRow = $optionsRenderer['startRowData'] + 1;
227
        $sheet->freezePane('A' . $freezeRow);
228
229
        // repeat the data header for each page!
230
        $sheet->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd($optionsRenderer['startRowData'], $optionsRenderer['startRowData']);
231
232
        // highlight header line
233
        $style = [
234
            'font' => [
235
                'bold' => true,
236
            ],
237
238
            'borders' => [
239
                'allborders' => [
240
                    'style' => PHPExcel_Style_Border::BORDER_MEDIUM,
241
                    'color' => [
242
                        'argb' => PHPExcel_Style_Color::COLOR_BLACK,
243
                    ],
244
                ],
245
            ],
246
            'fill' => [
247
                'type'       => PHPExcel_Style_Fill::FILL_SOLID,
248
                'startcolor' => [
249
                    'argb' => PHPExcel_Style_Color::COLOR_YELLOW,
250
                ],
251
            ],
252
        ];
253
        $range = 'A' . $optionsRenderer['startRowData'] . ':' . $highest['column'] . $optionsRenderer['startRowData'];
254
        $sheet->getStyle($range)->applyFromArray($style);
255
256
        // print borders
257
        $range = 'A' . $freezeRow . ':' . $highest['column'] . $highest['row'];
258
        $sheet->getStyle($range)->applyFromArray([
259
            'borders' => [
260
                'allborders' => [
261
                    'style' => PHPExcel_Style_Border::BORDER_THIN,
262
                ],
263
            ],
264
        ]);
265
266
        /*
267
         * Save the file
268
         */
269
        $path         = $optionsExport['path'];
270
        $saveFilename = date('Y-m-d_H-i-s') . $this->getCacheId() . '.xlsx';
271
272
        $excelWriter = new \PHPExcel_Writer_Excel2007($phpExcel);
273
        $excelWriter->setPreCalculateFormulas(false);
274
        $excelWriter->save($path . '/' . $saveFilename);
275
276
        /*
277
         * Send the response stream
278
         */
279
        $response = new ResponseStream();
280
        $response->setStream(fopen($path . '/' . $saveFilename, 'r'));
281
282
        $headers = new Headers();
283
        $headers->addHeaders([
284
            'Content-Type' => [
285
                'application/force-download',
286
                'application/octet-stream',
287
                'application/download',
288
            ],
289
            'Content-Length'      => filesize($path . '/' . $saveFilename),
290
            'Content-Disposition' => 'attachment;filename=' . $this->getFilename() . '.xlsx',
291
            'Cache-Control'       => 'must-revalidate',
292
            'Pragma'              => 'no-cache',
293
            'Expires'             => 'Thu, 1 Jan 1970 00:00:00 GMT',
294
        ]);
295
296
        $response->setHeaders($headers);
297
298
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $response; (Zend\Http\Response\Stream) is incompatible with the return type declared by the interface ZfcDatagrid\Renderer\RendererInterface::execute of type Zend\View\Model\ViewModel.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
299
    }
300
301
    /**
302
     * Calculates the column width, based on the papersize and orientation.
303
     *
304
     * @param \PHPExcel_Worksheet $sheet
305
     * @param array               $columns
306
     */
307
    protected function calculateColumnWidth(\PHPExcel_Worksheet $sheet, array $columns)
308
    {
309
        // First make sure the columns width is 100 "percent"
310
        $this->calculateColumnWidthPercent($columns);
311
312
        // width is in mm
313
        $paperWidth = $this->getPaperWidth();
314
315
        // remove margins (they are in inches!)
316
        $paperWidth -= $sheet->getPageMargins()->getLeft() / 0.0393700787402;
317
        $paperWidth -= $sheet->getPageMargins()->getRight() / 0.0393700787402;
318
319
        $paperWidth /= 2;
320
321
        $factor = $paperWidth / 100;
322
        foreach ($columns as $column) {
323
            /* @var $column Column\AbstractColumn */
324
            $column->setWidth($column->getWidth() * $factor);
325
        }
326
    }
327
328
    /**
329
     * Set the printing options.
330
     *
331
     * @param PHPExcel $phpExcel
332
     */
333
    protected function setPrinting(PHPExcel $phpExcel)
334
    {
335
        $optionsRenderer = $this->getOptionsRenderer();
336
337
        $phpExcel->getProperties()
338
            ->setCreator('https://github.com/ThaDafinser/ZfcDatagrid')
339
            ->setTitle($this->getTitle());
340
341
        /*
342
         * Printing setup
343
         */
344
        $papersize   = $optionsRenderer['papersize'];
345
        $orientation = $optionsRenderer['orientation'];
346
        foreach ($phpExcel->getAllSheets() as $sheet) {
347
            /* @var $sheet \PHPExcel_Worksheet */
348
            if ('landscape' == $orientation) {
349
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
350
            } else {
351
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT);
352
            }
353
354
            switch ($papersize) {
355
356
                case 'A5':
357
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5);
358
                    break;
359
360
                case 'A4':
361
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4);
362
                    break;
363
364
                case 'A3':
365
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3);
366
                    break;
367
368
                case 'A2':
369
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A2);
370
                    break;
371
            }
372
373
            // Margins
374
            $sheet->getPageMargins()->setTop(0.8);
375
            $sheet->getPageMargins()->setBottom(0.5);
376
            $sheet->getPageMargins()->setLeft(0.5);
377
            $sheet->getPageMargins()->setRight(0.5);
378
379
            $this->setHeaderFooter($sheet);
380
        }
381
382
        $phpExcel->setActiveSheetIndex(0);
383
    }
384
385
    /**
386
     * @param \PHPExcel_Worksheet $sheet
387
     */
388
    protected function setHeaderFooter(\PHPExcel_Worksheet $sheet)
389
    {
390
        $textRight = $this->translate('Page') . ' &P / &N';
391
392
        $sheet->getHeaderFooter()->setOddHeader('&L&16&G ' . $this->translate($this->getTitle()));
393
        $sheet->getHeaderFooter()->setOddFooter('&R' . $textRight);
394
    }
395
}
396