This question already has answers here:
Search nested object for property
(4 answers)
Closed 2 years ago.
I couldn't find a question that was quite like mine, but if you can find one feel free to let me know..
I'm trying to figure out how to effectively create an neat configuration object.
I want the object (or a config manager of some sort) to be able to convert an array or INI file into a parent/child grouping of objects.
Eg:
$config_array = array (
'somecategory' => array ( 'key' => 'somevalue' ),
'anothercategory' => array ( 'key' => 'anothervalue', 'key2' => 'anothervalue2' ),
);
$config = new Configuration_Array( $config_array );
echo $config->somecategory->key; // prints: 'somevalue'
I have effectively done this with the following code:
class Configuration_Array extends Configuration
{
protected $_child_class = 'Configuration_Array';
protected $_objects = null;
protected $_config = null;
public function __construct( $config_array = null, $child_class = null ) {
if ( null !== $config_array ) {
$this->setConfig( $config_array );
}
if ( null !== $child_class ) {
$this->setChildClass( $child_class );
}
}
public function __get( $name ) {
$name = strtolower( $name );
if ( ! isset ( $this->_objects[ $name ] ) ) {
$this->_createObject( $name );
}
return $this->_objects[ $name ];
}
public function __isset( $name ) {
$name = strtolower( $name );
return ( ( isset ( $this->_objects[ $name ] ) ) or $this->_can_create_object( $name ) );
}
public function reset() {
$this->_objects = null;
}
public function toArray() {
if ( ! is_array ( $this->_config ) ) {
throw new Exception( 'No configuration has been set' );
}
return $this->_config;
}
public function setConfig( $config ) {
if ( null === $config ) {
return $this->reset();
}
if ( ! is_array ( $config ) ) {
throw new Exception( 'Configuration is not a valid array' );
}
$this->_config = $config;
}
public function loadConfig( $path ) {
if ( ! is_string ( $path ) ) {
throw new Exception( 'Configuration Path "' . $path . '" is not a valid string' );
}
if ( ! is_readable ( $path ) ) {
throw new Exception( 'Configuration file "' . $path . '" is not readable' );
}
$this->setConfig( include( $path ) );
}
public function setChildClass( $class_name ) {
if ( ! is_string ( $class_name ) ) {
throw new Exception( 'Configuration Child Class is not a valid string' );
}
if ( ! class_exists ( $class_name ) ) {
throw new Exception( 'Configuration Child Class does not exist' );
}
$this->_child_class = $class_name;
}
public function getChildClass() {
if ( ! isset ( $this->_child_class ) ) {
throw new Exception( 'Configuration Child Class has not been set' );
}
return $this->_child_class;
}
protected function _createObject( $name ) {
$name = strtolower( $name );
if ( ! isset ( $this->_config[ $name ] ) ) {
throw new Exception( 'No configuration has been set for object "' . $name . '"' );
}
$child_class = $this->getChildClass();
if ( is_array ( $this->_config[ $name ] ) ) {
$child = new $child_class( $this->_config[ $name ], $child_class );
} else {
$child = $this->_config[ $name ];
}
return ( $this->_objects[ $name ] = $child );
}
protected function _can_create_object( $name ) {
$name = strtolower( $name );
return isset ( $this->_config[ $name ] );
}
}
The Problem
Most of this works perfectly, but I am having some trouble figuring out how I can use isset effectively. With property chaining, isset only works on the last value in the chain, eg:
if ( isset ( $config->somecategory->key ) ) {
Which uses the object returned by $config->somecategory and checks whether it holds an object called 'key'
This means that if $config->somecategory doesn't exist, an exception is thrown. The user would have to do this to check effectively:
if ( isset ( $config->somecategory ) and isset ( $config->somecategory->key ) ) {
But that seems quite annoying.
An array on the other hand doesn't need to be checked at each level; PHP can check the entire thing:
if ( isset ( $config[ 'somecategory' ][ 'key' ] ) ) { // No error/exception
What I'm looking for is a way to implement my class so I can treat my objects sort of the same way I'd treat an array:
if ( isset ( $config->somecategory->key ) ) {
In a way that wouldn't throw an exception if 'somecategory' doesn't exist...
Ideas?
Since PHP 7 it's possible to use a not well documented feature of null coalesce operator for this purpose.
$config_array = [
'somecategory' => [ 'key' => 'somevalue' ],
'anothercategory' => [ 'key' => 'anothervalue', 'key2' => 'anothervalue2' ],
];
// Quickly convert to object
$json = json_encode($config_array);
$config = json_decode($json);
echo $config->somecategory->key ?? null; // prints: 'somevalue'
echo $config->somecategory->missing_key ?? null; // no errors but also doesn't print anything
echo $config->somecategory->missing_key->go->crazy->with->chains ?? null; // no errors but also doesn't print anything
Here is an online example in action
Unfortunately there is not version of isset which checks your property chain correctly. Even writing your own method will not help as passing the chain as parameter to your method already fails if somecategory is already unset.
You can implement the magic method for accessing unset properties (maybe in a base class common to your config objects). This will create a dummy object of class UnsetProperty and return that.
Your class to $config->someCategory->key will deliver a UnsetProperty for $config->someCategory. This object will also delivery a new UnsetProperty for $obj->key. If you implement a method IsSet() in UnsetProperty returning false and in other properties returning true you can simplyfy your check to:
if($config->someCategory->key->IsSet()) ...
This will need a lot of to do so I am not sure if you do not like to go with the chained isset-calls.
if((isset($config->someCategory)) and (isset($config->someCategory->key))) ...
Depends on style and how many nested levels you have.
Hope you get the idea behind the possibility.
Take a look at Zend_Config. It operates almost exactly as you describe.
You could use it directly or simply as an instructional guide to build your own.
Maybe something like this?
The only problem is, you would have to call isEmpty to check if a configuration is given, and get to get the final value. (Like can be seen in the 3 test cases at the bottom)
<?php
// set debug
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');
class Config
{
protected $data;
public function __construct($data = null) {
$this->data = $data;
}
public function __get($name) {
return isset($this->data[$name]) ? new Config($this->data[$name]) : new Config();
}
public function isEmpty() {
return empty($this->data);
}
public function get() {
return $this->data;
}
}
$test = new Config(array(
'foo' => array(
'bar' => array(
'barfoo' => 1
)
)
));
// test 1
if (!$test->foo->bar->isEmpty()) {
print_r($test->foo->bar->get());
}
// test 2
if (!$test->foo->bar->foobar->isEmpty()) {
print_r($test->foo->bar->foobar->get());
}
// test 3
if (!$test->foo->bar->barfoo->isEmpty()) {
print_r($test->foo->bar->barfoo->get());
}
Example:
http://codepad.org/9EZ2Hqf8
Related
I need to reference an object property with a variable like this:
$user = User::find( 1 );
$mobile = $user->getData( 'phone.mobile' );
The value of the $data property in the object is a jSON array. Right now my user class looks like this:
class User extends Authenticable {
protected $fillable = [
'email',
'password',
'data',
'token',
];
protected $casts = [
'data' => 'array',
];
public function getData( $key = null ){
if( $key == null ){
// Return the entire data array if no key given
return $this->data;
}
else{
$arr_string = 'data';
$arr_key = explode( '.', $key );
foreach( $arr_key as $i => $index ){
$arr_string = $arr_string . "['" . $index . "']";
}
if( isset( $this->$arr_string ) ){
return $this->$arr_string;
}
}
return '';
}
}
The code above, always returns '', but $this->data['phone']['mobile'] returns the actual value stored in the database.
I guess I am referencing the key in a wrong way, can someone point me to the right way to access the value, given the string 'phone.mobile'
Laravel actually has a built-in helper function for the exact thing you're trying to do called array_get:
public function getData( $key = null )
{
if ($key === null) {
return $this->data;
}
return array_get($this->data, $key);
}
See documentation for more information: https://laravel.com/docs/5.5/helpers#method-array-get
I've been working on an immutable object for use in PHP. One can do this by adapting a singleton pattern and playing around with the magic methods easily enough, but I have a couple of specific needs here.
1. Under no circumstances is a reference to a contained value to be returned.
2. If the contained data is an array (most likely a big 'ol tree loaded form json_decode()), then one must be able to search for a key using a search string
e.g.:
$thing = $foo->get( 'go.get.the.thingy.at.this.level' );
...which would return:
$foo->_data['go']['get']['the']['thingy']['at']['this']['level']
3. When walking down a contained array looking for keys, references are used -- and not copies.
This means that I will want to avoid the use of a foreach() call if it at all possible. If you don't understand what I mean (or understand why this is a concern), in PHP:
foreach() does not work on the object you are iterating.
foreach() works on a copy of the object being iterated.
My Question
I've got something of a shell here (minor syntax problems aside), but I'd like to see if this can be made any more efficient, or if there are better tricks at accomplishing the same task
class ProperlyProtectedObject {
// PUBLIC STUFF
/// Initializes the singelton and returns a reference -- or cowardly returns a null.
public static function init( $value ) {
if( true === empty( self::$_self ) ) { // empty returns true if 0, false, null, or not set
if( false === isset( $value ) ) { // isset returns true if not set or null.
return null; // ...you idjit!
}
self::$_data = new ProperlyProtectedObject( $value );
}
return & self::$_self;
}
/// invoke ( php > =5.3 ): default
public function __invoke( $var ) {
return $this->get( $var );
}
public function __call( $f, $args ) {
return $this->get( $args );
}
public function __callStatic( $f, $args ) {
return null; // idjit.
}
/// get: NEAT TRICK:
/// if you know that you are storing an array here, then you can pass a dot syntax
/// string (e.g.: foo.bar.soemthing_else.foo.0 ) and
/// -- provided that your keys don't contain dots (:-P) --you can get the value of a
/// nested array key!
/// \return <Mixed> Any value **except** NULL is the value at the depth you are looking for.
/// \return NULL this means that you.have.a.BAD.Key.string.SomeWhere
public function get( $val ) {
$copyOfThing = null;
if( true === is_array( self::$_data ) ) {
if( true === is_string( $val ) ) {
$keys = explode( '.', $val );
if( 0 < count( $keys ) {
$copyOfThing = walk( self::$_data, $keys );
}
}
}
else {
$copyOfThing = self::$_data;
}
return $copyOfThing;
}
/// set: DOES NOTHING. This object is meant to take an act of congress to delete data.
public function __set( $objProp, $value ) {
// do nothing.
// echo '<pre>DIE IN A FIRE!!!! ' . var_export( $objProp, true ) . '</pre>';
}
/// isset:
public function __isset( $objProp ) {
return ( null !== $this->get( $objProp ) ) ? true : false;
}
/// unset: DOES NOTHING.This object is meant to take an act of congress to delete data
public function __unset( $objProp ) {
// do nothing.
// echo '<pre>DIE IN A FIRE!!!! ' . var_export( $objProp, true ) . '</pre>';
}
//
// PRIVATE STUFF
//
private $_self; ///< the singleton instance
private $_data = Array(); ///< this is the data. Just the data, and only the data.
/// CTOR: Singleton!
private function __construct( $value ) {
self::$_data = $value;
}
/// CCTOR: Singletons have have no need to copy themselves
private function __clone() {
// do nothing
}
/// fetches the value from WAAAY down in the array
private function walk( Array &$arr, Array $keystack ) {
$key = array_pop( $keystack );
if( false === array_key_exists( $key, $arr ) ) {
return null;
}
if( 0 count( $keystack ) ) {
return $arr[$key];
}
else {
return walk( $arr[$key], $keystack );
}
}
}
I've been searching the database of questions on the forum, but can not get a solution that is oriented to my question.
I come to you, because I doubt has arisen with an autoloader that I'm developing.
The point is this, when I try to instantiate a class defined by me the autoloader works fine, but when I try to instantiate a class of the PHP core throws me the following error.
For example, when I try to do this:
$conn = new PDO (...);
Then throws me the following error:
Fatal error: Class 'PDO' not found in ...
I've noticed that the autoloader is trying to load the class from which I have defined routes to my classes.
My question is, how I can do to load a class of the PHP core if I'm using a custom autoloader?
I hope you can guide me to solve this problem that I have been presented.
In advance thank you very much.
Excuse me for not placing the code of custom autoloader, I missed.
The code is the same used in symfony but modified and simplified.
class ClassLoader {
private $prefixes = array ();
private $fallbackDirs = array ();
private $useIncludePath = false;
public function getPrefixes () {
return $this->prefixes;
}
public function getFallbackDirs () {
return $this->fallbackDirs;
}
public function addPrefixes ( array $prefixes ) {
foreach ( $prefixes as $prefix => $path ) {
$this->addPrefix ( $prefix, $path );
}
}
public function addPrefix ( $prefix, $paths ) {
if ( !$prefix ) {
foreach ( ( array ) $paths as $path ) {
$this->fallbackDirs [] = $path;
}
return;
}
if ( isset ( $this->prefixes [ $prefix ] ) ) {
$this->prefixes [ $prefix ] = array_merge ( $this->prefixes [ $prefix ], ( array ) $paths );
} else {
$this->prefixes [ $prefix ] = ( array ) $paths;
}
}
public function setUseIncludePath ( $useIncludePath ) {
$this->useIncludePath = $useIncludePath;
}
public function getUseIncludePath () {
return $this->useIncludePath;
}
public function register ( $prepend = false ) {
spl_autoload_register ( array ( $this, 'loadClass' ) , true, $prepend );
}
public function unregister () {
spl_autoload_unregister ( array ( $this, 'loadClass' ) );
}
public function loadClass ( $class ) {
if ( $file = $this->findFile ( $class ) ) {
require $file;
return true;
}
}
public function findFile ( $class ) {
if ( '\\' == $class [ 0 ] ) {
$class = substr ( $class, 1 );
}
if ( false !== $pos = strrpos ( $class, '\\' ) ) {
// namespaced class name
$classPath = str_replace ( '\\', DIRECTORY_SEPARATOR, substr ( $class, 0, $pos ) ) . DIRECTORY_SEPARATOR;
$className = substr ( $class, $pos + 1 );
} else {
// PEAR-like class name
$classPath = null;
$className = $class;
}
$classPath .= str_replace ( '_', DIRECTORY_SEPARATOR, $className ) . '.php';
foreach ( $this->prefixes as $prefix => $dirs ) {
if ( 0 === strpos ( $class, $prefix ) ) {
foreach ( $dirs as $dir ) {
if ( file_exists ( $dir . DIRECTORY_SEPARATOR . $classPath ) ) {
return $dir . DIRECTORY_SEPARATOR . $classPath;
}
}
}
}
foreach ( $this->fallbackDirs as $dir ) {
if ( file_exists ( $dir . DIRECTORY_SEPARATOR . $classPath ) ) {
return $dir . DIRECTORY_SEPARATOR . $classPath;
}
}
if ( $this->useIncludePath && $file = stream_resolve_include_path ( $classPath ) ) {
return $file;
}
}
}
But if you want to see the original file is here
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/ClassLoader/ClassLoader.php
Well my friends.
I got the reason why I did not load the PDO class of the PHP core.
According to my research, I do one of two things:
Or put a backslash at the time of the class instances
$conn = new \PDO (...);
Or put the PDO class in the use clause.
use api\ecobd\nucleo\Conexion, PDO;
I commented that I chose the second because the first did not work.
Thanks anyway for the help given, served me well to better target my search to solve my problem :)
Check if PDO extension is loaded and PDO works without registering your auto loader. Auto loader should not effect core classes and you don't need an auto loader to load core classes.
I need a php validator class that validates user inputs.
I want it to be able to accept an assoc array of fields => values like:
array(
"username" => "Alex",
"email_address" => "###3423£alex#my.mail.com"
);
and then return an array of errors like this:
array(
"username" => "",
"email_address" => "Invalid Email Address"
);
But I'm really struggling on HOW the hell I'm going to do this!
I've read countless pages on PHP validators and read that the best way to do this is with the strategy pattern. But i dont know how??
Like... This is what I've got so far:
class Validator {
private
$_errors,
$_fields,
static private $_map = array (
"firstname" => "name",
"surname" => "name",
"agency_name" => "name",
"agency_office" => "name",
"username" => "username",
"email_address" => "email_address",
);
public function __construct( array $fields ) {
$this->_fields = $fields;
}
public function validate() {
foreach ( $this->_fields as $field => $value ) {
if ( method_exists( __CLASS__, self::$_map[$field] ) ) {
if ( in_array( $field, self::$_map ) ) {
$this->{self::$_map[$field]}( $field, $value );
}
}
else {
die( " Unable to validate field $field" );
}
}
}
public function get_errors() {
return $this->_errors;
}
private function name( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z]{2,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 2 to 50 alphanumerical characters";
}
}
private function username( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9_\-]{10,50}$/", $value ) ) {
$this->errors[$field] = "Invalid. Must be 10 to 50 characters. Can contain digits, characters, _ (underscore) and - (hyphen)";
}
}
private function password( $field, $value ) {
if ( !preg_match( "/^[a-zA-Z0-9\.\-]{8,30}$/", $value ) ) {
$this->_errors[$field] = "Invalid. Must be 8 to 30 characters. Can contain digits, characters, . (full stop) and - (hyphen)";
}
}
private function email_address( $field, $value ) {
if ( !filter_var( $value, FILTER_VALIDATE_EMAIL ) ) {
$this->_errors[$field] = "Invalid Email Address";
}
}
}
The problems with this is, it doesn't even consider database connections for like, already registered usernames,
Also is doesn't match passwords
I've just got coders block at the moment and its destroying me on the inside :(
Can anybody give a an explaination of the classes required and functions each class will need to do?
I really need the inputs and outputs to be in the format already explained though!
Thankyou Very Much Internet People!
As a part of the my MVC I have solved the same problem. I could give you a listing, but in a few lines try to describe how.
I got 3 base classes Form, Validator, Field, each of object of this classes configuring through one YAML file, structured somehow like this:
name: // field name
i18n: [ ru, en ] // is the field i18n
field:
class: Base // class to use for field
options: { specific_save: true } // options from available (defined in class)
attributes: { } // attributes, for HTML rendering
validator:
class: String // Class to validate with
options: { required: true, max: 100 } // options for validator
So, lets start with Form, when object is constructing the form takes the YAML file described above, and due to that configuration creates fields. Something like this:
// Imlement this function to configure form;
foreach ($this->_config as $f => $c)
{
$class = '\\Lighty\\Form\\Field\\' . (isset($c['field']['class']) && $c['field']['class'] ? $c['field']['class'] : 'Base');
$o = isset($c['field']['options']) && is_array($c['field']['options']) ? $c['field']['options'] : array();
$a = isset($c['field']['attributes']) && is_array($c['field']['attributes']) ? $c['field']['attributes'] : array();
$field = new $class($this, $o, $a);
$field->setName($f);
$class = '\\Lighty\\Form\\Validator\\' . (isset($c['validator']['class']) && $c['validator']['class'] ? $c['validator']['class'] : 'Base');
$o = isset($c['validator']['options']) && is_array($c['validator']['options']) ? $c['validator']['options'] : array();
$m = isset($c['validator']['messages']) && is_array($c['validator']['messages']) ? $c['validator']['messages'] : array();
$field->setValidator($validator = new $class($field, $o, $m));
if (isset($this->_options['default'][$f]))
{
$field->setValue($this->_options['default'][$f]);
}
if (isset($c['i18n']))
{
if (is_array($c['i18n']))
{
$field->setCultures($c['i18n']);
}
$field->setI18n((bool) $c['i18n']);
}
$this->addField($field);
So, now we have form with fields and validator for each field, then to validate I use this mechanism:
Form goes through each field, calling validate() method,
Field (got the binded value) call validate($value) method of binded Validator, passing the stored value. Inside this method Validator calls the validateOption() method, in which there is a simple switch for each options, for example:
switch ($o)
{
case 'required':
$valid = $state && trim($value) != '' || !$state;
break;
default:
return \warning(sprintf('Undefined validator option "%s" in %s validator class', $o, get_class($this->getField()->getValidator())), null);
}
Here you can see validating on required option. If I need more validators, I extend class of the Base validator, defined few more options, and redefine validateOption(), where in default statement of the option's switch put parent::validateOption(). So specified options validates in new class, and old one in base validator Class.
If there any questions... You're welcome.
How can i check to see if a static class has been declared?
ex
Given the class
class bob {
function yippie() {
echo "skippie";
}
}
later in code how do i check:
if(is_a_valid_static_object(bob)) {
bob::yippie();
}
so i don't get:
Fatal error: Class 'bob' not found in file.php on line 3
You can also check for existence of a specific method, even without instantiating the class
echo method_exists( bob, 'yippie' ) ? 'yes' : 'no';
If you want to go one step further and verify that "yippie" is actually static, use the Reflection API (PHP5 only)
try {
$method = new ReflectionMethod( 'bob::yippie' );
if ( $method->isStatic() )
{
// verified that bob::yippie is defined AND static, proceed
}
}
catch ( ReflectionException $e )
{
// method does not exist
echo $e->getMessage();
}
or, you could combine the two approaches
if ( method_exists( bob, 'yippie' ) )
{
$method = new ReflectionMethod( 'bob::yippie' );
if ( $method->isStatic() )
{
// verified that bob::yippie is defined AND static, proceed
}
}
bool class_exists( string $class_name [, bool $autoload ])
This function checks whether or not the given class has been defined.