Binary::doCreateCall()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php
2
/*
3
 * Copyright (C) 2017 by TEQneers GmbH & Co. KG
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy
6
 * of this software and associated documentation files (the "Software"), to deal
7
 * in the Software without restriction, including without limitation the rights
8
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
 * copies of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in
13
 * all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
 * THE SOFTWARE.
22
 */
23
24
/**
25
 * Git Stream Wrapper for PHP
26
 *
27
 * @category   TQ
28
 * @package    TQ_VCS
29
 * @subpackage VCS
30
 * @copyright  Copyright (C) 2018 by TEQneers GmbH & Co. KG
31
 */
32
33
namespace TQ\Vcs\Cli;
34
35
/**
36
 * Encapsulates access to the a VCS command line binary
37
 *
38
 * @author     Stefan Gehrig <gehrigteqneers.de>
39
 * @category   TQ
40
 * @package    TQ_VCS
41
 * @subpackage VCS
42
 * @copyright  Copyright (C) 2018 by TEQneers GmbH & Co. KG
43
 */
44
abstract class Binary
45
{
46
    /**
47
     * The file system path to a VCS binary
48
     *
49
     * @var string
50
     */
51
    protected $path;
52
53
    /**
54
     * Ensures that the given arguments is a valid VCS binary
55
     *
56
     * @param   Binary|string|null          $binary     The VCS binary
57
     * @return  static
58
     * @throws  \InvalidArgumentException               If $binary is not a valid VCS binary
59
     */
60 197
    public static function ensure($binary)
61
    {
62 197
        if ($binary === null || is_string($binary)) {
63
            $binary  = new static($binary);
64
        }
65 197
        if (!($binary instanceof static)) {
66
            throw new \InvalidArgumentException(
67
                sprintf(
68
                    'The $binary argument must either be a TQ\Vcs\Binary
69
                    instance or a path to the VCS binary (%s given)',
70
                    (is_object($binary)) ? get_class($binary) : gettype($binary)
71
                )
72
            );
73
        }
74 197
        return $binary;
75
    }
76
77
78
    /**
79
     * Checks if the current system is Windows
80
     *
81
     * @return  boolean     True if we're on a Windows machine
82
     */
83 175
    protected static function isWindows()
84
    {
85 175
        return (strpos(PHP_OS, 'WIN') !== false);
86
    }
87
88
    /**
89
     * Creates a VCS binary interface
90
     *
91
     * @param   string   $path              The path to the VCS binary
92
     * @throws  \InvalidArgumentException   If no VCS binary is found
93
     */
94 191
    public function __construct($path)
95
    {
96 191
        if (!is_string($path) || empty($path)) {
97
            throw new \InvalidArgumentException('No path to the VCS binary found');
98
        }
99 191
        $this->path    = $path;
100 191
    }
101
102
    /**
103
     * Create a call to the VCS binary for later execution
104
     *
105
     * @param   string  $path           The full path to the VCS repository
106
     * @param   string  $command        The VCS command, e.g. show, commit or add
107
     * @param   array   $arguments      The command arguments
108
     * @return  Call
109
     */
110 175
    public function createCall($path, $command, array $arguments)
111
    {
112 175
        if (!self::isWindows()) {
113 175
            $binary = escapeshellcmd($this->path);
114
        } else {
115
            $binary = $this->path;
116
        }
117 175
        if (!empty($command)) {
118 173
            $command    = escapeshellarg($command);
119
        }
120
121 175
        list($args, $files) = $this->sanitizeCommandArguments($arguments);
122 175
        $cmd                = $this->createCallCommand($binary, $command, $args, $files);
123 175
        $call               = $this->doCreateCall($cmd, $path);
124 175
        return $call;
125
    }
126
127
    /**
128
     * The call factory
129
     *
130
     * @param   string  $cmd        The command string to be executed
131
     * @param   string  $path       The working directory
132
     * @return  Call
133
     */
134 175
    protected function doCreateCall($cmd, $path)
135
    {
136 175
        return Call::create($cmd, $path);
137
    }
138
139
    /**
140
     * Creates the command string to be executed
141
     *
142
     * @param   string      $binary     The path to the binary
143
     * @param   string      $command    The VCS command
144
     * @param   array       $args       The list of command line arguments (sanitized)
145
     * @param   array       $files      The list of files to be added to the command line call
146
     * @return  string                  The command string to be executed
147
     */
148 175
    protected function createCallCommand($binary, $command, array $args, array $files)
149
    {
150 175
        $cmd    = trim(sprintf('%s %s %s', $binary, $command, implode(' ', $args)));
151 175
        if (count($files) > 0) {
152 113
            $cmd    .= ' -- '.implode(' ', $files);
153
        }
154 175
        return $cmd;
155
    }
156
157
    /**
158
     * Sanitizes a command line argument
159
     *
160
     * @param   string      $key        The argument key
161
     * @param   string      $value      The argument value (can be empty)
162
     * @return  string
163
     */
164 160
    protected function sanitizeCommandArgument($key, $value)
165
    {
166 160
        $key  = ltrim($key, '-');
167 160
        if (strlen($key) == 1 || is_numeric($key)) {
168 66
            $arg = sprintf('-%s', escapeshellarg($key));
169 66
            if ($value !== null) {
170 66
                $arg    .= ' '.escapeshellarg($value);
171
            }
172
        } else {
173 156
            $arg = sprintf('--%s', escapeshellarg($key));
174 156
            if ($value !== null) {
175 121
                $arg    .= '='.escapeshellarg($value);
176
            }
177
        }
178 160
        return $arg;
179
    }
180
181
    /**
182
     * Sanitizes a list of command line arguments and splits them into args and files
183
     *
184
     * @param   array       $arguments      The list of arguments
185
     * @return  array                       An array with (args, files)
186
     */
187 175
    protected function sanitizeCommandArguments(array $arguments)
188
    {
189 175
        $args       = array();
190 175
        $files      = array();
191 175
        $fileMode   = false;
192 175
        foreach ($arguments as $k => $v) {
193 171
            if ($v === '--' || $k === '--') {
194 115
                $fileMode   = true;
195 115
                continue;
196
            }
197 169
            if (is_int($k)) {
198 165
                if (strpos($v, '-') === 0) {
199 155
                    $args[]  = $this->sanitizeCommandArgument($v, null);
200 140
                } else if ($fileMode) {
201 113
                    $files[] = escapeshellarg($v);
202
                } else {
203 165
                    $args[]  = escapeshellarg($v);
204
                }
205
            } else {
206 123
                if (strpos($k, '-') === 0) {
207 169
                    $args[] = $this->sanitizeCommandArgument($k, $v);
208
                }
209
            }
210
        }
211 175
        return array($args, $files);
212
    }
213
214
    /**
215
     * Extracts the CLI call parameters from the arguments to a magic method call
216
     *
217
     * @param   string  $method             The VCS command, e.g. show, commit or add
218
     * @param   array   $arguments          The command arguments with the path to the VCS
219
     *                                      repository being the first argument
220
     * @return  array                       An array with (path, method, args, stdIn)
221
     * @throws \InvalidArgumentException    If the method is called with less than one argument
222
     */
223 153
    protected function extractCallParametersFromMagicCall($method, array $arguments)
224
    {
225 153
        if (count($arguments) < 1) {
226
            throw new \InvalidArgumentException(sprintf(
227
                '"%s" must be called with at least one argument denoting the path', $method
228
            ));
229
        }
230
231 153
        $path   = array_shift($arguments);
232 153
        $args   = array();
233 153
        $stdIn  = null;
234
235 153
        if (count($arguments) > 0) {
236 153
            $args   = array_shift($arguments);
237 153
            if (!is_array($args)) {
238 1
                $args   = array($args);
239
            }
240
241 153
            if (count($arguments) > 0) {
242 6
                $stdIn  = array_shift($arguments);
243 6
                if (!is_string($stdIn)) {
244
                    $stdIn   = null;
245
                }
246
            }
247
        }
248 153
        return array($path, $method, $args, $stdIn);
249
    }
250
251
    /**
252
     * Method overloading - allows calling VCS commands directly as class methods
253
     *
254
     * @param   string  $method             The VCS command, e.g. show, commit or add
255
     * @param   array   $arguments          The command arguments with the path to the VCS
256
     *                                      repository being the first argument
257
     * @return  CallResult
258
     */
259 153
    public function __call($method, array $arguments)
260
    {
261 153
        list($path, $method, $args, $stdIn) = $this->extractCallParametersFromMagicCall($method, $arguments);
262
263 153
        $call   = $this->createCall($path, $method, $args);
264 153
        return $call->execute($stdIn);
265
    }
266
}
267
268