Completed
Push — master ( 4e2bc6...a3357c )
by Martin
25:55 queued 08:29
created

Renderer   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 17
Bugs 1 Features 2
Metric Value
wmc 35
c 17
b 1
f 2
lcom 1
cbo 6
dl 0
loc 358
rs 9

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 245 22
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\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 \ZfcDatagrid\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 \ZfcDatagrid\Column\AbstractColumn */
90
91
                $value = $row[$col->getUniqueId()];
92
                if (is_array($value)) {
93
                    $value = implode(PHP_EOL, $value);
94
                }
95
96
                /* @var $column \ZfcDatagrid\Column\AbstractColumn */
97
                $currentColumn = PHPExcel_Cell::stringFromColumnIndex($xColumn);
98
                $cell          = $sheet->getCell($currentColumn . $yRow);
99
100
                switch (get_class($col->getType())) {
101
102
                    case 'ZfcDatagrid\Column\Type\Number':
103
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_NUMERIC);
104
                        break;
105
106
                    case 'ZfcDatagrid\Column\Type\DateTime':
107
                        if ($value instanceof \DateTime) {
108
                            $value->setTimezone(new \DateTimeZone($col->getType()
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ZfcDatagrid\Column\Type\AbstractType as the method getOutputTimezone() does only exist in the following sub-classes of ZfcDatagrid\Column\Type\AbstractType: ZfcDatagrid\Column\Type\DateTime. 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...
109
                                ->getOutputTimezone()));
110
                        }
111
                        $cell->setValue(\PHPExcel_Shared_Date::PHPToExcel($value));
112
                        $cell->getStyle()
113
                        ->getNumberFormat()
114
                        ->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME);
115
                        break;
116
117
                    default:
118
                        $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
119
                        break;
120
                }
121
122
                $columnStyle = $sheet->getStyle($currentColumn . $yRow);
123
                $columnStyle->getAlignment()->setWrapText(true);
124
125
                /*
126
                 * Styles
127
                 */
128
                $styles = array_merge($this->getRowStyles(), $col->getStyles());
129
                foreach ($styles as $style) {
130
                    /* @var $style \ZfcDatagrid\Column\Style\AbstractStyle */
131
                    if ($style->isApply($row) === true) {
132
                        switch (get_class($style)) {
133
134
                            case 'ZfcDatagrid\Column\Style\Bold':
135
                                $columnStyle->getFont()->setBold(true);
136
                                break;
137
138
                            case 'ZfcDatagrid\Column\Style\Italic':
139
                                $columnStyle->getFont()->setItalic(true);
140
                                break;
141
142
                            case 'ZfcDatagrid\Column\Style\Color':
143
                                $columnStyle->getFont()
144
                                    ->getColor()
145
                                    ->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...
146
                                break;
147
148
                            case 'ZfcDatagrid\Column\Style\BackgroundColor':
149
                                $columnStyle->getFill()->applyFromArray([
150
                                    'type'  => \PHPExcel_Style_Fill::FILL_SOLID,
151
                                    'color' => [
152
                                        '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...
153
                                    ],
154
                                ]);
155
                                break;
156
157
                            case 'ZfcDatagrid\Column\Style\Align':
158
                                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...
159
                                    case \ZfcDatagrid\Column\Style\Align::$RIGHT:
160
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
161
                                        break;
162
                                    case \ZfcDatagrid\Column\Style\Align::$LEFT:
163
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
164
                                        break;
165
                                    case \ZfcDatagrid\Column\Style\Align::$CENTER:
166
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
167
                                        break;
168
                                    case \ZfcDatagrid\Column\Style\Align::$JUSTIFY:
169
                                        $columnStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
170
                                        break;
171
                                    default:
172
                                        //throw new \Exception('Not defined yet: "'.get_class($style->getAlignment()).'"');
173
                                        break;
174
                                }
175
176
                                break;
177
178
                            case 'ZfcDatagrid\Column\Style\Strikethrough':
179
                                $columnStyle->getFont()->setStrikethrough(true);
180
                                break;
181
182
                            case 'ZfcDatagrid\Column\Style\Html':
183
                                // @todo strip the html?
184
                                break;
185
186
                            default:
187
                                throw new \Exception('Not defined yet: "' . get_class($style) . '"');
188
                                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...
189
                        }
190
                    }
191
                }
192
193
                $xColumn ++;
194
            }
195
196
            $yRow ++;
197
        }
198
199
        /*
200
         * Autofilter, freezing, ...
201
         */
202
        $highest = $sheet->getHighestRowAndColumn();
203
204
        // Letzte Zeile merken
205
206
        // Autofilter + Freeze
207
        $sheet->setAutoFilter('A' . $optionsRenderer['startRowData'] . ':' . $highest['column'] . $highest['row']);
208
        $freezeRow = $optionsRenderer['startRowData'] + 1;
209
        $sheet->freezePane('A' . $freezeRow);
210
211
        // repeat the data header for each page!
212
        $sheet->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd($optionsRenderer['startRowData'], $optionsRenderer['startRowData']);
213
214
        // highlight header line
215
        $style = [
216
            'font' => [
217
                'bold' => true,
218
            ],
219
220
            'borders' => [
221
                'allborders' => [
222
                    'style' => PHPExcel_Style_Border::BORDER_MEDIUM,
223
                    'color' => [
224
                        'argb' => PHPExcel_Style_Color::COLOR_BLACK,
225
                    ],
226
                ],
227
            ],
228
            'fill' => [
229
                'type'       => PHPExcel_Style_Fill::FILL_SOLID,
230
                'startcolor' => [
231
                    'argb' => PHPExcel_Style_Color::COLOR_YELLOW,
232
                ],
233
            ],
234
        ];
235
        $range = 'A' . $optionsRenderer['startRowData'] . ':' . $highest['column'] . $optionsRenderer['startRowData'];
236
        $sheet->getStyle($range)->applyFromArray($style);
237
238
        // print borders
239
        $range = 'A' . $freezeRow . ':' . $highest['column'] . $highest['row'];
240
        $sheet->getStyle($range)->applyFromArray([
241
            'borders' => [
242
                'allborders' => [
243
                    'style' => PHPExcel_Style_Border::BORDER_THIN,
244
                ],
245
            ],
246
        ]);
247
248
        /*
249
         * Save the file
250
         */
251
        $path         = $optionsExport['path'];
252
        $saveFilename = date('Y-m-d_H-i-s') . $this->getCacheId() . '.xlsx';
253
254
        $excelWriter = new \PHPExcel_Writer_Excel2007($phpExcel);
255
        $excelWriter->setPreCalculateFormulas(false);
256
        $excelWriter->save($path . '/' . $saveFilename);
257
258
        /*
259
         * Send the response stream
260
         */
261
        $response = new ResponseStream();
262
        $response->setStream(fopen($path . '/' . $saveFilename, 'r'));
263
264
        $headers = new Headers();
265
        $headers->addHeaders([
266
            'Content-Type' => [
267
                'application/force-download',
268
                'application/octet-stream',
269
                'application/download',
270
            ],
271
            'Content-Length'      => filesize($path . '/' . $saveFilename),
272
            'Content-Disposition' => 'attachment;filename=' . $this->getFilename() . '.xlsx',
273
            'Cache-Control'       => 'must-revalidate',
274
            'Pragma'              => 'no-cache',
275
            'Expires'             => 'Thu, 1 Jan 1970 00:00:00 GMT',
276
        ]);
277
278
        $response->setHeaders($headers);
279
280
        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...
281
    }
282
283
    /**
284
     * Calculates the column width, based on the papersize and orientation
285
     *
286
     * @param \PHPExcel_Worksheet $sheet
287
     * @param array               $columns
288
     */
289
    protected function calculateColumnWidth(\PHPExcel_Worksheet $sheet, array $columns)
290
    {
291
        // First make sure the columns width is 100 "percent"
292
        $this->calculateColumnWidthPercent($columns);
293
294
        // width is in mm
295
        $paperWidth = $this->getPaperWidth();
296
297
        // remove margins (they are in inches!)
298
        $paperWidth -= $sheet->getPageMargins()->getLeft() / 0.0393700787402;
299
        $paperWidth -= $sheet->getPageMargins()->getRight() / 0.0393700787402;
300
301
        $paperWidth /= 2;
302
303
        $factor = $paperWidth / 100;
304
        foreach ($columns as $column) {
305
            /* @var $column \ZfcDatagrid\Column\AbstractColumn */
306
            $column->setWidth($column->getWidth() * $factor);
307
        }
308
    }
309
310
    /**
311
     * Set the printing options
312
     *
313
     * @param PHPExcel $phpExcel
314
     */
315
    protected function setPrinting(PHPExcel $phpExcel)
316
    {
317
        $optionsRenderer = $this->getOptionsRenderer();
318
319
        $phpExcel->getProperties()
320
            ->setCreator('https://github.com/ThaDafinser/ZfcDatagrid')
321
            ->setTitle($this->getTitle());
322
323
        /*
324
         * Printing setup
325
         */
326
        $papersize   = $optionsRenderer['papersize'];
327
        $orientation = $optionsRenderer['orientation'];
328
        foreach ($phpExcel->getAllSheets() as $sheet) {
329
            /* @var $sheet \PHPExcel_Worksheet */
330
            if ('landscape' == $orientation) {
331
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
332
            } else {
333
                $sheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT);
334
            }
335
336
            switch ($papersize) {
337
338
                case 'A5':
339
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A5);
340
                    break;
341
342
                case 'A4':
343
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A4);
344
                    break;
345
346
                case 'A3':
347
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A3);
348
                    break;
349
350
                case 'A2':
351
                    $sheet->getPageSetup()->setPaperSize(PHPExcel_Worksheet_PageSetup::PAPERSIZE_A2);
352
                    break;
353
            }
354
355
            // Margins
356
            $sheet->getPageMargins()->setTop(0.8);
357
            $sheet->getPageMargins()->setBottom(0.5);
358
            $sheet->getPageMargins()->setLeft(0.5);
359
            $sheet->getPageMargins()->setRight(0.5);
360
361
            $this->setHeaderFooter($sheet);
362
        }
363
364
        $phpExcel->setActiveSheetIndex(0);
365
    }
366
367
    /**
368
     * @param \PHPExcel_Worksheet $sheet
369
     */
370
    protected function setHeaderFooter(\PHPExcel_Worksheet $sheet)
371
    {
372
        $textRight = $this->translate('Page') . ' &P / &N';
373
374
        $sheet->getHeaderFooter()->setOddHeader('&L&16&G ' . $this->translate($this->getTitle()));
375
        $sheet->getHeaderFooter()->setOddFooter('&R' . $textRight);
376
    }
377
}
378