1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Console;
11
12/**
13 * A static, utility class for interacting with Console environment.
14 * Declared abstract to prevent from instantiating.
15 */
16abstract class Console
17{
18    /**
19     * @var Adapter\AdapterInterface
20     */
21    protected static $instance;
22
23    /**
24     * Allow overriding whether or not we're in a console env. If set, and
25     * boolean, returns that value from isConsole().
26     * @var bool
27     */
28    protected static $isConsole;
29
30    /**
31     * Create and return Adapter\AdapterInterface instance.
32     *
33     * @param  null|string  $forceAdapter Optional adapter class name. Can be absolute namespace or class name
34     *                                    relative to Zend\Console\Adapter\. If not provided, a best matching
35     *                                    adapter will be automatically selected.
36     * @param  null|string  $forceCharset optional charset name can be absolute namespace or class name relative to
37     *                                    Zend\Console\Charset\. If not provided, charset will be detected
38     *                                    automatically.
39     * @throws Exception\InvalidArgumentException
40     * @throws Exception\RuntimeException
41     * @return Adapter\AdapterInterface
42     */
43    public static function getInstance($forceAdapter = null, $forceCharset = null)
44    {
45        if (static::$instance instanceof Adapter\AdapterInterface) {
46            return static::$instance;
47        }
48
49        // Create instance
50
51        if ($forceAdapter !== null) {
52            // Use the supplied adapter class
53            if (substr($forceAdapter, 0, 1) == '\\') {
54                $className = $forceAdapter;
55            } elseif (stristr($forceAdapter, '\\')) {
56                $className = __NAMESPACE__ . '\\' . ltrim($forceAdapter, '\\');
57            } else {
58                $className = __NAMESPACE__ . '\\Adapter\\' . $forceAdapter;
59            }
60
61            if (!class_exists($className)) {
62                throw new Exception\InvalidArgumentException(sprintf(
63                    'Cannot find Console adapter class "%s"',
64                    $className
65                ));
66            }
67        } else {
68            // Try to detect best instance for console
69            $className = static::detectBestAdapter();
70
71            // Check if we were able to detect console adapter
72            if (!$className) {
73                throw new Exception\RuntimeException('Cannot create Console adapter - am I running in a console?');
74            }
75        }
76
77        // Create adapter instance
78        static::$instance = new $className();
79
80        // Try to use the supplied charset class
81        if ($forceCharset !== null) {
82            if (substr($forceCharset, 0, 1) == '\\') {
83                $className = $forceCharset;
84            } elseif (stristr($forceAdapter, '\\')) {
85                $className = __NAMESPACE__ . '\\' . ltrim($forceCharset, '\\');
86            } else {
87                $className = __NAMESPACE__ . '\\Charset\\' . $forceCharset;
88            }
89
90            if (!class_exists($className)) {
91                throw new Exception\InvalidArgumentException(sprintf(
92                    'Cannot find Charset class "%s"',
93                    $className
94                ));
95            }
96
97            // Set adapter charset
98            static::$instance->setCharset(new $className());
99        }
100
101        return static::$instance;
102    }
103
104    /**
105     * Reset the console instance
106     */
107    public static function resetInstance()
108    {
109        static::$instance = null;
110    }
111
112    /**
113     * Check if currently running under MS Windows
114     *
115     * @see http://stackoverflow.com/questions/738823/possible-values-for-php-os
116     * @return bool
117     */
118    public static function isWindows()
119    {
120        return
121            (defined('PHP_OS') && (substr_compare(PHP_OS, 'win', 0, 3, true) === 0)) ||
122            (getenv('OS') != false && substr_compare(getenv('OS'), 'windows', 0, 7, true))
123        ;
124    }
125
126    /**
127     * Check if running under MS Windows Ansicon
128     *
129     * @return bool
130     */
131    public static function isAnsicon()
132    {
133        return getenv('ANSICON') !== false;
134    }
135
136    /**
137     * Check if running in a console environment (CLI)
138     *
139     * By default, returns value of PHP_SAPI global constant. If $isConsole is
140     * set, and a boolean value, that value will be returned.
141     *
142     * @return bool
143     */
144    public static function isConsole()
145    {
146        if (null === static::$isConsole) {
147            static::$isConsole = (PHP_SAPI == 'cli');
148        }
149        return static::$isConsole;
150    }
151
152    /**
153     * Override the "is console environment" flag
154     *
155     * @param  null|bool $flag
156     */
157    public static function overrideIsConsole($flag)
158    {
159        if (null != $flag) {
160            $flag = (bool) $flag;
161        }
162        static::$isConsole = $flag;
163    }
164
165    /**
166     * Try to detect best matching adapter
167     * @return string|null
168     */
169    public static function detectBestAdapter()
170    {
171        // Check if we are in a console environment
172        if (!static::isConsole()) {
173            return;
174        }
175
176        // Check if we're on windows
177        if (static::isWindows()) {
178            if (static::isAnsicon()) {
179                $className = __NAMESPACE__ . '\Adapter\WindowsAnsicon';
180            } else {
181                $className = __NAMESPACE__ . '\Adapter\Windows';
182            }
183
184            return $className;
185        }
186
187        // Default is a Posix console
188        $className = __NAMESPACE__ . '\Adapter\Posix';
189        return $className;
190    }
191
192    /**
193     * Pass-thru static call to current AdapterInterface instance.
194     *
195     * @param $funcName
196     * @param $arguments
197     * @return mixed
198     */
199    public static function __callStatic($funcName, $arguments)
200    {
201        $instance = static::getInstance();
202        return call_user_func_array(array($instance, $funcName), $arguments);
203    }
204}
205