Completed
Push — master ( 9f03d3...842a8a )
by Martin
27:18 queued 12:16
created

Renderer::execute()   F

Complexity

Conditions 24
Paths 680

Size

Total Lines 257
Code Lines 146

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 257
rs 2.248
cc 24
eloc 146
nc 680
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Output as an excel file.
4
 */
5
namespace ZfcDatagrid\Renderer\PHPExcel;
6
7
use PHPExcel;
8
use PHPExcel_Cell;
9
use PHPExcel_Cell_DataType;
10
use PHPExcel_Style_Alignment;
11
use PHPExcel_Style_Border;
12
use PHPExcel_Style_Color;
13
use PHPExcel_Style_Fill;
14
use PHPExcel_Worksheet_PageSetup;
15
use Zend\Http\Headers;
16
use Zend\Http\Response\Stream as ResponseStream;
17
use ZfcDatagrid\Column;
18
use ZfcDatagrid\Renderer\AbstractExport;
19
20
class Renderer extends AbstractExport
21
{
22
    public function getName()
23
    {
24
        return 'PHPExcel';
25
    }
26
27
    public function isExport()
28
    {
29
        return true;
30
    }
31
32
    public function isHtml()
33
    {
34
        return false;
35
    }
36
37
    public function execute()
38
    {
39
        $options = $this->getOptions();
40
        $optionsExport = $options['settings']['export'];
41
42
        $optionsRenderer = $this->getOptionsRenderer();
43
44
        $phpExcel = new PHPExcel();
45
46
        // Sheet 1
47
        $phpExcel->setActiveSheetIndex(0);
48
        $sheet = $phpExcel->getActiveSheet();
49
        $sheet->setTitle($this->translate($optionsRenderer['sheetName']));
50
51
        if (true === $optionsRenderer['displayTitle']) {
52
            $sheet->setCellValue('A'.$optionsRenderer['rowTitle'], $this->getTitle());
53
            $sheet->getStyle('A'.$optionsRenderer['rowTitle'])
54
                ->getFont()
55
                ->setSize(15);
56
        }
57
58
        /*
59
         * Print settings
60
         */
61
        $this->setPrinting($phpExcel);
62
63
        /*
64
         * Calculate column width
65
         */
66
        $this->calculateColumnWidth($sheet, $this->getColumnsToExport());
67
68
        /*
69
         * Header
70
         */
71
        $xColumn = 0;
72
        $yRow = $optionsRenderer['startRowData'];
73
        foreach ($this->getColumnsToExport() as $col) {
74
            /* @var $column Column\AbstractColumn */
75
            $sheet->setCellValueByColumnAndRow($xColumn, $yRow, $this->translate($col->getLabel()));
76
77
            $sheet->getColumnDimension(PHPExcel_Cell::stringFromColumnIndex($xColumn))->setWidth($col->getWidth());
78
79
            ++$xColumn;
80
        }
81
82
        /*
83
         * Data
84
         */
85
        $yRow = $optionsRenderer['startRowData'] + 1;
86
        foreach ($this->getData() as $row) {
87
            $xColumn = 0;
88
            foreach ($this->getColumnsToExport() as $col) {
89
                /* @var $col Column\AbstractColumn */
90
91
                $value = $row[$col->getUniqueId()];
92
                if (is_array($value)) {
93
                    $value = implode(PHP_EOL, $value);
94
                }
95
96
                /* @var $column Column\AbstractColumn */
97
                $currentColumn = PHPExcel_Cell::stringFromColumnIndex($xColumn);
98
                $cell = $sheet->getCell($currentColumn.$yRow);
99
100
                switch (get_class($col->getType())) {
101
102
                    case Column\Type\Number::class:
103
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_NUMERIC);
104
                        break;
105
106
                    case Column\Type\DateTime::class:
107
                        /* @var $dateType Column\Type\DateTime */
108
                        $dateType = $col->getType();
109
110
                        if (! $value instanceof \DateTime && is_scalar($value)) {
111
                            $value = \DateTime::createFromFormat($dateType->getSourceDateTimeFormat(), $value);
112
                            $value->setTimezone(new \DateTimeZone($dateType->getSourceTimezone()));
113
                        }
114
115
                        $value->setTimezone(new \DateTimeZone($dateType->getOutputTimezone()));
116
                        $cell->setValue(\PHPExcel_Shared_Date::PHPToExcel($value));
117
118
                        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...
119
                            $outputPattern = $dateType->getOutputPattern();
120
                        } else {
121
                            $outputPattern = \PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME;
122
                        }
123
124
                        $cell->getStyle()
125
                            ->getNumberFormat()
126
                            ->setFormatCode($outputPattern);
127
                        break;
128
129
                    default:
130
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
131
                        break;
132
                }
133
134
                $columnStyle = $sheet->getStyle($currentColumn.$yRow);
135
                $columnStyle->getAlignment()->setWrapText(true);
136
137
                /*
138
                 * Styles
139
                 */
140
                $styles = array_merge($this->getRowStyles(), $col->getStyles());
141
                foreach ($styles as $style) {
142
                    /* @var $style Column\Style\AbstractStyle */
143
                    if ($style->isApply($row) === true) {
144
                        switch (get_class($style)) {
145
146
                            case Column\Style\Bold::class:
147
                                $columnStyle->getFont()->setBold(true);
148
                                break;
149
150
                            case Column\Style\Italic::class:
151
                                $columnStyle->getFont()->setItalic(true);
152
                                break;
153
154
                            case Column\Style\Color::class:
155
                                $columnStyle->getFont()
156
                                    ->getColor()
157
                                    ->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...
158
                                break;
159
160
                            case Column\Style\BackgroundColor::class:
161
                                $columnStyle->getFill()->applyFromArray([
162
                                    'type' => \PHPExcel_Style_Fill::FILL_SOLID,
163
                                    'color' => [
164
                                        '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...
165
                                    ],
166
                                ]);
167
                                break;
168
169
                            case Column\Style\Align::class:
170
                                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...
171
                                    case Column\Style\Align::$RIGHT:
172
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
173
                                        break;
174
                                    case Column\Style\Align::$LEFT:
175
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
176
                                        break;
177
                                    case Column\Style\Align::$CENTER:
178
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
179
                                        break;
180
                                    case Column\Style\Align::$JUSTIFY:
181
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
182
                                        break;
183
                                    default:
184
                                        //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...
185
                                        break;
186
                                }
187
188
                                break;
189
190
                            case Column\Style\Strikethrough::class:
191
                                $columnStyle->getFont()->setStrikethrough(true);
192
                                break;
193
194
                            case Column\Style\Html::class:
195
                                // @todo strip the html?
196
                                break;
197
198
                            default:
199
                                throw new \Exception('Not defined yet: "'.get_class($style).'"');
200
                                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...
201
                        }
202
                    }
203
                }
204
205
                ++$xColumn;
206
            }
207
208
            ++$yRow;
209
        }
210
211
        /*
212
         * Autofilter, freezing, ...
213
         */
214
        $highest = $sheet->getHighestRowAndColumn();
215
216
        // Letzte Zeile merken
217
218
        // Autofilter + Freeze
219
        $sheet->setAutoFilter('A'.$optionsRenderer['startRowData'].':'.$highest['column'].$highest['row']);
220
        $freezeRow = $optionsRenderer['startRowData'] + 1;
221
        $sheet->freezePane('A'.$freezeRow);
222
223
        // repeat the data header for each page!
224
        $sheet->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd($optionsRenderer['startRowData'], $optionsRenderer['startRowData']);
225
226
        // highlight header line
227
        $style = [
228
            'font' => [
229
                'bold' => true,
230
            ],
231
232
            'borders' => [
233
                'allborders' => [
234
                    'style' => PHPExcel_Style_Border::BORDER_MEDIUM,
235
                    'color' => [
236
                        'argb' => PHPExcel_Style_Color::COLOR_BLACK,
237
                    ],
238
                ],
239
            ],
240
            'fill' => [
241
                'type' => PHPExcel_Style_Fill::FILL_SOLID,
242
                'startcolor' => [
243
                    'argb' => PHPExcel_Style_Color::COLOR_YELLOW,
244
                ],
245
            ],
246
        ];
247
        $range = 'A'.$optionsRenderer['startRowData'].':'.$highest['column'].$optionsRenderer['startRowData'];
248
        $sheet->getStyle($range)->applyFromArray($style);
249
250
        // print borders
251
        $range = 'A'.$freezeRow.':'.$highest['column'].$highest['row'];
252
        $sheet->getStyle($range)->applyFromArray([
253
            'borders' => [
254
                'allborders' => [
255
                    'style' => PHPExcel_Style_Border::BORDER_THIN,
256
                ],
257
            ],
258
        ]);
259
260
        /*
261
         * Save the file
262
         */
263
        $path = $optionsExport['path'];
264
        $saveFilename = date('Y-m-d_H-i-s').$this->getCacheId().'.xlsx';
265
266
        $excelWriter = new \PHPExcel_Writer_Excel2007($phpExcel);
267
        $excelWriter->setPreCalculateFormulas(false);
268
        $excelWriter->save($path.'/'.$saveFilename);
269
270
        /*
271
         * Send the response stream
272
         */
273
        $response = new ResponseStream();
274
        $response->setStream(fopen($path.'/'.$saveFilename, 'r'));
275
276
        $headers = new Headers();
277
        $headers->addHeaders([
278
            'Content-Type' => [
279
                'application/force-download',
280
                'application/octet-stream',
281
                'application/download',
282
            ],
283
            'Content-Length' => filesize($path.'/'.$saveFilename),
284
            'Content-Disposition' => 'attachment;filename='.$this->getFilename().'.xlsx',
285
            'Cache-Control' => 'must-revalidate',
286
            'Pragma' => 'no-cache',
287
            'Expires' => 'Thu, 1 Jan 1970 00:00:00 GMT',
288
        ]);
289
290
        $response->setHeaders($headers);
291
292
        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...
293
    }
294
295
    /**
296
     * Calculates the column width, based on the papersize and orientation.
297
     *
298
     * @param \PHPExcel_Worksheet $sheet
299
     * @param array               $columns
300
     */
301
    protected function calculateColumnWidth(\PHPExcel_Worksheet $sheet, array $columns)
302
    {
303
        // First make sure the columns width is 100 "percent"
304
        $this->calculateColumnWidthPercent($columns);
305
306
        // width is in mm
307
        $paperWidth = $this->getPaperWidth();
308
309
        // remove margins (they are in inches!)
310
        $paperWidth -= $sheet->getPageMargins()->getLeft() / 0.0393700787402;
311
        $paperWidth -= $sheet->getPageMargins()->getRight() / 0.0393700787402;
312
313
        $paperWidth /= 2;
314
315
        $factor = $paperWidth / 100;
316
        foreach ($columns as $column) {
317
            /* @var $column Column\AbstractColumn */
318
            $column->setWidth($column->getWidth() * $factor);
319
        }
320
    }
321
322
    /**
323
     * Set the printing options.
324
     *
325
     * @param PHPExcel $phpExcel
326
     */
327
    protected function setPrinting(PHPExcel $phpExcel)
328
    {
329
        $optionsRenderer = $this->getOptionsRenderer();
330
331
        $phpExcel->getProperties()
332
            ->setCreator('https://github.com/ThaDafinser/ZfcDatagrid')
333
            ->setTitle($this->getTitle());
334
335
        /*
336
         * Printing setup
337
         */
338
        $papersize = $optionsRenderer['papersize'];
339
        $orientation = $optionsRenderer['orientation'];
340
        foreach ($phpExcel->getAllSheets() as $sheet) {
341
            /* @var $sheet \PHPExcel_Worksheet */
342
            if ('landscape' == $orientation) {
343
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
344
            } else {
345
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT);
346
            }
347
348
            switch ($papersize) {
349
350
                case 'A5':
351
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5);
352
                    break;
353
354
                case 'A4':
355
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4);
356
                    break;
357
358
                case 'A3':
359
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3);
360
                    break;
361
362
                case 'A2':
363
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A2);
364
                    break;
365
            }
366
367
            // Margins
368
            $sheet->getPageMargins()->setTop(0.8);
369
            $sheet->getPageMargins()->setBottom(0.5);
370
            $sheet->getPageMargins()->setLeft(0.5);
371
            $sheet->getPageMargins()->setRight(0.5);
372
373
            $this->setHeaderFooter($sheet);
374
        }
375
376
        $phpExcel->setActiveSheetIndex(0);
377
    }
378
379
    /**
380
     * @param \PHPExcel_Worksheet $sheet
381
     */
382
    protected function setHeaderFooter(\PHPExcel_Worksheet $sheet)
383
    {
384
        $textRight = $this->translate('Page').' &P / &N';
385
386
        $sheet->getHeaderFooter()->setOddHeader('&L&16&G '.$this->translate($this->getTitle()));
387
        $sheet->getHeaderFooter()->setOddFooter('&R'.$textRight);
388
    }
389
}
390