Completed
Push — master ( 53b99e...05e097 )
by Basil
03:48
created

ExportHelper::generateContentArray()   C

Complexity

Conditions 16
Paths 21

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 5.5666
c 0
b 0
f 0
cc 16
nc 21
nop 3

How to fix   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
namespace luya\helpers;
4
5
use yii\db\ActiveQueryInterface;
6
use yii\db\ActiveRecordInterface;
7
use yii\helpers\Html;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, luya\helpers\Html.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use luya\Exception;
9
10
/**
11
 * Exporting into Formats.
12
 *
13
 * @author Basil Suter <[email protected]>
14
 * @since 1.0.0
15
 */
16
class ExportHelper
17
{
18
    /**
19
     * Export an Array or ActiveQuery instance into a CSV formated string.
20
     *
21
     * @param array|ActiveQueryInterface $input The data to export into a csv
22
     * @param array $keys Defines which keys should be packed into the generated CSV. The defined keys does not change the sort behavior of the generated csv.
23
     * @param string $header Whether the column name should be set as header inside the csv or not.
24
     * @return string The generated CSV as string.
25
     */
26
    public static function csv($input, array $keys = [], $header = true)
27
    {
28
        $delimiter = ",";
29
        $input = self::transformInput($input);
30
        $array = self::generateContentArray($input, $keys, $header);
0 ignored issues
show
Bug introduced by
It seems like $input defined by self::transformInput($input) on line 29 can also be of type object; however, luya\helpers\ExportHelper::generateContentArray() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$keys is of type array, but the function expects a string.

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...
Bug introduced by
It seems like $header defined by parameter $header on line 26 can also be of type string; however, luya\helpers\ExportHelper::generateContentArray() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
31
32
        return self::generateOutputString($array, $delimiter);
33
    }
34
35
    /**
36
     * Export an Array or ActiveQuery instance into a Excel formatted string.
37
     *
38
     * @param array|ActiveQueryInterface $input
39
     * @param array $keys Defines which keys should be packed into the generated xlsx. The defined keys does not change the sort behavior of the generated xls.
40
     * @param bool $header
41
     * @return mixed
42
     * @throws Exception
43
     * @since 1.0.4
44
     */
45
    public static function xlsx($input, array $keys = [], $header = true)
46
    {
47
        $input = self::transformInput($input);
48
49
        $array = self::generateContentArray($input, $keys, $header);
0 ignored issues
show
Bug introduced by
It seems like $input defined by self::transformInput($input) on line 47 can also be of type object; however, luya\helpers\ExportHelper::generateContentArray() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$keys is of type array, but the function expects a string.

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...
50
51
        $writer = new XLSXWriter();
52
        $writer->writeSheet($array);
53
54
        return $writer->writeToString();
55
    }
56
57
    /**
58
     * Check type of input and return correct array.
59
     *
60
     * @param array|object $input
61
     * @return array
62
     * @since 1.0.4
63
     */
64
    protected static function transformInput($input)
65
    {
66
        if ($input instanceof ActiveQueryInterface) {
67
            return $input->all();
68
        }
69
70
        return $input;
71
    }
72
73
    /**
74
     * Generate content by rows.
75
     *
76
     * @param array $contentRows
77
     * @param string$delimiter
78
     * @param string $keys
79
     * @param bool $generateHeader
80
     * @return array
81
     * @throws Exception
82
     * @since 1.0.4
83
     */
84
    protected static function generateContentArray($contentRows, $keys, $generateHeader = true)
85
    {
86
        if (is_scalar($contentRows)) {
87
            throw new Exception("Content must be either an array, object or traversable");
88
        }
89
90
        $attributeKeys = $keys;
91
        $header = [];
92
        $rows = [];
93
        $i = 0;
94
        foreach ($contentRows as $content) {
95
            // handle rows content
96
            if (!empty($keys) && is_array($content)) {
97
                foreach ($content as $k => $v) {
98
                    if (!in_array($k, $keys)) {
99
                        unset($content[$k]);
100
                    }
101
                }
102
            } elseif (!empty($keys) && is_object($content)) {
103
                $attributeKeys[get_class($content)] = $keys;
104
            }
105
            $rows[$i] = ArrayHelper::toArray($content, $attributeKeys, false);
0 ignored issues
show
Documentation introduced by
$attributeKeys is of type string, but the function expects a array.

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...
106
107
            // handler header
108
            if ($i == 0 && $generateHeader) {
109
                if ($content instanceof ActiveRecordInterface) {
110
                    foreach ($content as $k => $v) {
0 ignored issues
show
Bug introduced by
The expression $content of type object<yii\db\ActiveRecordInterface> is not traversable.
Loading history...
111
                        if (empty($keys)) {
112
                            $header[] = $content->getAttributeLabel($k);
0 ignored issues
show
Bug introduced by
The method getAttributeLabel() does not exist on yii\db\ActiveRecordInterface. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
113
                        } elseif (in_array($k, $keys)) {
114
                            $header[] = $content->getAttributeLabel($k);
0 ignored issues
show
Bug introduced by
The method getAttributeLabel() does not exist on yii\db\ActiveRecordInterface. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
115
                        }
116
                    }
117
                } else {
118
                    $header = array_keys($rows[0]);
119
                }
120
            }
121
122
            $i++;
123
        }
124
125
        if ($generateHeader) {
126
            return ArrayHelper::merge([$header], $rows);
127
        }
128
129
        return $rows;
130
    }
131
132
    /**
133
     * @param array $input
134
     * @param $delimiter
135
     * @return null|string
136
     * @since 1.0.4
137
     */
138
    protected static function generateOutputString(array $input, $delimiter)
139
    {
140
        $output = null;
141
        foreach ($input as $row) {
142
            $output.= self::generateRow($row, $delimiter, '"');
143
        }
144
145
        return $output;
146
    }
147
148
    /**
149
     * Generate a row by its items.
150
     *
151
     * @param array $row
152
     * @param string $delimiter
153
     * @param string $enclose
154
     * @return string
155
     * @since 1.0.4
156
     */
157
    protected static function generateRow(array $row, $delimiter, $enclose)
158
    {
159
        array_walk($row, function (&$item) use ($enclose) {
160
            if (is_bool($item)) {
161
                $item = (int) $item;
162
            } elseif (is_null($item)) {
163
                $item = '';
164
            } elseif (!is_scalar($item)) {
165
                $item = "[array]";
166
            }
167
            $item = $enclose.Html::encode($item).$enclose;
168
        });
169
170
        return implode($delimiter, $row) . PHP_EOL;
171
    }
172
}
173