1: <?php
2:
3: namespace GAubry\Helpers;
4:
5: /**
6: * Some helpers used in several personal packages.
7: * @SuppressWarnings(TooManyMethods)
8: *
9: * Copyright (c) 2013 Geoffroy Aubry <geoffroy.aubry@free.fr>
10: * Licensed under the GNU Lesser General Public License v3 (LGPL version 3).
11: *
12: * @copyright 2013 Geoffroy Aubry <geoffroy.aubry@free.fr>
13: * @license http://www.gnu.org/licenses/lgpl.html
14: */
15: class Helpers
16: {
17: /**
18: * @codeCoverageIgnore
19: */
20: private function __construct()
21: {
22: }
23:
24: /**
25: * Flatten a multidimensional array (keys are ignored).
26: *
27: * @param array $aArray
28: * @return array a one dimensional array.
29: * @see http://stackoverflow.com/a/1320156/1813519
30: */
31: public static function flattenArray (array $aArray)
32: {
33: $aFlattened = array();
34: array_walk_recursive(
35: $aArray,
36: function ($mValue) use (&$aFlattened) {
37: $aFlattened[] = $mValue;
38: }
39: );
40: return $aFlattened;
41: }
42:
43: /**
44: * Returns the UTF-8 translation of the specified string, only if not already in UTF-8.
45: *
46: * @param string $s
47: * @return string the UTF-8 translation of the specified string, only if not already in UTF-8.
48: */
49: public static function utf8Encode ($str)
50: {
51: return (preg_match('//u', $str) === 1 ? $str : utf8_encode($str));
52: }
53:
54: /**
55: * Executes the given shell command and returns an array filled with every line of output from the command.
56: * Trailing whitespace, such as \n, is not included in this array.
57: * On shell error (exit code <> 0), throws a \RuntimeException with error message..
58: *
59: * @param string $sCmd shell command
60: * @param string $sOutputPath optional redirection of standard output
61: * @param string $sErrorPath optional redirection of standard error
62: * @param bool $bAppend true to append to specified files
63: * @return array array filled with every line of output from the command
64: * @throws \RuntimeException if shell error
65: */
66: public static function exec ($sCmd, $sOutputPath = '', $sErrorPath = '', $bAppend = false)
67: {
68: // set STDOUT and STDERR
69: $sAppending = ($bAppend ? '>>' : '>');
70: if (empty($sOutputPath)) {
71: if (empty($sErrorPath)) {
72: $sStreams = '2>&1';
73: } else {
74: $sStreams = "2$sAppending$sErrorPath";
75: }
76: } elseif (empty($sErrorPath)) {
77: $sStreams = "2>&1 1$sAppending$sOutputPath";
78: } else {
79: $sStreams = "1$sAppending$sOutputPath 2$sAppending$sErrorPath";
80: }
81:
82: // execute cmd
83: $sFullCmd = "( $sCmd ) $sStreams";
84: exec($sFullCmd, $aResult, $iReturnCode);
85:
86: // retrieve content of STDOUT and STDERR
87: if (empty($sOutputPath)) {
88: $aOutput = $aResult;
89: if (empty($sErrorPath)) {
90: $aError = $aResult;
91: } else {
92: $aError = file($sErrorPath, FILE_IGNORE_NEW_LINES);
93: }
94: } elseif (empty($sErrorPath)) {
95: $aOutput = file($sOutputPath, FILE_IGNORE_NEW_LINES);
96: $aError = $aResult;
97: } else {
98: $aOutput = file($sOutputPath, FILE_IGNORE_NEW_LINES);
99: $aError = file($sErrorPath, FILE_IGNORE_NEW_LINES);
100: }
101:
102: // result
103: if ($iReturnCode !== 0) {
104: throw new \RuntimeException(
105: "Exit code not null: $iReturnCode. Result: '" . implode("\n", $aError) . "'",
106: $iReturnCode
107: );
108: }
109: return $aOutput;
110: }
111:
112: /**
113: * Remove all Bash color sequences from the specified string.
114: *
115: * @param string $sMsg
116: * @return string specified string without any Bash color sequence.
117: */
118: public static function stripBashColors ($sMsg)
119: {
120: return preg_replace('/\x1B\[([0-9]{1,2}(;[0-9]{1,2}){0,2})?[m|K]/', '', $sMsg);
121: }
122:
123: /**
124: * Rounds specified value with precision $iPrecision as native round() function, but keep trailing zeros.
125: *
126: * @param float $fValue value to round
127: * @param int $iPrecision the optional number of decimal digits to round to (can also be negative)
128: * @return string
129: */
130: public static function round ($fValue, $iPrecision = 0)
131: {
132: $sPrintfPrecision = max(0, $iPrecision);
133: return sprintf("%01.{$sPrintfPrecision}f", round($fValue, $iPrecision));
134: }
135:
136: /**
137: * Returns a string with the first character of each word in specified string capitalized,
138: * if that character is alphabetic.
139: * Additionally, each character that is immediately after one of $aDelimiters will be capitalized too.
140: *
141: * @param string $sString
142: * @param array $aDelimiters
143: * @return string
144: */
145: public static function ucwordWithDelimiters ($sString, array $aDelimiters = array())
146: {
147: $sReturn = ucwords($sString);
148: foreach ($aDelimiters as $sDelimiter) {
149: if (strpos($sReturn, $sDelimiter) !== false) {
150: $sReturn = implode($sDelimiter, array_map('ucfirst', explode($sDelimiter, $sReturn)));
151: }
152: }
153: return $sReturn;
154: }
155:
156: /**
157: * Returns specified value in the most appropriate unit, with that unit.
158: * If $bBinaryPrefix is FALSE then use SI units (i.e. k, M, G, T),
159: * else use IED units (i.e. Ki, Mi, Gi, Ti).
160: * @see http://en.wikipedia.org/wiki/Binary_prefix
161: *
162: * @param int $iValue
163: * @param bool $bBinaryPrefix
164: * @return array a pair constituted by specified value in the most appropriate unit and that unit
165: */
166: public static function intToMultiple ($iValue, $bBinaryPrefix = false)
167: {
168: static $aAllPrefixes = array(
169: 10 => array(12 => 'T', 9 => 'G', 6 => 'M', 3 => 'k', 0 => ''),
170: 2 => array(40 => 'Ti', 30 => 'Gi', 20 => 'Mi', 10 => 'Ki', 0 => ''),
171: );
172:
173: $iBase = ($bBinaryPrefix ? 2 : 10);
174: $aPrefixes = $aAllPrefixes[$iBase];
175: $iMaxMultiple = 0;
176: foreach (array_keys($aPrefixes) as $iMultiple) {
177: if ($iValue >= pow($iBase, $iMultiple)) {
178: $iMaxMultiple = $iMultiple;
179: break;
180: }
181: }
182:
183: return array($iValue / pow($iBase, $iMaxMultiple), $aPrefixes[$iMaxMultiple]);
184: }
185:
186: /**
187: * Format a number with grouped thousands.
188: * It is an extended version of number_format() that allows do not specify $decimals.
189: *
190: * @param float $fNumber The number being formatted.
191: * @param string $sDecPoint Sets the separator for the decimal point.
192: * @param string $sThousandsSep Sets the thousands separator. Only the first character of $thousands_sep is used.
193: * @param int $iDecimals Sets the number of decimal points.
194: * @return string A formatted version of $number.
195: */
196: public static function numberFormat ($fNumber, $sDecPoint = '.', $sThousandsSep = ',', $iDecimals = null)
197: {
198: if ($iDecimals !== null) {
199: return number_format($fNumber, $iDecimals, $sDecPoint, $sThousandsSep);
200: } else {
201: $tmp = explode('.', $fNumber);
202: $out = number_format($tmp[0], 0, $sDecPoint, $sThousandsSep);
203: if (isset($tmp[1])) {
204: $out .= $sDecPoint.$tmp[1];
205: }
206: return $out;
207: }
208: }
209:
210: /**
211: * Formats a line passed as a fields array as CSV and return it, without the trailing newline.
212: * Inspiration: http://www.php.net/manual/en/function.str-getcsv.php#88773
213: *
214: * @param array $aInput
215: * @param string $sDelimiter
216: * @param string $sEnclosure
217: * @return string specified array converted into CSV format string
218: */
219: public static function strPutCSV ($aInput, $sDelimiter = ',', $sEnclosure = '"')
220: {
221: // Open a memory "file" for read/write...
222: $hTmpFile = fopen('php://temp', 'r+');
223: fputcsv($hTmpFile, $aInput, $sDelimiter, $sEnclosure);
224: // ... rewind the "file" so we can read what we just wrote...
225: rewind($hTmpFile);
226: $sData = fgets($hTmpFile);
227: fclose($hTmpFile);
228: // ... and return the $data to the caller, with the trailing newline from fgets() removed.
229: return rtrim($sData, "\n");
230: }
231:
232: /**
233: * array_merge_recursive() does indeed merge arrays, but it converts values with duplicate
234: * keys to arrays rather than overwriting the value in the first array with the duplicate
235: * value in the second array, as array_merge does. I.e., with array_merge_recursive(),
236: * this happens (documented behavior):
237: *
238: * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
239: * ⇒ array('key' => array('org value', 'new value'));
240: *
241: * arrayMergeRecursiveDistinct() does not change the datatypes of the values in the arrays.
242: * Matching keys' values in the second array overwrite those in the first array, as is the
243: * case with array_merge, i.e.:
244: *
245: * arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
246: * ⇒ array('key' => array('new value'));
247: *
248: * EVO on indexed arrays:
249: * Before:
250: * arrayMergeRecursiveDistinct(array('a', 'b'), array('c')) ⇒ array('c', 'b')
251: * Now:
252: * ⇒ array('c')
253: *
254: * @param array $aArray1
255: * @param array $aArray2
256: * @return array An array of values resulted from strictly merging the arguments together.
257: * @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk>
258: * @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com>
259: * @author Geoffroy Aubry
260: * @see http://fr2.php.net/manual/en/function.array-merge-recursive.php#89684
261: */
262: public static function arrayMergeRecursiveDistinct (array $aArray1, array $aArray2)
263: {
264: $aMerged = $aArray1;
265: if (self::isAssociativeArray($aMerged)) {
266: foreach ($aArray2 as $key => &$value) {
267: if (is_array($value) && isset($aMerged[$key]) && is_array($aMerged[$key])) {
268: $aMerged[$key] = self::arrayMergeRecursiveDistinct($aMerged[$key], $value);
269: } else {
270: $aMerged[$key] = $value;
271: }
272: }
273: } else {
274: $aMerged = $aArray2;
275: }
276: return $aMerged;
277: }
278:
279: /**
280: * Returns TRUE iff the specified array is associative.
281: * If the specified array is empty, then return FALSE.
282: *
283: * http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential
284: *
285: * @param array $aArray
286: * @return bool true ssi iff the specified array is associative
287: */
288: public static function isAssociativeArray (array $aArray)
289: {
290: foreach (array_keys($aArray) as $key) {
291: if (! is_int($key)) {
292: return true;
293: }
294: }
295: return false;
296: }
297: }
298: