Completed
Push — master ( 820a9e...594aaa )
by Andrii
12:39
created

Helper   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 36
eloc 75
c 6
b 1
f 0
dl 0
loc 159
ccs 0
cts 97
cp 0
rs 9.52

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getEncoder() 0 9 2
A encodeVar() 0 3 1
C mergeConfig() 0 32 15
A exportVar() 0 13 3
B dumpClosure() 0 29 7
A collectClosures() 0 18 6
A exportDefines() 0 9 2
1
<?php
2
/**
3
 * Composer plugin for config assembling
4
 *
5
 * @link      https://github.com/hiqdev/composer-config-plugin
6
 * @package   composer-config-plugin
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config;
12
13
use Closure;
14
use ReflectionFunction;
15
use Riimu\Kit\PHPEncoder\PHPEncoder;
16
17
/**
18
 * Helper class.
19
 *
20
 * @author Andrii Vasyliev <[email protected]>
21
 */
22
class Helper
23
{
24
    /**
25
     * Merges two or more arrays into one recursively.
26
     * Based on Yii2 yii\helpers\BaseArrayHelper::merge.
27
     * @return array the merged array
28
     */
29
    public static function mergeConfig(): array
30
    {
31
        $args = \func_get_args();
32
        $res = array_shift($args) ?: [];
33
        foreach ($args as $items) {
34
            if (!\is_array($items)) {
35
                continue;
36
            }
37
            foreach ($items as $k => $v) {
38
                if ($v instanceof \yii\helpers\UnsetArrayValue || $v instanceof \Yiisoft\Arrays\UnsetArrayValue) {
0 ignored issues
show
Bug introduced by
The type Yiisoft\Arrays\UnsetArrayValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type yii\helpers\UnsetArrayValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
                    unset($res[$k]);
40
                } elseif ($v instanceof \yii\helpers\ReplaceArrayValue || $v instanceof \Yiisoft\Arrays\ReplaceArrayValue) {
0 ignored issues
show
Bug introduced by
The type Yiisoft\Arrays\ReplaceArrayValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type yii\helpers\ReplaceArrayValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
41
                    $res[$k] = $v->value;
42
                } elseif (\is_int($k)) {
43
                    /// XXX skip repeated values
44
                    if (\in_array($v, $res, true))  {
45
                        continue;
46
                    }
47
                    if (isset($res[$k])) {
48
                        $res[] = $v;
49
                    } else {
50
                        $res[$k] = $v;
51
                    }
52
                } elseif (\is_array($v) && isset($res[$k]) && \is_array($res[$k])) {
53
                    $res[$k] = self::mergeConfig($res[$k], $v);
54
                } else {
55
                    $res[$k] = $v;
56
                }
57
            }
58
        }
59
60
        return $res;
61
    }
62
63
    public static function exportDefines(array $defines): string
64
    {
65
        $res = '';
66
        foreach ($defines as $key => $value) {
67
            $var = static::exportVar($value);
68
            $res .= "defined('$key') or define('$key', $var);\n";
69
        }
70
71
        return $res;
72
    }
73
74
    /**
75
     * Returns PHP-executable string representation of given value.
76
     * In contrast to var_dump outputs Closures as PHP code.
77
     * @param mixed $value
78
     * @return string
79
     * @throws \ReflectionException
80
     */
81
    public static function exportVar($value): string
82
    {
83
        $closures = self::collectClosures($value);
84
        $res = static::encodeVar($value);
85
        if (!empty($closures)) {
86
            $subs = [];
87
            foreach ($closures as $key => $closure) {
88
                $subs["'" . $key . "'"] = self::dumpClosure($closure);
89
            }
90
            $res = strtr($res, $subs);
91
        }
92
93
        return $res;
94
    }
95
96
    /**
97
     * Riimu/Kit-PHPEncoder based `var_export` alternative.
98
     * @param mixed $value
99
     * @return string
100
     */
101
    public static function encodeVar($value): string
102
    {
103
        return static::getEncoder()->encode($value);
104
    }
105
106
    private static $encoder;
107
108
    private static function getEncoder()
109
    {
110
        if (static::$encoder === null) {
0 ignored issues
show
Bug introduced by
Since $encoder is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $encoder to at least protected.
Loading history...
111
            static::$encoder = new PHPEncoder([
112
                'object.format' => 'serialize',
113
            ]);
114
        }
115
116
        return static::$encoder;
117
    }
118
119
    /**
120
     * Collects closures from given input.
121
     * Substitutes closures with a tag.
122
     * @param mixed $input will be changed
123
     * @return array array of found closures
124
     */
125
    private static function collectClosures(&$input): array
126
    {
127
        static $closureNo = 1;
128
        $closures = [];
129
        if (\is_array($input)) {
130
            foreach ($input as &$value) {
131
                if (\is_array($value) || $value instanceof Closure) {
132
                    $closures = array_merge($closures, self::collectClosures($value));
133
                }
134
            }
135
        } elseif ($input instanceof Closure) {
136
            ++$closureNo;
137
            $key = "--==<<[[((Closure#$closureNo))]]>>==--";
138
            $closures[$key] = $input;
139
            $input = $key;
140
        }
141
142
        return $closures;
143
    }
144
145
    /**
146
     * Dumps closure object to string.
147
     * Based on http://www.metashock.de/2013/05/dump-source-code-of-closure-in-php/.
148
     * @param Closure $closure
149
     * @return string
150
     * @throws \ReflectionException
151
     */
152
    public static function dumpClosure(Closure $closure): string
153
    {
154
        $res = 'function (';
155
        $fun = new ReflectionFunction($closure);
156
        $args = [];
157
        foreach ($fun->getParameters() as $arg) {
158
            $str = '';
159
            if ($arg->isArray()) {
160
                $str .= 'array ';
161
            } elseif ($arg->getClass()) {
162
                $str .= $arg->getClass()->name . ' ';
163
            }
164
            if ($arg->isPassedByReference()) {
165
                $str .= '&';
166
            }
167
            $str .= '$' . $arg->name;
168
            if ($arg->isOptional()) {
169
                $str .= ' = ' . \var_export($arg->getDefaultValue(), true);
170
            }
171
            $args[] = $str;
172
        }
173
        $res .= implode(', ', $args);
174
        $res .= ') {' . PHP_EOL;
175
        $lines = file($fun->getFileName());
176
        for ($i = $fun->getStartLine(); $i < $fun->getEndLine(); ++$i) {
177
            $res .= $lines[$i];
178
        }
179
180
        return rtrim($res, "\r\n ,");
181
    }
182
}
183