Completed
Push — master ( 080ba5...2a2655 )
by Fabio
13:17 queued 07:29
created

CultureInfo::getCurrencies()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CultureInfo class file.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the BSD License.
8
 *
9
 * Copyright(c) 2004 by Qiang Xue. All rights reserved.
10
 *
11
 * To contact the author write to {@link mailto:[email protected] Qiang Xue}
12
 * The latest version of PRADO can be obtained from:
13
 * {@link http://prado.sourceforge.net/}
14
 *
15
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
16
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
17
 * @package Prado\I18N\core
18
 */
19
20
namespace Prado\I18N\core;
21
22
use Exception;
23
24
/**
25
 * CultureInfo class.
26
 *
27
 * Represents information about a specific culture including the
28
 * names of the culture, the calendar used, as well as access to
29
 * culture-specific objects that provide methods for common operations,
30
 * such as formatting dates, numbers, and currency.
31
 *
32
 * The CultureInfo class holds culture-specific information, such as the
33
 * associated language, sublanguage, country/region, calendar, and cultural
34
 * conventions.
35
 *
36
 * The culture names follow the format "<languagecode>_<country/regioncode>",
37
 * where <languagecode> is a lowercase two-letter code derived from ISO 639
38
 * codes. You can find a full list of the ISO-639 codes at
39
 * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
40
 *
41
 * The <country/regioncode2> is an uppercase two-letter code derived from
42
 * ISO 3166. A copy of ISO-3166 can be found at
43
 * http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
44
 *
45
 * For example, Australian English is "en_AU".
46
 *
47
 * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
48
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
49
 * @package Prado\I18N\core
50
 */
51
class CultureInfo
52
{
53
	/**
54
	 * The ICU data array, shared by all instances of this class.
55
	 * @var array
56
	 */
57
	protected static $data = [];
58
59
	/**
60
	 * The current culture.
61
	 * @var string
62
	 */
63
	protected $culture;
64
65
	/**
66
	 * A list of resource bundles keys
67
	 * @var array
68
	 */
69
	protected static $bundleNames = [
70
		'Core' => null,
71
		'Currencies' => 'ICUDATA-curr',
72
		'Languages' => 'ICUDATA-lang',
73
		'Countries' => 'ICUDATA-region',
74
		'zoneStrings' => 'ICUDATA-zone',
75
	];
76
77
	/**
78
	 * A list of properties that are accessable/writable.
79
	 * @var array
80
	 */
81
	protected $properties = [];
82
83
	/**
84
	 * Culture type, all.
85
	 * @see getCultures()
86
	 * @var int
87
	 */
88
	const ALL = 0;
89
90
	/**
91
	 * Culture type, neutral.
92
	 * @see getCultures()
93
	 * @var int
94
	 */
95
	const NEUTRAL = 1;
96
97
	/**
98
	 * Culture type, specific.
99
	 * @see getCultures()
100
	 * @var int
101
	 */
102
	const SPECIFIC = 2;
103
104
	/**
105
	 * Display the culture name.
106
	 * @return string the culture name.
107
	 * @see getName()
108
	 */
109
	public function __toString()
110
	{
111
		return $this->getName();
112
	}
113
114
	/**
115
	 * Allow functions that begins with 'set' to be called directly
116
	 * as an attribute/property to retrieve the value.
117
	 * @param mixed $name
118
	 * @return mixed
119
	 */
120 View Code Duplication
	public function __get($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
121
	{
122
		$getProperty = 'get' . $name;
123
		if (in_array($getProperty, $this->properties)) {
124
			return $this->$getProperty();
125
		} else {
126
			throw new Exception('Property ' . $name . ' does not exists.');
127
		}
128
	}
129
130
	/**
131
	 * Allow functions that begins with 'set' to be called directly
132
	 * as an attribute/property to set the value.
133
	 * @param mixed $name
134
	 * @param mixed $value
135
	 */
136 View Code Duplication
	public function __set($name, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
	{
138
		$setProperty = 'set' . $name;
139
		if (in_array($setProperty, $this->properties)) {
140
			$this->$setProperty($value);
141
		} else {
142
			throw new Exception('Property ' . $name . ' can not be set.');
143
		}
144
	}
145
146
147
	/**
148
	 * Initializes a new instance of the CultureInfo class based on the
149
	 * culture specified by name. E.g. <code>new CultureInfo('en_AU');</cdoe>
150
	 * The culture indentifier must be of the form
151
	 * "language_(country/region/variant)".
152
	 * @param string $culture a culture name, e.g. "en_AU".
153
	 * @return return new CultureInfo.
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
154
	 */
155
	public function __construct($culture = 'en')
156
	{
157
		$this->properties = get_class_methods($this);
158
159
		if (empty($culture)) {
160
			$culture = 'en';
161
		}
162
163
		$this->setCulture($culture);
164
	}
165
166
	/**
167
	 * Gets the CultureInfo that for this culture string
168
	 * @param mixed $culture
169
	 * @return CultureInfo invariant culture info is "en".
170
	 */
171
	public static function getInstance($culture)
172
	{
173
		if (!isset(self::$instances[$culture])) {
174
			self::$instances[$culture] = new CultureInfo($culture);
175
		}
176
		return self::$instances[$culture];
177
	}
178
179
	/**
180
	 * Determine if a given culture is valid. Simply checks that the
181
	 * culture data exists.
182
	 * @param string $culture a culture
183
	 * @return bool true if valid, false otherwise.
184
	 */
185
	public static function validCulture($culture)
186
	{
187
		return in_array($culture, self::getCultures());
188
	}
189
190
	/**
191
	 * Set the culture for the current instance. The culture indentifier
192
	 * must be of the form "<language>_(country/region)".
193
	 * @param string $culture culture identifier, e.g. "fr_FR_EURO".
194
	 */
195
	protected function setCulture($culture)
196
	{
197
		if (!empty($culture)) {
198
			if (!preg_match('/^[_\\w]+$/', $culture)) {
199
				throw new Exception('Invalid culture supplied: ' . $culture);
200
			}
201
		}
202
203
		$this->culture = $culture;
204
	}
205
206
	/**
207
	 * Load the ICU culture data for the specific culture identifier.
208
	 * @param string $culture the culture identifier.
0 ignored issues
show
Bug introduced by
There is no parameter named $culture. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
209
	 */
210
	protected function loadCultureData($key)
211
	{
212
		foreach(self::$bundleNames as $bundleKey => $bundleName)
213
		{
214
			if($key == $bundleKey)
215
			{
216
				if(!array_key_exists($this->culture, self::$data))
217
					self::$data[$this->culture] = [];
218
219
				self::$data[$this->culture][$bundleKey] = \ResourceBundle::create($this->culture, $bundleName, true);
220
				break;
221
			}
222
		}
223
	}
224
225
	/**
226
	 * Find the specific ICU data information from the data.
227
	 * The path to the specific ICU data is separated with a slash "/".
228
	 * E.g. To find the default calendar used by the culture, the path
229
	 * "calendar/default" will return the corresponding default calendar.
230
	 * @param string $path the data you want to find.
231
	 * @param string $key bundle name.
232
	 * @return mixed the specific ICU data.
233
	 */
234
	public function findInfo($path='/', $key = null)
235
	{
236
		if($key === null)
237
		{
238
			// try to guess the bundle from the path. Always defaults to "Core".
239
			$key = 'Core';
240
			foreach(self::$bundleNames as $bundleName => $icuBundleName)
241
			{
242
				if(strpos($path, $bundleName) === 0)
243
				{
244
					$key = $bundleName;
245
					break;
246
				}
247
			}
248
		}
249
250
		if(!array_key_exists($this->culture, self::$data))
251
			$this->loadCultureData($key);
252
		if(!array_key_exists($this->culture, self::$data) || !array_key_exists($key, self::$data[$this->culture]))
253
			return [];
254
255
		return $this->searchResources(self::$data[$this->culture][$key], $path);
256
	}
257
258
	/**
259
	 * Search the array for a specific value using a path separated using
260
	 * slash "/" separated path. e.g to find $info['hello']['world'],
261
	 * the path "hello/world" will return the corresponding value.
262
	 * @param array $info the array for search
0 ignored issues
show
Bug introduced by
There is no parameter named $info. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
263
	 * @param string $path slash "/" separated array path.
264
	 * @return mixed the value array using the path
265
	 */
266
	private function searchResources($resource, $path='/')
267
	{
268
		$index = explode('/', $path);
269
		for($i = 0, $k = count($index); $i < $k; ++$i)
270
		{
271
			if(is_object($resource))
272
				$resource = $resource->get($index[$i]);
273
		}
274
275
		return $this->simplify($resource);
276
	}
277
278
	/**
279
	 * Gets the culture name in the format
280
	 * "<languagecode2>_(country/regioncode2)".
281
	 * @return string culture name.
282
	 */
283
	public function getName()
284
	{
285
		return $this->culture;
286
	}
287
288
	/**
289
	 * Gets the default calendar used by the culture, e.g. "gregorian".
290
	 * @return string the default calendar.
291
	 */
292
	public function getCalendar()
293
	{
294
		return $this->findInfo('calendar/default');
295
	}
296
297
	/**
298
	 * Gets the culture name in the language that the culture is set
299
	 * to display. Returns <code>array('Language','Country');</code>
300
	 * 'Country' is omitted if the culture is neutral.
301
	 * @return array array with language and country as elements, localized.
302
	 */
303
	public function getNativeName()
304
	{
305
		$lang = substr($this->culture, 0, 2);
306
		$reg = substr($this->culture, 3, 2);
307
		$language = $this->findInfo("Languages/{$lang}");
308
		$region = $this->findInfo("Countries/{$reg}");
309
		if($region)
0 ignored issues
show
Bug Best Practice introduced by
The expression $region of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
310
			return $language.' ('.$region.')';
311
		else
312
			return $language;
313
	}
314
315
	/**
316
	 * Gets the culture name in English.
317
	 * Returns <code>array('Language','Country');</code>
318
	 * 'Country' is omitted if the culture is neutral.
319
	 * @return string language (country), it may locale code string if english name does not exist.
320
	 */
321
	public function getEnglishName()
322
	{
323
		$lang = substr($this->culture, 0, 2);
324
		$reg = substr($this->culture, 3, 2);
325
		$culture = $this->getInvariantCulture();
326
327
		$language = $culture->findInfo("Languages/{$lang}");
328
		if (count($language) == 0) {
329
			return $this->culture;
330
		}
331
332
		$region = $culture->findInfo("Countries/{$reg}");
333
		if($region)
0 ignored issues
show
Bug Best Practice introduced by
The expression $region of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
334
			return $language.' ('.$region.')';
335
		else
336
			return $language;
337
	}
338
339
	/**
340
	 * Gets the CultureInfo that is culture-independent (invariant).
341
	 * Any changes to the invariant culture affects all other
342
	 * instances of the invariant culture.
343
	 * The invariant culture is assumed to be "en";
344
	 * @return CultureInfo invariant culture info is "en".
345
	 */
346
	public static function getInvariantCulture()
347
	{
348
		static $invariant;
349
		if ($invariant === null) {
350
			$invariant = new CultureInfo();
351
		}
352
		return $invariant;
353
	}
354
355
	/**
356
	 * Gets a value indicating whether the current CultureInfo
357
	 * represents a neutral culture. Returns true if the culture
358
	 * only contains two characters.
359
	 * @return bool true if culture is neutral, false otherwise.
360
	 */
361
	public function getIsNeutralCulture()
362
	{
363
		return strlen($this->culture) == 2;
364
	}
365
366
	/**
367
	 * Gets the list of supported cultures filtered by the specified
368
	 * culture type. This is an EXPENSIVE function, it needs to traverse
369
	 * a list of ICU files in the data directory.
370
	 * This function can be called statically.
371
	 * @param int $type culture type, CultureInfo::ALL, CultureInfo::NEUTRAL
372
	 * or CultureInfo::SPECIFIC.
373
	 * @return array list of culture information available.
374
	 */
375
	public static function getCultures($type=CultureInfo::ALL)
376
	{
377
		$all = \ResourceBundle::getLocales('');
378
379
		switch($type)
380
		{
381
			case CultureInfo::ALL :
382
				return $all;
383 View Code Duplication
			case CultureInfo::NEUTRAL :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
				foreach($all as $key => $culture)
385
				{
386
					if(strlen($culture) != 2)
387
						unset($all[$key]);
388
				}
389
				return $all;
390 View Code Duplication
			case CultureInfo::SPECIFIC :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
				foreach($all as $key => $culture)
392
				{
393
					if(strlen($culture) == 2)
394
						unset($all[$key]);
395
				}
396
				return $all;
397
		}
398
	}
399
400
	/**
401
	 * Simplify a single element array into its own value.
402
	 * E.g. <code>array(0 => array('hello'), 1 => 'world');</code>
403
	 * becomes <code>array(0 => 'hello', 1 => 'world');</code>
404
	 * @param array $array with single elements arrays
0 ignored issues
show
Bug introduced by
There is no parameter named $array. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
405
	 * @return array simplified array.
406
	 */
407
	protected function simplify($obj)
408
	{
409
		if(is_scalar($obj)) {
410
			return $obj;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $obj; (integer|double|string|boolean) is incompatible with the return type documented by Prado\I18N\core\CultureInfo::simplify of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
411
		} elseif($obj instanceof \ResourceBundle) {
412
			$array = [];
413
			foreach($obj as $k => $v)
0 ignored issues
show
Bug introduced by
The expression $obj of type object<ResourceBundle> is not traversable.
Loading history...
414
				$array[$k] = $v;
415
		} else {
416
			$array = $obj;
417
		}
418
419
		for($i = 0, $k = count($array); $i<$k; ++$i)
420
		{
421
			$key = key($array);
422
			if ($key !== null
423
				&& is_array($array[$key])
424
				&& count($array[$key]) == 1) {
425
				$array[$key] = $array[$key][0];
426
			}
427
			next($array);
428
		}
429
		return $array;
430
	}
431
432
	/**
433
	 * Get a list of countries in the language of the localized version.
434
	 * @return array a list of localized country names.
435
	 */
436
	public function getCountries()
437
	{
438
		return $this->simplify($this->findInfo('Countries', 'Countries'));
439
	}
440
441
	/**
442
	 * Get a list of currencies in the language of the localized version.
443
	 * @return array a list of localized currencies.
444
	 */
445
	public function getCurrencies()
446
	{
447
		static $arr;
448
		if($arr === null)
449
		{
450
			$arr = $this->findInfo('Currencies', 'Currencies');
451
			foreach($arr as $k => $v)
452
				$arr[$k] = $this->simplify($v);
453
		}
454
		return $arr;
455
	}
456
457
	/**
458
	 * Get a list of languages in the language of the localized version.
459
	 * @return array list of localized language names.
460
	 */
461
	public function getLanguages()
462
	{
463
		return $this->simplify($this->findInfo('Languages', 'Languages'));
464
	}
465
466
	/**
467
	 * Get a list of scripts in the language of the localized version.
468
	 * @return array list of localized script names.
469
	 */
470
	public function getScripts()
471
	{
472
		return $this->simplify($this->findInfo('Scripts', 'Languages'));
473
	}
474
475
	/**
476
	 * Get a list of timezones in the language of the localized version.
477
	 * @return array list of localized timezones.
478
	 */
479
	public function getTimeZones()
480
	{
481
		static $arr;
482
		if($arr === null)
483
		{
484
			$validPrefixes = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Etc', 'Europe', 'Indian', 'Pacific');
485
			$tmp = $this->findInfo('zoneStrings', 'zoneStrings');
486
			foreach($tmp as $k => $v)
487
			{
488
				foreach($validPrefixes as $prefix)
489
				{
490
					if(strpos($k, $prefix) === 0)
491
					{
492
						$arr[] = str_replace(':', '/', $k);
493
						break;
494
					}
495
				}
496
			}
497
		}
498
		return $arr;
499
	}
500
}
501