I've been looking for a solution to my problem but every case seems to differ from mine. I'm attempting to import a CSV file using PHP.
This is the Error I receive:
Fatal error: Call to undefined method Teacher::email_teacher() in C:\wamp\www\Projet\models\Db.class.php on line 66
This is from index.php. Thanks for your help in advance.
<?php
session_start();
define('VIEW_PATH','views/');
define('CONT_PATH','controllers/');
$csvfileTeacher = 'models/professeurs.csv';
function uploadClass($classe) {
require 'models/' . $classe . '.class.php';
}
spl_autoload_register('uploadClass');
function getTeacher($csvfileTeacher) {
$teachers = array ();
if (file_exists ( $csvfileTeacher )) {
$fcontents = file ( $csvfileTeacher );
$i = count ( $fcontents ) - 1;
for($index = 1; $index <= $i; $index ++) {
$icontent = $fcontents [$index];
preg_match ( '/^(.*);(.*);(.*);(.*)/', $icontent, $result );
$teachers [$index] = new teacher ( $result [1], $result [2], $result [3], $result [4] );
}
}
return $teachers;
}
if (file_exists ( $csvfileTeacher )) {
$teachers = getTeacher ( $csvfileTeacher );
foreach ( $teachers as $teacher ) {
Db::getInstance ()->insert_teacher ( $teacher ); ///here is where the problem seems to be
}
}
Here is the function insert_teacher() from Db.class.php:
public function insert_teacher($teacher) {
$query = 'INSERT INTO teachers VALUES ( ' . $this->_db->quote ( $teacher->email_teacher () ) . ',' . $this->_db->quote ( $teacher->firstname_teacher () ) . ',' . $this->_db->quote ( $teacher->lastname_teacher () ) . ',' . $this->_db->quote ( $teacher->supervisor () ) .')';
$this->_db->prepare ( $query )->execute ();
}
This is the teacher class:
<?php
class Teacher{
private $_email_teacher;
private $_firstname_teacher;
private $_lastname_teacher;
private $_supervisor;
public function __construct($email_teacher,$firstname_teacher,$lastname_teacher,$supervisor){
$this->_email_teacher=$email_teacher;
$this->_firstname_teacher=$firstname_teacher;
$this->_lastname_teacher=$lastname_teacher;
$this->_supervisor=$supervisor;
}
public function email(){
return $this->_email_teacher;
}
public function firstname(){
return $this->_firstname_teacher;
}
public function lastname(){
return $this->_lastname_teacher;
}
public function supervisor(){
return $this->_supervisor;
}
}
?>
Based on the method definitions in the teacher class, it appears that the problem is that you are not using the correct names for the methods.
$teacher->email_teacher ()
needs to be
$teacher->email()
The email() method returns the property _email_teacher.
Methods like email() are referred to as accessors. They are used to access private properties of an object. The leading underscore in _email_teacher is a commonly used naming convention to indicate that a property is private.
Related
I have the following Phql query:
$persons = Person::query()
->columns([
'id' => 'id',
'name' => 'first_name || last_name'
])
->where("first_name LIKE :searchQuery:")
->orWhere("last_name LIKE :searchQuery:")
->bind(['searchQuery' => $searchQuery . '%'])
->execute();
The database used is an sqlite database.
The query works fine until I include the concatenation operator of sqlite ||
The exception thrown is:
Scanner: Unknown opcode 402
I have tried CONCAT(first_name, last_name), but it throws the exception:
SQLSTATE[HY000]: General error: 1 no such function: CONCAT
Also much better is to use dialect extension:
namespace App/Dialect;
class Sqlite extends \Phalcon\Db\Dialect\Sqlite
{
/**
* Sqlite constructor.
*/
public function __construct()
{
$this->registerCustomFunctions();
}
/**
* Register Custom dialect functions
*/
public function registerCustomFunctions()
{
$customFunctions = [
'CONCAT_WS' => 'ConcatWs',
];
foreach ($customFunctions as $key => $value) {
$className = 'App\\Dialect\\Extensions\\'.$value;
$object = new $className;
$this->registerCustomFunction($key, $object->getFunction());
}
}
}
namespace App/Dialect/Extensions;
class ConcatWs
{
public function getFunction()
{
return function (Dialect $dialect, $expression) {
$sql = '';
$count = count($expression['arguments']);
if (true !== $count >= 2) {
throw new Exception('CONCAT_WS requires 2 or more parameters');
}
if (2 === $count) {
return $this->getSqlExpression($expression['arguments'][1]);
}
$separator = array_shift($expression['arguments']);
--$count;
foreach ($expression['arguments'] as $argument) {
$sql .= $this->getSqlExpression($argument);
if (0 !== --$count) {
$sql .= ' || '.$this->getSqlExpression($separator).' || ';
}
}
return $sql;
};
}
}
And then in your db service:
use App/Dialect/Sqlite as SqliteDialect;
$di->set('db', function() {
return new Sqlite([
// other options, like password etc
'dialectClass' => SqliteDialect::class
]);
});
Sadly this is not supported for sqllite, but you can extend the class and add support for MySQL concat function. Here is a working solution: https://forum.phalconphp.com/discussion/15233/concatenate-columns-using-sqlite
MySQL solution:
->columns([
'id',
'CONCAT_WS("#", id, created_at) AS concatenatedValue',
])
Result
Array
(
[0] => Array
(
[id] => 1
[concatenatedValue] => 1#2017-04-04 12:08:52
I need to access multiple arrays, the problem lies when I get to the arrays I need like below, I can't access it traditionally because the key will be different every time.
I'm dealing with the following array:
Array
(
[oe_schedule_charge] => Array
(
[617cdb2797153d6fbb03536d429a525b] => Array
(
[schedule] =>
[args] => Array
(
[0] => Array
(
[id] => cus_2OPctP95LW8smv
[amount] => 12
)
)
)
)
)
There are going to be hundreds of these arrays and I need a way to efficiently access the data within them. I'm using the following code with expected output:
function printValuesByKey($array, $key) {
if (!is_array($array)) return;
if (isset($array[$key]))
echo $key .': '. $array[$key] .'<br>';
else
foreach ($array as $v)
printValuesByKey($v, $key);
}
$cron = _get_cron_array();
foreach( $cron as $time => $hook ) {
if (array_key_exists('oe_schedule_charge', $hook)) {
echo '<div>';
echo date('D F d Y', $time);
echo printValuesByKey($hook, 'amount');
echo printValuesByKey($hook, 'id');
echo '</div>';
}
}
But I've never had to deal with this much data, so I would like to take the proper precautions. Any light that can be shed on accessing a multidimensional array like this in an efficient way would be greatly appreciated.
I would consider loading it into an object, then writing member functions to get what you want.
class myclass {
private $_uniqueKey;
private $_schedule;
private $_args = array();
private $_amount = array();
private $_id = array();
public function __construct($arrayThing)
{
foreach($arrayThing['oe_schedule_charge'] as $uniqueKey => $dataArray)
{
$this->_uniqueKey = $uniqueKey;
$this->_schedule = $dataArray['schedule'];
$this->_args = $dataArray['args'];
}
$this->_afterConstruct();
}
private function _afterConstruct()
{
foreach($this->_args as $argItem)
{
if(isset($argItem['amount']) && isset($argItem['id']))
{
$this->_amount[] = $argItem['amount'];
$this->_id[] = $argItem['id'];
}
}
}
public function getUniqueKey()
{
return $this->_uniqueKey;
}
public function getSchedule()
{
return $this->_schedule;
}
public function getArgs()
{
return $this->_args;
}
public function printShitOut($time)
{
//You define this. But if you do a print_r( on the object, it will tell you all the items you need. )
}
//code would be like this:
$cron = _get_cron_array();
foreach( $cron as $time => $hook )
{
$obj = new myclass($hook);
$obj->printShitOut($time);
}
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.
Im new to php and have programmed in other languages. Im trying to solve a certain programming situation: Basically I need to access strings stored inside an object. The internal data structure of the object is an associative array. The values are the strings Im trying to access.
This is the code Im using:
<?php
class OrderAuthenticator
{
private $OrderObj;
public function __construct($Order)
{
echo 'Contructed an instance of Order Authenticator<br/>';
$this->OrderObj = $Order;
echo 'Instantiated OrderContainer<br/>';
}
public function authenticate_Drinks()
{
//echo __LINE__ ;
//4 number or characters including spaces between them
$pattern_drinkName = '([0-9a-zA-Z\s]{1,75})';
//100 characters with spaces allowed between them
$pattern_drinkCalories = '([0-9]{0,3})';
//100 characters with spaces allowed between them
$pattern_drinkCategory = '([0-9A-Za-z\s]{1,50})';
//100 characters with spaces allowed between them
$pattern_drinkDescription = '([0-9A-Za-z\s]{0,300})';
$pattern_drinkPrice = '([0-9.]{1,6})';
//echo __LINE__ ;
$DrinkContainer = $this->OrderObj->getDrinkContainer();
//echo __LINE__ ;
foreach($DrinkContainer as $Drink)
{
//print_r($Drink);
echo __LINE__ ;
echo '<br/>';
}
}
?>
This code produces the following output:
Array (
[0] => Drink Object (
[dataArray:private] => Array (
[drink_name] => SimpleXMLElement Object ( [0] => Gin )
[drink_cals] => SimpleXMLElement Object ( )
[drink_Category] => SimpleXMLElement Object ( [0] => Alocholic )
[drink_desc] => SimpleXMLElement Object ( )
[drink_price] => SimpleXMLElement Object ( [0] => 4.00 )
)
)
)
Now, what I need to do is take the string values out and I need to run a regular expression check on each of those. So I need to store each of these strings in a variable in some kind of a loop.
I had this code trying to do that within the above loop but it didnt work:
$drink_name = $Drink->getName();
echo 'drink name = '.$drink_name.'<br/>';
$drink_calories = $Drink->getCalories();
echo 'drink calories = '.$drink_calories.'<br/>';
$drink_category = $Drink->getCategory();
echo 'drink category = '.$drink_category.'<br/>';
$drink_Description = $Drink->getDescription();
echo 'drink Description = '.$drink_Description.'<br/>';
$Drink_Price = $Drink->getPrice();
echo 'drink Price = '.$Drink_Price.'<br/>';
if(!preg_match($pattern_drinkName, $drink_name))
{
echo __LINE__ ;
return 'Drink name'.$drink_name .' did not match<br/>';
}
else if(!preg_match($pattern_drinkCalories, $drink_calories))
{
echo __LINE__ ;
return 'Drink calories'.$drink_calories .' did not match<br/>';
}
else if(!preg_match($pattern_drinkCategory, $drink_category))
{
echo __LINE__ ;
return 'Drink category'.$drink_category .' did not match<br/>';
}
else if(!preg_match($pattern_drinkDescription, $drink_Description))
{
echo __LINE__ ;
return 'Drink Description'.$drink_Description .' did not match<br/>';
}
else if(!preg_match($pattern_drinkPrice, $Drink_Price))
{
echo __LINE__ ;
return 'Drink Price'.$Drink_Price .' did not match<br/>';
}
else
{
echo __LINE__ ;
return 'Merchant Location input is valid<br/>';
}
Here is the Drink class :
<?php
class Drink
{
private $dataArray;// = array();
public function __construct()
{
echo 'Entered constructor for Drink.php<br/>';
$this->dataArray = array();
}
public function setName($drink_Name)
{
echo 'Added Drink Name to DrinkObj= '.$drink_Name. '<br/>';
$this->dataArray["drink_name"] = $drink_Name;
}
public function getName()
{
echo 'Inside Drink name<br/>';
return $this->dataArray["drink_name"];
}
public function setCalories($drink_Cals)
{
echo 'Added Drink Calories to DrinkObj= '.$drink_Cals. '<br/>';
$this->dataArray["drink_cals"] = $drink_Cals;
}
public function getCalories()
{
return $this->dataArray["drink_cals"];
}
public function setCategory($drink_Category)
{
echo 'Added Drink Category to DrinkObj= '.$drink_Category. '<br/>';
$this->dataArray["drink_Category"] = $drink_Category;
}
public function getCategory()
{
return $this->dataArray["drink_Category"];
}
public function setDescription($drink_Desc)
{
echo 'Added Drink Description to DrinkObj= '.$drink_Desc. '<br/>';
$this->dataArray["drink_desc"] = $drink_Desc;
}
public function getDescription()
{
return $this->dataArray["drink_desc"];
}
public function setPrice($drink_Price)
{
echo 'Added Drink Price to DrinkObj= '.$drink_Price. '<br/>';
$this->dataArray["drink_price"] = $drink_Price;
}
public function getPrice()
{
return $this->dataArray["drink_price"];
}
}
?>
$patterns = array(
'name' => '([0-9a-zA-Z\s]{1,75})',
'calories' => '([0-9]{0,3})',
'category' => '([0-9A-Za-z\s]{1,50})',
'description' => '([0-9A-Za-z\s]{0,300})',
'price' => '([0-9.]{1,6})'
);
$DrinkContainer = $this->OrderObj->getDrinkContainer();
foreach($DrinkContainer as $Drinks)
{
foreach($Drinks as $DrinkObject)
{
$properties = array(
'name' => $DrinkObject->getName(),
'calories' => $DrinkObject->getCalories(),
'category' => $DrinkObject->getCategory(),
'description' => $DrinkObject->getDescription(),
'price' => $DrinkObject->getPrice()
);
foreach($properties as $propname => $propvalue)
{
if(!preg_match($patterns[$propname], $propvalue))
{
return "Drink $propname $propvalue did not match<br/>";
}
}
}
}
In addition to using foreach, as Matt shows, Drink can implement the Iterator or IteratorAggregate interfaces so you can iterate over drinks directly, rather than having to create a second array. It could be as simple as using ArrayIterator to wrap the data array:
<?php
class Drink implements IteratorAggregate {
function getIterator() {
return new ArrayIterator($this->dataArray);
}
#...
or you could write a class to :
<?php
class DataIterator implements Iterator {
protected $data, $idx, $key, $fields;
function __construct($data, $fields = null) {
$this->data = $data;
if ($fields) {
$this->fields = $fields;
} else if (method_exists($data, 'fields')) {
$this->fields = $data->fields();
} else {
throw new InvalidArgumentException(__CLASS__ . ' expects ' . get_class($data) . " to have a 'fields' method, but it doesn't.");
}
}
/*** Iterator ***/
function current() {
return $this->data->{$this->key};
}
function key() {
return $this->key;
}
function next() {
if (++$this->idx < count($this->fields)) {
$this->key = $this->fields[$this->idx];
} else {
$this->key = null;
}
}
function rewind() {
$this->key = $this->fields[$this->idx = 0];
}
function valid() {
return ! is_null($this->key);
}
}
class Drink implements IteratorAggregate {
private $dataArray = array(
'drink_name' => null, 'drink_cals' => null,
'drink_Category' => null, 'drink_desc' => null,
'drink_price' => null
);
function __get($name) {
$method = 'get' . ucfirst($name);
if (method_exists($this, $method)) {
return $this->$method();
}
# else return value is undefined. Could also throw an exception.
}
function __set($name, $val) {
$method = 'set' . ucfirst($name);
if (method_exists($this, $method)) {
return $this->$method($val);
}
# could throw and exception if $name isn't an accessible property.
}
/* Helps to describe Drinks by returning an array of property names.
*/
function fields() {
return array_keys($this->dataArray);
}
function getIterator() {
return new DataIterator($this);
}
# ...
}
#...
$patterns = array(
'name' => '(^[0-9a-zA-Z\s]{1,75}$)',
'calories' => '(^[0-9]{0,3}$)',
'category' => '(^[0-9A-Za-z\s]{1,50}$)',
'description' => '(^[0-9A-Za-z\s]{0,300}$)',
'price' => '(^[0-9.]{1,6}$)'
);
foreach($drinks as $i => $drink) {
foreach($drink as $propname => $propvalue) {
if(!preg_match($patterns[$propname], $propvalue)) {
return "Drink $i's $propname ('$propvalue') is invalid.";
# or:
//$errors[$i][$propname] = "'$propvalue' is invalid";
}
}
}
Property overloading (__get, __set) isn't necessary for iteration, but does allow for write access within a foreach loop using variable property names (e.g. $drink->$name). Variable property names should be used sparingly as they can obfuscate what property is being accessed, but it's acceptable in a foreach loop because it's clear that every accessible property is being accessed.
You could move validation to the set* methods, throwing exceptions on failure, at which point there would be no need for a validation step.
Notes: <br/> isn't semantic. Often, it should be replaced with paragraph (<p>) elements and the like, using styling to create space. The patterns should be anchored at the start (^) and end ($), otherwise you could get a successful match on just a part of a value, causing validation to succeed when it should fail.
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