Well, I use the idna_convert PHP class (http://idnaconv.net/index.html) in order to encode / decode domain names.
Unfortunately, it doesn't seem to provide an interface to check whether a domain name is already punycode or not.
What's the best way to achieve this? It would be nice if someone could post source code how to verify a domain is punycode or not (with explanation, because the idna_convert code is not really clear to me). I already know how to catch the exception from idna_convert. :-)
Btw.: idna_convert throws an exception when you try to convert a domain name to punycode that is already punycode (see https://github.com/phlylabs/idna-convert/blob/master/src/Punycode.php; line 157). Moreover, I do not really understand how their check works.
The simplest way - just convert it anyway and check if the result is equal to input.
EDIT: You can extend Punycode class with a check like this:
class PunycodeCheck extends Punycode
{
public function check_encoded($decoded)
{
$extract = self::byteLength(self::punycodePrefix);
$check_pref = $this->UnicodeTranscoder->utf8_ucs4array(self::punycodePrefix);
$check_deco = array_slice($decoded, 0, $extract);
if ($check_pref == $check_deco)
return true;
return false;
}
}
It depends on what exactly you want.
As first basic check, see if the domain name contains only ASCII characters. If yes, then the domain is "already punycode", in the sense that it can't be further transformed. For checking whether a string only contains ASCII characters, see Determine if UTF-8 text is all ASCII?.
If on top of that, you want to check wether the domain is in the IDN form, split the domain at the dots . and check if any of the substrings starts with xn--.
If in addition to that, you want to check if the domain is IDN and is valid, just attempt to decode it with the library's decode function.
It is not very easy to check if a domain is in Punycode or not. Several checks in needed to implement by rules that are already said by #Wladston.
This is the adapted code examples that I took from ValidateHelper class from the composition of my library: Helper classes for PrestaShop CMS. I have also added the test and the result of its execution.
/**
* Validate helper.
*
* #author Maksim T. <zapalm#yandex.com>
*/
class ValidateHelper
{
/**
* Checks if the given domain is in Punycode.
*
* #param string $domain The domain to check.
*
* #return bool Whether the domain is in Punycode.
*
* #see https://developer.mozilla.org/en-US/docs/Mozilla/Internationalized_domain_names_support_in_Mozilla#ASCII-compatible_encoding_.28ACE.29
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isPunycodeDomain($domain)
{
$hasPunycode = false;
foreach (explode('.', $domain) as $part) {
if (false === static::isAscii($part)) {
return false;
}
if (static::isPunycode($part)) {
$hasPunycode = true;
}
}
return $hasPunycode;
}
/**
* Checks if the given value is in ASCII character encoding.
*
* #param string $value The value to check.
*
* #return bool Whether the value is in ASCII character encoding.
*
* #see https://en.wikipedia.org/wiki/ASCII
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isAscii($value)
{
return ('ASCII' === mb_detect_encoding($value, 'ASCII', true));
}
/**
* Checks if the given value is in Punycode.
*
* #param string $value The value to check.
*
* #return bool Whether the value is in Punycode.
*
* #throws \LogicException If the string is not encoded by UTF-8.
*
* #see https://en.wikipedia.org/wiki/Punycode
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isPunycode($value)
{
if (false === static::isAscii($value)) {
return false;
}
if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true)) {
throw new \LogicException('The string should be encoded by UTF-8 to do the right check.');
}
return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8'));
}
}
/**
* Test Punycode domain validator.
*
* #author Maksim T. <zapalm#yandex.com>
*/
class Test
{
/**
* Run the test.
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function run()
{
$domains = [
// White list
'почта#престашоп.рф' => false, // Russian, Unicode
'modulez.ru' => false, // English, ASCII
'xn--80aj2abdcii9c.xn--p1ai' => true, // Russian, ASCII
'xn--80a1acn3a.xn--j1amh' => true, // Ukrainian, ASCII
'xn--srensen-90a.example.com' => true, // German, ASCII
'xn--mxahbxey0c.xn--xxaf0a' => true, // Greek, ASCII
'xn--fsqu00a.xn--4rr70v' => true, // Chinese, ASCII
// Black List
'xn--престашоп.xn--рф' => false, // Russian, Unicode
'xn--prestashop.рф' => false, // Russian, Unicode
];
foreach ($domains as $domain => $isPunycode) {
echo 'TEST: ' . $domain . (ValidateHelper::isPunycodeDomain($domain)
? ' is in Punycode [' . ($isPunycode ? 'OK' : 'FAIL') . ']'
: ' is NOT in Punycode [' . (false === $isPunycode ? 'OK' : 'FAIL') . ']'
) . PHP_EOL;
}
}
}
Test::run();
// The output result:
//
// TEST: почта#престашоп.рф is NOT in Punycode [OK]
// TEST: modulez.ru is NOT in Punycode [OK]
// TEST: xn--80aj2abdcii9c.xn--p1ai is in Punycode [OK]
// TEST: xn--80a1acn3a.xn--j1amh is in Punycode [OK]
// TEST: xn--srensen-90a.example.com is in Punycode [OK]
// TEST: xn--mxahbxey0c.xn--xxaf0a is in Punycode [OK]
// TEST: xn--fsqu00a.xn--4rr70v is in Punycode [OK]
// TEST: xn--престашоп.xn--рф is NOT in Punycode [OK]
// TEST: xn--prestashop.рф is NOT in Punycode [OK]
The only exception that the encode() method throws is when the domain is already punycode. So you can do the following:
try {
$punycode->encode($decoded);
} catch (\InvalidArgumentException $e) {
//do whatever is needed when already punycode
//or do nothing
}
However it's a workaround solution.
Related
I know this may look like duplicate from this question: Ignore slash while using encryption in Codeigniter. But I still didn't have the answer from it.
I want to sent encrypted email name as URL to their email account.
Then that URL is decrypted to search if that email name is exist in my database to permit that email into my system.
The problem is:
If I use urlencode or base64_encode after encryption, it always resulted in empty value to search the database after decrypt. I think it because the encrypted value always changing.
If I use the casual encryption, it might have the ("/") character.
If I only use the encode, without the encryption, it might permit the email name to have access into my system.
Lastly, I found some library: Ignore Slash while using encryption in codeigniter - GitHub .
But it gave me this error: Undefined property: CI_Loader::$my_encrypt
I don't know what I've done wrong, I already:
Capitalized the class name first letter.
Using the same file name with the class name. (capitalized too)
Change the extend to CI_Encryption because the Encrypt class is already deprecated.
Insert the public function __construct() {parent::__construct();} before all method.
Place the file inside application/library.
Load the library $this->load->library('my_encrypt');
Load the method using $this->my_encrypt->encode($key); this is the line that gave me an error.
I know that this may sound like a simple mistake, but I'm using another third-party library too but it didn't give me an error at all.
Can anyone help me find the mistake / missing step there?
Update -
Before I load the library in the controller, I want to check the result first in view. But it doesn't give me any changes even when I put the code inside controller. Here is the code :
$key = 'example#gmail.com';
$this->load->library('my_encrypt');
$segment = $this->my_encrypt->encode($key);
echo $segment;
echo ( $this->my_encrypt->decode($segment) );
Update:
Fix library code to extend with CI_Encryption library
Have you loaded the library? Name librabry as MY_Encrypt.php in application libraries
<?php
class MY_Encrypt extends CI_Encrypt
{
/**
* Encodes a string.
*
* #param string $string The string to encrypt.
* #param string $key[optional] The key to encrypt with.
* #param bool $url_safe[optional] Specifies whether or not the
* returned string should be url-safe.
* #return string
*/
public function __construct() {
parent::__construct();
}
function encode($string, $key="", $url_safe=TRUE)
{
$ret = parent::encode($string, $key);
if ($url_safe)
{
$ret = strtr(
$ret,
array(
'+' => '.',
'=' => '-',
'/' => '~'
)
);
}
return $ret;
}
/**
* Decodes the given string.
*
* #access public
* #param string $string The encrypted string to decrypt.
* #param string $key[optional] The key to use for decryption.
* #return string
*/
function decode($string, $key="")
{
$string = strtr(
$string,
array(
'.' => '+',
'-' => '=',
'~' => '/'
)
);
return parent::decode($string, $key);
}
}
?>
Now call the encrypt library and use the encryption class instead of my_encrypt
$key='Welcome';
$this->load->library('encrypt');
$key1= $this->encrypt->encode($key);
echo $key1;
fixed to extend the CI_Encryption library, sorry for bothering. :)
class MY_Encrypt extends CI_Encryption
{
/**
* Encodes a string.
*
* #param string $string The string to encrypt.
* #param string $key[optional] The key to encrypt with.
* #param bool $url_safe[optional] Specifies whether or not the
* returned string should be url-safe.
* #return string
*/
public function __construct() {
parent::__construct();
}
function encode($string)
{
$ret = parent::encrypt($string);
if ( !empty($string) )
{
$ret = strtr(
$ret,
array(
'+' => '.',
'=' => '-',
'/' => '~'
)
);
}
return $ret;
}
/**
* Decodes the given string.
*
* #access public
* #param string $string The encrypted string to decrypt.
* #param string $key[optional] The key to use for decryption.
* #return string
*/
function decode($string)
{
$string = strtr(
$string,
array(
'.' => '+',
'-' => '=',
'~' => '/'
)
);
return parent::decrypt($string);
}
}
?>
I'm writing a script for parsing a log file from an network device. The log file generated from the device it's not regular, the lines doesn't follow a logic sequence and haves multiple patterns. My script needs to extract from the log lines only the ones that matches an specific pattern and from that lines specific information as datetime, entry type, resource type and resource name from the url in the string. The pattern that I need to match it's the following:
dd-mm-yyyy hh:mm:ss INFO spx.resource.media - New Resource 'URI' [flags] (dlc/tcd)
where 'INFO' is the entry type, 'spx.resource.media' the resource type and in the URI resides the resource name. Currently we need to filter those that haves a specifics extensions.
I reviewed several posts that cover this subject and using this online tool: I came with this regular expresion:
/(\d{2}-\d{2}-\d{4}\s{1}\d{2}:\d{2}:\d{2})\s{1,}(\w{4})\s{1,}(spx.resource.media)(.{1,}(?<=(?:.jpg)|(?:.png)))/g
The problem is that the last regex group matches the whole URI plus the characters and spaces from the resource type and on, and y only need the filename with the extension. I tried this 'regex-to-get-a-filename-from-a-url' (can't post the link insufficient reputation) but doesn't workout 'cause the debugger marks the ^/ as unescaped delimiter. Also if removed doesn't work. A portion of the log can be found here. I really need to get this.
Thanks for reading and/or answering
have a look at this. First Identify the location of the file then you can loop through accordingly to get what you want
<?php
$handle = #fopen("/tmp/inputfile.txt", "r");
if ($handle) {
while (($buffer = fgets($handle, 4096)) !== false) {
echo $buffer;
}
if (!feof($handle)) {
echo "Error: unexpected fgets() fail\n";
}
fclose($handle);
}
?>
A month ago a came with a solution. What I wanted was to extract the filename and the rest of the subgroups with one pattern, I don´t know if this is possible but with my current regex skills is not. So what I did was to use three regex patterns as you can see in the code below:
This code is part of a class that I (obviously) called Parser. First I define the patterns as constants in the class.
/**
* #const string Log line pattern
*/
const LINE_REGEX_PATTERN = '/(\d{2}-\d{2}-\d{4}\s{1}\d{2}:\d{2}:\d{2})\s{1,}(\w{4})\s{1,}(spx.resource.media)(.{1,}(?<=%extensions%))/';
/**
* #const string Full URL pattern
*/
const FULL_URL_PATTERN = '/\b((?:https?|ftps?|file|spx):\/\/[-A-Z0-9+&##\/%?=~_|$!:,.;]*[A-Z0-9+&##\/%=~_|$])/i';
/**
* #const string Filename pattern
*/
const RESOURCE_REGEX_PATTERN = '/((?:[^\/][\d\w\.-]+)(?<=%extensions%))/';
As you can see, I use a placeholder for the file extensions because in this case I need them to be dynamically set by configuration or database query. Next I validate each extracted line against the first pattern
/**
* Line extract
*
* #param string $file_line File line string
*
* #return array An array if matches
* Array (
* [0] => Matched line
* [1] => Date\Time subgroup (format >> d-M-y H:i:s)
* [2] => String flag subgroup
* [3] => Resource type subgroup (not used)
* [4] => Text string containing resource URL
* )
* , null otherwise
*
* #throws RegexException If malformed pattern
*/
private function extractMatches($file_line)
{
$extensions = array();
// build valid extensions subgroup
foreach ($this->valid_extensions as $extension) {
$extensions[] = sprintf("(?:\.%s)", $extension);
}
$matches = array();
// replace extensions placeholder
$pattern = str_replace('%extensions%', implode('|', $extensions), self::LINE_REGEX_PATTERN);
$is_valid = preg_match($pattern, $file_line, $matches);
if ($is_valid === false) {
throw new RegexException();
}
return $matches;
}
From the resulting array (if any) I fetch the 5th element (the one that stores the text with the URL in it), then I passed to two other functions, the first one to full URL extraction and the second one to finally extract the filename. See below:
/**
* Full URL extract
*
* #param string $text Text with URL in it
*
* #return string The URL, empty string otherwise
*
* #throws RegexException If malformed pattern
*/
private function extractUrl($text)
{
$match = array();
$is_valid = preg_match(self::FULL_URL_PATTERN, $text, $match);
if ($is_valid === false) {
throw new RegexException();
} elseif ($is_valid === 1) {
return $match[0];
}
return ''; // No URL found!
}
/**
* Filename extract
*
* #param string $url Resource URL (expects no GET parameters)
*
* #return string Resource filename (includes extension), empty string otherwise
*
* #throws RegexException If malformed pattern
*/
private function extractResourceNameFromUrl($url)
{
$extensions = array();
// build valid extensions subgroup
foreach ($this->valid_extensions as $extension) {
$extensions[] = sprintf("(?:\.%s)", $extension);
}
$matches = array();
// replace extensions placeholder
$pattern = str_replace('%extensions%', implode('|', $extensions), self::RESOURCE_REGEX_PATTERN);
$is_valid = preg_match($pattern, $url, $matches);
if ($is_valid === false) {
throw new RegexException();
} elseif ($is_valid === 1) {
return $matches[1];
}
return '';
}
Finally some where in my app I just did:
$parser = new Parser();
// fetch file line loop
$matches = $parser->extractMatches($file_line);
$url = $parser->extractUrl($matches[4]);
$filename = $parser->extractResourceNameFromUrl($matches[4]);
Hope helps somebody. Thanks!
my sniff doesn't work and doesn't recognize the property private $testvar. I want to make a Doc-Block mandatory there.
When I run code sniffer, the process method doesn't seem to be used. I added some echos there before.
Does the token T_PROPERTY exist? I cannot find it on php manual http://php.net/manual/en/tokens.php
Yet, in the squiz lab source code T_PROPERTY is used.
<?php
/**
* Extension for the pear class comment sniff.
*
*/
/**
* Extension for the pear class comment sniff.
*
*/
class XYZ_Sniffs_Commenting_PropertyCommentSniff implements PHP_CodeSniffer_Sniff
{
private $testvar = 1;
/**
* Returns an array of tokens this test wants to listen for.
*
* #return array
*/
public function register()
{
return array(T_PROPERTY);
}
/**
* Checks the property comments.
*
* #param PHP_CodeSniffer_File $phpcsFile the file object
* #param int $stackPtr the stack pointer
*
* #return void
*/
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$find = PHP_CodeSniffer_Tokens::$scopeModifiers;
$find[] = T_WHITESPACE;
$find[] = T_STATIC;
$commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG
&& $tokens[$commentEnd]['code'] !== T_COMMENT
) {
$phpcsFile->addError('Missing property doc comment', $stackPtr, 'Missing');
$phpcsFile->recordMetric($stackPtr, 'Function has property comment', 'no');
return;
} else {
$phpcsFile->recordMetric($stackPtr, 'Function has property comment', 'yes');
}
}
}
Thanks for your help :).
The T_PROPERTY token is only used when checking JavaScript files. It doesn't exist for PHP files.
For PHP files, you'll want to use the AbstractVariableSniff helper. Here is a sniff that checks comments of member vars: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php
Notice how it extends PHP_CodeSniffer_Standards_AbstractVariableSniff and then only implements the processMemberVar() method. It leaves the processVariable() and processVariableInString() methods empty because it doesn't care about regular variables inside the code.
Also note that if you are writing commenting sniffs, the comment parser is completely different in the 2.0 version (currently in beta but due to go stable any week now). Take a look at the new version of the above sniff here: https://github.com/squizlabs/PHP_CodeSniffer/blob/phpcs-fixer/CodeSniffer/Standards/Squiz/Sniffs/Commenting/VariableCommentSniff.php
I looked over and over and I couldn't seem to find an answer to what I want, but here goes:
I have a client that is really computer illiterate and I want to ensure that any errors that arise are handled without intervention. The one thing I'm stuck on currently though is the "The URI you submitted has disallowed characters." error.
I don't want to simply allow all the characters to circumvent the error. Instead what I'd like to do is simply redirect to a particular URI when this error happens. For example:
www.local.com/project/login/'''' ---> www.local.com/project/login
I looked at doing it with hooks, but I'd like to be able to specify a custom URL when I want to execute the check.
I was thinking about using a call to a library method which will pull in the characters from the configuration, then redirect based on whether the check passes or not, with the current URL and URI redirect as a parameter. Is this the way to do it, or is there an easier way to manage this?
I've read all the comments above - but I think you missed the easy way to do this.
Just overload the _filter_uri() function, and do whatever you want:
(Place this file in application/core/MY_URI.php)
// Normally this is not fully uppercase - but for some reason the URI filename is
Class MY_URI extends CI_URI
{
/**
* Filter segments for malicious characters
*
* #access private
* #param string
* #return string
*/
function _filter_uri($str)
{
if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE)
{
if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $str))
{
// DO SOMETHING HERE LIKE REDIRECT OR CHANGE THE URL
}
}
// Convert programatic characters to entities
$bad = array('$', '(', ')', '%28', '%29');
$good = array('$', '(', ')', '(', ')');
return str_replace($bad, $good, $str);
}
This is solution I am using on my project:
File: application/core/MY_URI.php
class MY_URI extends CI_URI {
/**
* Filter URI
*
* Filters segments for malicious characters.
*
* #param string $str
* #return void
*/
public function filter_uri(&$str)
{
if ( ! empty($str) && ! empty($this->_permitted_uri_chars) && ! preg_match('/^['.$this->_permitted_uri_chars.']+$/i'.(UTF8_ENABLED ? 'u' : ''), $str))
{
return preg_replace('~[^a-zA-Z 0-9%.:_\-,()]+~', '', $str);
}
}
}
I reuse a Config class (simplified code at the bottom) in a lot of my projects. It's generally instantiated once in my app bootstrap file and stored in a container class from which I inject it as a dependency into objects via static "makeThis()" method calls.
Because it's instantiated every time my apps are executed I want to streamline it as much as possible. Repeat: I'm looking to make this process as efficient as possible.
I have default config values specified in the Config::$defaults array at instantiation. What I want to optimize specifically is the assignment of properties inside the load_conf_environment() function. You'll find code comments inside this function further elucidating my requirements.
The code below should be clear enough (to anyone capable of providing a valid answer) to illustrate how the class works. I'm looking for an imaginative and efficient way to handle this operation and I'm not averse to a massive refactoring if you can support your claim.
Finally, let me pre-emptively rule out one suggestion: NO, I'm not interested in using constants for the directory path values. I used to do that but it makes unit testing difficult.
UPDATE:
I've considered using gettype() to determine the default
value's type and subsequently cast the passed value, but according to
the php docs this is not a good solution.
Currently I'm storing the different config directive names in arrays
by type and doing a check to see which array contains the directive
name and perform a typecast or assign values based on that. This seems "ugly" to me
and is what I'm trying to get away from if possible.
UPDATE2:
Having config values match the variable type of the defaults is the ideal situation, but I kind of just got it into my head that I wanted it that way ... it may not even really matter. Maybe it should just be incumbent upon the coder to provide valid configuration values? Anyone have any thoughts on that?
<?php
class Config
{
/**
* The base application directory in which all app_dirs reside
*/
protected $app_path;
/**
* Configuration directives
*
* #var array
* #access protected
*/
protected $vals = array();
/**
* List of default config directive values
*
* #var array
* #access protected
*/
protected $defaults;
/**
* Class constructor -- basically sets default values
*
* #param string $app_path Base application directory
*
* #return void
*/
public function __construct($app_path=NULL)
{
$this->defaults = array(
'debug' => FALSE,
'autoload' => FALSE,
'is_cli_app' => FALSE,
'is_web_app' => FALSE,
'smarty' => FALSE,
'phpar' => FALSE,
'front_url' => '',
'app_dir_bin' => 'bin',
'app_dir_conf' => 'conf',
'app_dir_docs' => 'docs',
'app_dir_controllers' => 'controllers',
'app_dir_lib' => 'lib',
'app_dir_models' => 'models',
'app_dir_test' => 'test',
'app_dir_vendors' => 'vendors',
'app_dir_views' => 'views',
'app_dir_webroot' => 'webroot',
);
if ($app_dir) {
$this->set_app_path($app_path);
}
}
/**
* Setter function for $app_path property
*
* #param string $app_path Path to the app on the server
*
* #return void
* #throws ConfigException On unreadable/nonexistent path
*/
public function set_app_path($app_path)
{
$app_path = dirname(realpath((string)$app_path));
if (is_readable($app_path)) {
$this->app_path = $app_path;
} else {
$msg = 'Specified app path directory could not be read';
throw new ConfigException($msg);
}
}
/**
* Set default values for uninitialized directives
*
* Called after $cfg file is read and applied to fill
* out any unspecified directives.
*
* #return void
*/
protected function set_defaults()
{
foreach ($this->defaults as $key => $val) {
if ( ! isset($this->vals[$key])) {
$this->vals[$key] = $this->is_app_dir($key)
? $this->app_dir . '/' . $this->defaults[$key]
: $this->defaults[$key];
}
}
}
/**
* (Re-)Loads configuration directives
*
* #param mixed $cfg Config array or directory path to config.php
*
* #throws ConfigException On invalid config array
* #return void
*/
public function load_conf_environment($cfg)
{
// Reset all configuration directives
$this->vals = array();
if ( ! is_array($cfg)) {
$cfg = $this->get_cfg_arr_from_file($cfg);
}
if (empty($cfg)) {
$msg = 'A valid $cfg array or environment path is required for ' .
'environment configuration';
throw new ConfigException($msg);
}
foreach ($cfg as $name => $val) {
// does a setter method exist for this directive?
$method = "set_$name";
if (method_exists($this, $method)) {
$this->$method($val);
continue;
}
/*
ASSIGN SPECIFIED DIRECTIVE VALUE
THE ASSIGNED VALUE SHOULD BE OF THE SAME TYPE SPECIFIED by $this->defaults
IF DIRECTIVE IS ONE OF THE 'app_dir' VARS:
- IF IT'S A VALID ABSOLUTE PATH, SET THAT AS THE VALUE
- OTHERWISE, app_dir PATHS SHOULD BE RELATIVE TO $this->app_path
I CONSIDERED USING gettype() TO DETERMINE THE DEFAULT VAR TYPE
AND THEN CAST THE VALUE, BUT THE DOCS SAY THIS IS A BAD IDEA.
*/
}
// set default vals for any directives that weren't specified
$this->set_defaults();
if ( ! $this->vals['is_cli_app'] && ! $this->vals['is_web_app']) {
$msg = 'App must be specified as either WEB or CLI';
throw new Rasmus\ConfigException($msg);
}
}
/**
* Load a configuration array from a specified file
*
* If no valid configuration file can be found,
* an empty array is returned. Valid config paths
*
* #param string $path Config environment directory path
*
* #return array List of config key=>value pairs
* #throws ConfigException On invalid environment path
*/
protected function get_cfg_arr_from_file($path)
{
$path = (string)$path;
$path = rtrim($path, DIRECTORY_SEPARATOR) . '/config.php';
if (is_readable($path)) {
require $path;
if (is_array($cfg) && ! empty($cfg)) {
return $cfg;
}
}
return array();
}
/**
* Has the specified config directive been loaded?
*
* #param string $directive Configuration directive name
*
* #return bool On whether or not a specific directive exists
*/
public function is_loaded($directive)
{
return isset($this->vals[$directive]);
}
/**
* Retrieves a key=>value list of config directives
*
* #return array List of configuration directives
*/
public function get_directives()
{
return $this->vals;
}
/**
* Magic method mapping object properties to $vals array
*
* #param string $name Object property name
*
* #return mixed
*/
public function __get($name)
{
if (isset($this->vals[$name])) {
return $this->vals[$name];
} else {
$msg = "Invalid property: $name is not a valid configuration directive";
throw new OutOfBoundsException($msg);
}
}
}
?>
Handling default values
Is there a reason why you set defaults for directives that need them after loading values from the config file?
Since the defaults are already hard-coded in the __construct, why not just move them to the $vals property and avoid needing to set them later on? This way, your load_conf_environment method overwrites the default value that already exists for the directive.
Validating types
This is something I would enforce with each directive's setter method. Each setter should verify the type of value with the is_* functions or by type-hinting in the setter arguments and throw exceptions accordingly. I would probably go so far as to require each directive have a setter. It's more work, but cleaner and 100% enforceable.
Handling app_dir directives (one option)
In the construct, if set_app_path is called, run through the vals property and call the set_app_dir_directive method (see below) on the app_dir directives so that they are prefixed with the app path.
Make a new set_app_dir_directive method:
public function set_app_dir_directive($key, $val)
{
$this->vals[$key] = $this->app_dir . '/' . $val;
}
// then...
public function load_conf_environment($cfg)
{
...
foreach ($cfg as $name => $val) {
$method = "set_$name";
if (method_exists($this, $method)) {
...
}
if ($this->is_app_dir_directive($name)) {
$this->set_app_dir_directive($name, $val);
continue;
}
// Continue storing vals.
...
}
}