PHP Immutable Object Efficiencies - php

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 );
}
}
}

Related

PHP - iterate through different value types

I have object of class $values like:
Array
(
[0] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => CONNECT_NETWORKS_ON_SIGN_UP
[value:App\ValueObject\Features:private] => 1
)
[1] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => SHOW_BILLING
[value:App\ValueObject\Features:private] => 1
)
[2] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => FEATURE_FLAGS
[value:App\ValueObject\Features:private] => 'activity'
)
)
All array keys are returning boolean type value expect one, which returns string value.
My result with the code:
$arrays = array_map(
function($value) { return [strtolower((string) $value->getFeature())]; },
iterator_to_array($values)
);
return array_merge(...$arrays);
returns list of feature names like:
"features": [
"connect_networks_on_sign_up",
"show_billing",
"feature_flags"
]
What I want to edit is that for the last one we write its value NOT feature name ($value->getValue())
I am assuming that using in_array() PHP function would be the best approach here but I can't find a way to use it within my current method.
Tried with foreach() loop but nothing happens, like it's something wrong:
$features = [];
foreach ($values as $value)
{
$setParam = $value->getFeature();
if ($value == 'FEATURE_FLAGS') {
$setParam = $value->getValue();
}
$features[] = strtolower((string) $setParam);
}
return $features;
Can someone help?
Thanks
You should probably operate on the feature code FEATURE_FLAGS, rather than assuming that the last feature in the array always contains the flags. Using your existing code, that could be as simple as:
$arrays = array_map(
function($value)
{
/*
* If the current Features object has the feature code FEATURE_FLAGS,
* return the value itself, otherwise return the feature code in lowercase
*/
return ($value->getFeature() == 'FEATURE_FLAGS') ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
If you want to define an array of feature codes that you need to treat this way, you can define it internally in the callback, but it is probably a better idea to define it externally. You can then pass it into the callback with use
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
Working example:
// Mock the Features class
class Features
{
private $feature;
private $value;
public function __construct($feature, $value)
{
$this->feature = $feature;
$this->value = $value;
}
public function getFeature()
{
return $this->feature;
}
public function setFeature($feature): void
{
$this->feature = $feature;
}
public function getValue()
{
return $this->value;
}
public function setValue($value): void
{
$this->value = $value;
}
}
// Mock an iterator with test Feature instances
$values = new ArrayIterator( [
new Features('CONNECT_NETWORKS_ON_SIGN_UP', 1),
new Features('SHOW_BILLING', 1),
new Features('FEATURE_FLAGS', 'activity')
]);
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
$output = array_merge(...$arrays);
$expectedResult = [
'connect_networks_on_sign_up',
'show_billing',
'activity'
];
assert($output == $expectedResult, 'Result should match expectations');
print_r($output);

Check for anonymous functions in PHP arrays?

How can we check for anonymous functions inside PHP arrays?
Example:
$array = array('callback' => function() {
die('calls back');
});
Can we then just simply use in_array, and to something like this:
if( in_array(function() {}, $array) ) {
// Yes! There is an anonymous function inside my elements.
} else {
// Nop! There are no anonymous function inside of me.
}
I'm experimenting with method chaining and PHP's Magic Methods, and I've come to the point where I provide some functions anonymously, and just want to check if they are defined, but I wish not to loop through the object, nor to use gettype, or anything similar.
You can filter the array by checking if the value is an instance of Closure:
$array = array( 'callback' => function() { die( 'callback'); });
$anon_fns = array_filter( $array, function( $el) { return $el instanceof Closure; });
if( count( $anon_fns) == 0) { // Assumes count( $array) > 0
echo 'No anonymous functions in the array';
} else {
echo 'Anonymous functions exist in the array';
}
Pretty much, just check if the element of the array is an instance of Closure. If it is, you have a callable type.
Nickb's answer is great for figuring out if it is an anonymous function, but you may also use is_callable to figure out if it is any type of function ( probably more safe to assume )
For example
$x = function() { die(); }
$response = action( array( $x ) );
...
public function action( $array ){
foreach( $array as $element )
if( is_callable( $element ) )
....
}

Better way for checking $_REQUEST variable

$p = (isset($_REQUEST["p"])?$_REQUEST["p"]:"");
This is the common line I usually use in my php code. I always assume is there a better(small and faster) way to write the same ?
Create your own function :
function getIfSet(&$value, $default = null)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($_REQUEST['p']);
There's no other clean solution.
EDIT:
PHP 7 adds a null coalescing operator ("??")
$p = $_REQUEST["p"] ?? '';
https://www.php.net/manual/en/migration70.new-features.php
ORIGINAL:
if you want something shorter, and are content with an empty (string) default value, the following works:
$p = #$_REQUEST['p'];
# is the error-suppression operator and will keep the expression from giving a warning if the value is not set.
http://www.php.net/manual/en/language.operators.errorcontrol.php
How more shorter do you want it?
Of course, if you are using this every time you access a request value, you should create a function somewhere and then use that:
function reqVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_REQUEST[$val] ) ? $_REQUEST[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
function getVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_GET[$val] ) ? $_GET[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
function postVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_POST[$val] ) ? $_POST[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
Now add the sql incjection check:
function nosql( $var )
{
if ( is_array( $var ) ) {
foreach ( $var as $key => $elem ) $var[$key] = nosql( $elem );
} else if ( $var === null ) {
return null;
} else {
if ( get_magic_quotes_gpc() ) $var = stripslashes( $var );
$var = mysql_real_escape_string( $var );
}
return $var;
}
And access it always simple like this:
$p = reqVal( 'p', 0 );
$p = getVal( 'p', 'something', false );
$p = postVal( 'p' ); // or just forget the 2nd and 3rd parameter
The answers that wrap your existing code in a function are good - they do indeed tidy up the code if you've got a bunch of them.
However the better solution is to sanitize your entire request array based on a set of expected values before you start.
For example:
function sanitiseRequest() {
$expected = array(
'p' => '',
'id' => 0,
//etc
);
//throw away any input that wasn't expected...
foreach($_REQUEST as $key=>$value) {
if(!isset($expected[$key]) { unset $_REQUEST[$key]; }
}
//and for any expected values that weren't passed, set them to the defaults.
foreach($expected as $key=>$defvalue) {
if(!isset($_REQUEST[$key]) { $_REQUEST[$key] = $defvalue; }
}
}
Then simply add a call to this function at the start of the code, and you won't need to worry about doing isset($_REQUEST[..]) anywhere else in your code.
This concept can be expanded to force the incoming arguments to be the correct data type as well, or any other data cleansing you may want to do. This can give you complete confidence that the incoming data is populated as you expect.
Hope that helps.
I usually take advantage of the fact that PHP is loosely typed and simply do:
$p = (string) $_REQUEST['p'];
This way, even if $_REQUEST['p'] is not set, an empty string still gets stored into $p. Keep in mind that this only works if your error handler ignores notices, as accessing an unset key will trigger an E_NOTICE along the lines of "undefined index".
This is indeed so common, that i wonder there is no native way of doing it in PHP. Most developers write their own function to read safely from an array.
/**
* Gets the value associated with the specified key from an array.
* #param array $array The array to search for the key.
* #param mixed $key The key of the value to get.
* #param mixed $default The default value to return, if the
* specified key does not exist.
* #return mixed Value that is associated with the specified
* key, or the default value, if no such key exists.
*/
function getValueFromArray($array, $key, $default = null)
{
$containsKey = isset($array[$key]);
if ($containsKey)
return $array[$key];
else
return $default;
}
/**
* Gets the value associated with the specified key from an array.
* #param array $array The array to search for the key.
* #param mixed $key The key of the value to get.
* #param mixed $value Retrieves the found value, or is set to null
* if the key could not be found.
* #return bool Returns true if the key could be found, otherwise false.
*/
public function tryGetValueFromArray($array, $key, &$value)
{
$containsKey = isset($array[$key]);
if ($containsKey)
$value = $array[$key];
else
$value = null;
return $containsKey;
}
You can find many examples of different solutions here http://php.net/manual/en/function.isset.php in the User Contributed Notes section.
Try this:
function get_if_set( $varname, $parent=null ) {
if ( !is_array( $parent ) && !is_object($parent) ) {
$parent = $GLOBALS;
}
return array_key_exists( $varname, $parent ) ? $parent[$varname] : null;
}
This one works well for me.
And you don't have to write the name twice.
It won't change the var if it is already set. So it is safe to use for quick n dirty conversion of old application using register_globals.
function getIfSet($key, $default = null)
{
global $$key;
if(!isset($$key)){
if(isset($_REQUEST[$key])){
$$key=$_REQUEST[$key];
}else{
if(!is_null($default)){
$$key = $default;
}
}
}
}
function getIfSetArray($list){
foreach($list as $item){
getIfSet($item);
}
}
getIfSet('varname');
getIfSetArray(['varname_1','varname_2']);
echo $varname;
echo $varname_1;

Is there a better way to loop two associative arrays to match their values?

I'm parsing the HTTP_ACCEPT_LANGUAGE header to get users' language and I'm building a class to do that.
Actually I build an associative array ("$this->user_lang") where keys are the language (such as "en-us", "it-it", "it-ch" etc) and the value is the quality factor (so I can order languages).
Then I have another associative array named "$this->installed_langs" where I declare supported language and locales (in the form "en" => "en_US", "it" => "it_IT").
All I want to do is try to match one of the key of "$this->user_lang" with one of the "$this->installed_langs" (without care of the local zone after the "-") and return the first occurrence (with no care for other matching case).
I ended up with this method but it seems a bit too complex...
public function show() {
$g_locale = null;
foreach ($this->user_lang as $lang => $q) {
foreach($this->installed_langs as $valid => $locale) {
if (strpos($lang, $valid) !== false) {
if ($g_locale === null) $g_locale = $locale;
}
}
}
// debug:
echo $g_locale;
}
I hope I have explained it well, btw if you need more informations, please, ask me.
Try this
public function show() {
$g_locale = null;
foreach ($this->user_lang as $lang => $q) {
if ( array_key_exists( $lang, $this->installed_langs ) ) {
$g_locale = $this->installed_langs[$lang];
}
}
}
function show() {
$g_locale = null;
foreach ($this->user_lang as $lang => $q) {
$_key=explode($lang, '-'); // 'en-us' => 'array('en', 'us')
$key=$_key[0]; // 'en'
if ( array_key_exists( $key, $this->installed_langs ) ) {
$g_locale = $this->installed_langs[$key];
}
}
}

Property chaining and isset in configuration object [duplicate]

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

Categories