I see myself doing this a lot:
function getTheProperty()
{
if (! isset($this->theproperty)) {
$property = // logic to initialise thepropery
$this->theproperty = $property;
}
return $this->theproperty;
}
This is good, since it avoids the epxensive logic used to initialise the value. however, the downside so far as i can see is that i cant determine exactly how clients will use this and this may be confusing.
Is this a good pattern to use? What considerations should be taking when doing this?
How about adding a parameter - $forceNew for example that bypasses the memoization?
Magic Methods. Something like:
Class MyMagic {
private $initinfo = array(
'foo' => array('someFunction', array('arg1', 'arg2'))
'bar' => array(array($this, 'memberFunction'), array('arg3'))
);
public function __get($name) {
if( ! isset($this->$name) ) {
if( isset($this->initinfo[$name]) ) {
$this->$name = call_user_func_array(initinfo[$name][0], initinfo[$name][1]);
} else {
throw new Exception('Property ' . $name . 'is not defined.');
}
} else {
return $this->$name;
}
}
}
$magic = new MyMagic();
echo $magic->foo; //echoes the return of: someFunction('arg1', 'arg2')
echo $magic->bar; //echoes the return of: $this->memberFunction('arg3')
Related
I'm new to php - objects and arrays, especially. Coming from a JavaScript world, I'm having a modicum of trouble understanding the right way to construct objects, that may easily be iterated.
I'd like to create an object (or array - although I suspect an object would be more suitable) with the following structure:
$client_body:
$cst:
$title: 'Unique string'
$copy: function_result()
$ser:
$title: 'Unique string'
$copy: function_result()
$imp
$title: 'Unique string'
$copy: function_result()
...
I've been trying with variations on the following, but with numerous errors:
$client_body = new stdClass();
$client_body->cst->title = 'Client case study';
$client_body->cst->copy = get_field('client_cst');
$client_body->ser->title = 'Our service';
$client_body->ser->copy = get_field('client_ser');
...
And it seems that, using this approach, I'd have to use a new stdClass invocation with each new top-level addition, which seems a little verbose.
Could someone point me in the right direction?
You can just typecast an array to an object:
$client_body = (object)array(
"cst" => (object)array(
"title" => "Unique string",
"copy" => function_result()
)
);
You can try this object class more OOP:
<?php
class ClientBody{
protected $cst;
protected $ser;
protected $imp;
public function __construct($cst = '', $ser ='', $imp = '')
{
$this->cst = $cst;
$this->ser = $ser;
$this->imp = $imp;
}
public function getCst()
{
return $this->cst;
}
public function getSer()
{
return $this->ser;
}
public function getImp()
{
return $this->imp;
}
public function setCst($value)
{
$this->cst = $value;
}
public function setSer($value)
{
$this->ser = $value;
}
public function setImp($value)
{
$this->imp = $value;
}
}
$myObject = new ClientBody('toto', 'titi', 'tata');
echo $myObject->getCst(); // output 'toto'
echo $myObject->getSer(); // output 'titi'
echo $myObject->getImp(); // output 'tata'
Or you could use json_decode($client_body, TRUE);
I have one PHP class as below (part of the code):
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
}
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
The calling code in index.php
$msg = 'Michael,18';
myclass::getHandle()->doProcess($msg);
In my webpage says index.php, it calls function doProcess() over and over again. When the function is called, string is passed and stored in an array. In the next call, if let's say same name is passed again, I want to update his age. My problem is I don't know how to check if the array $arrX contains the name. From my own finding, the array seems to be re-initiated (back to zero element) when the code is called. My code never does the update and always go to the array_push part. Hope somebody can give some thoughts on this. Thank you.
There is a ) missing in your else condition of your doProcess() function, it should read:
else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
)); // <-- there was the missing )
}
Here is a complete running solution based on your code:
<?php
class myclass{
private static $arrX = array();
private function is_val_exists($needle, $haystack) {
if(in_array($needle, $haystack)) {
return true;
}
foreach($haystack as $element) {
if(is_array($element) && $this->is_val_exists($needle, $element))
return true;
}
return false;
}
//the $anInput is a string e.g. Michael,18
public function doProcess($anInput){
$det = explode(",", $anInput);
if( $this->is_val_exists( $det[0], $this->returnProcess() ) ){
//update age of Michael
for ($i=0; $i<count(self::$arrX); $i++) {
if (is_array(self::$arrX[$i]) && self::$arrX[$i]['name'] == $det[0]) {
self::$arrX[$i]['age'] = $det[1];
break;
}
}
} else{
array_push(self::$arrX, array(
'name' => $det[0],
'age' => $det[1]
));
}
}
public function returnProcess(){
return self::$arrX;
}
}
$mc = new myclass();
$mc->doProcess('Michael,18');
$mc->doProcess('John,23');
$mc->doProcess('Michael,19');
$mc->doProcess('John,25');
print_r($mc->returnProcess());
?>
You can test it here: PHP Runnable
As I said in comments, it looks like you want to maintain state between requests. You can't use pure PHP to do that, you should use an external storage solution instead. If it's available, try Redis, it has what you need and is quite simple to use. Or, if you're familiar with SQL, you could go with MySQL for example.
On a side note, you should read more about how PHP arrays work.
Instead of array_push, you could have just used self::$arrX[] = ...
Instead of that, you could have used an associative array, e.g. self::$arrX[$det[0]] = $det[1];, that would make lookup much easier (array_key_exists etc.)
Can you try updating the is_val_exists as follows:
private function is_val_exists($needle, $haystack) {
foreach($haystack as $element) {
if ($element['name'] == $needle) {
return true;
}
return false;
}
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.
PROBLEM
I have a function that takes in a nested array where the structure and nesting of the array is unknown at run-time. All that is known is some of the target fieldnames and desired values of some of the leafs.
QUESTIONS
1) I am hoping to modify this unknown structure and still have the code be readable and easily understood by fellow programmers. What (if any) solution will allow me to do things like this in PHP?
// Pseudo-code for things I would like to be able to do
// this is kinda like the same thing as XPATH, but for native PHP array
// find *every* fname with value of "Brad" and change it to "Brian"
$mydata->find_all('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
// find *the first* fave_color and set it to "Green"
$mydata->find('*:fave_color')->get(0)->set_equal_to('Green');
2) If there is nothing out there that will let me do this, is there something, anything, that at least comes close to the spirit of what I am hoping to accomplish here?
SAMPLE ARRAY
$mydata = array(
'people' => array(
array('fname'=>'Alice'),
array('fname'=>'Brad'),
array('fname'=>'Chris'),
),
'animals' => array(
array('fname'=>'Dino'),
array('fname'=>'Lassie'),
array('fname'=>'Brad'),
),
'settings' => array(
'user_prefs'=>array(
'localhost'=>array(
'fave_color'=>'blue',
),
),
),
'places' => array(
array('state'=>'New york',
'cities'=>array(
'name'=>'Albany',
'name'=>'Buffalo',
'name'=>'Corning',
),
'state'=>'California',
'cities'=>array(
'name'=>'Anaheim',
'name'=>'Bakersfield',
'name'=>'Carlsbad',
),
),
),
);
Although I maintain that you should stick with explicit manipulation as in my previous answer, boredom and intrigue got the better of me ;)
It probably has holes (and clearly lacks docs) but if you insist on this route, it should get you started:
class Finder {
protected $data;
public function __construct(&$data) {
if (!is_array($data)) {
throw new InvalidArgumentException;
}
$this->data = &$data;
}
public function all() {
return $this->find();
}
public function find($expression = null) {
if (!isset($expression)) {
return new Results($this->data);
}
$results = array();
$this->_find(explode(':', $expression), $this->data, $results);
return new Results($results);
}
protected function _find($parts, &$data, &$results) {
if (!$parts) {
return;
}
$currentParts = $parts;
$search = array_shift($currentParts);
if ($wildcard = $search == '*') {
$search = array_shift($currentParts);
}
foreach ($data as $key => &$value) {
if ($key === $search) {
if ($currentParts) {
$this->_find($currentParts, $value, $results);
} else {
$results[] = &$value;
}
} else if ($wildcard && is_array($value)) {
$this->_find($parts, $value, $results);
}
}
}
}
class Results {
protected $data;
public function __construct(&$data) {
$this->data = $data;
}
public function get($index, $limit = 1) {
$this->data = array_slice($this->data, $index, $limit);
return $this;
}
public function set_equal_to($value) {
foreach ($this->data as &$datum) {
$datum = $value;
}
}
public function __call($method, $args) {
if (!preg_match('/^where_?(key|value)_?(eq|contains)$/i', $method, $m)) {
throw new BadFunctionCallException;
}
if (!isset($args[0])) {
throw new InvalidArgumentException;
}
$operand = $args[0];
$isKey = strtolower($m[1]) == 'key';
$method = array('Results', '_compare' . (strtolower($m[2]) == 'eq' ? 'EqualTo' : 'Contains'));
$ret = array();
foreach ($this->data as $key => &$datum) {
if (call_user_func($method, $isKey ? $key : $datum, $operand)) {
$ret[] = &$datum;
}
}
$this->data = $ret;
return $this;
}
protected function _compareEqualTo($value, $test) {
return $value == $test;
}
protected function _compareContains($value, $test) {
return strpos($value, $test) !== false;
}
}
$finder = new Finder($mydata);
$finder->find('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
$finder->find('*:fave_color')->get(0)->set_equal_to('Green');
$finder->find('places:*:cities:*:name')->where_value_contains('ba')->set_equal_to('Stackoton');
print_r($mydata);
There's certainly no native solution for this and the syntax is rather strange. If you want the code to "be readable and easily understood by fellow programmers" please stick to methods that we're used to working with ;)
foreach ($mydata as $type => &$data) {
foreach ($data as &$member) {
if (isset($member['fname']) && $member['fname'] == 'Brad') {
$member['fname'] = 'Brian';
}
}
}
It's admittedly more verbose, but there's much less chance of confusion.
I am trying to create an form validator class that will check to make sure that people have typed a particular number of characters into specified fields. Here is my page:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
require_once('functions.php');
require_once('formValidator.php');
$validator = new FormValidator();
$validator->setFirst($_POST['first']);
$validator->setLast($_POST['last']);
$validator->setEmail($_POST['email']);
$errors = array();
$validator->checkLength($errors);
echo $errors;
if (!empty($errors)) {
echo '<p>' . $message . '</p>';
}
}
// form here (code above is in PHP tags, of course)
Here is my class:
class FormValidator {
public $first;
public $last;
public $email;
public $fields_with_lengths;
public function setFirst($first) {
$this->first = $first;
}
public function setLast($last) {
$this->last = $last;
}
public function setEmail($email) {
$this->email = $email;
}
function setLengths() {
$this->fields_with_lengths = array('first' => 2, 'last' => 2, 'email' => 8);
}
function checkLength($fields_with_lengths) {
$length_errors = array();
foreach($fields_with_lengths as $fieldname => $maxlength) {
if (strlen(trim($_POST[$fieldname])) > $maxlength) {
$length_errors[] = $fieldname;
}
}
if (!empty($length_errors)) {
$message = 'There were no errors';
} else {
$message = 'There were errors';
}
return $message;
}
}
$validator = new FormValidator();
$validator->setFirst($_POST['first']);
$validator->setLast($_POST['last']);
$validator->setEmail($_POST['email']);
Can anyone see what I am doing wrong? I'd be grateful for some help. Thanks.
You're passing an empty array into checkLength(), then iterating over it (so the for loop will never have anything to iterate over). You're returning $message from checkLength() but not assigning it to anything.
$errors = array('first' => 8,
'last' => 8,
'email' => 16
);
$message = $validator->checkLength($errors);
In PHP by default all values to functions are passed as values, not references (unless they are objects).
First of all, if you want to use a variable as a return value, you'd have to define checkLength($var) as:
function checkLength(&$fields_with_lengths) { /* ... */ }
You could in PHP4 do a call like checkLength(&$errors), but that's deprecated as of PHP5 and will throw an E_STRICT warning. Of course, you still can (but that functionality may be thrown away soon), though the "correct" way is, as said, giving it as reference.
An even more elegant way of acting, and what should really be done in PHP, is to return the $fields_with_lengths. This way:
function checkLength() {
$var = array();
// Your code
return $var;
}
And in the call to the function, just say
$return = $validator->checkLength();
But that does not solve the logic of your problem.
You are iterating over a parameter which is an empty array.
You are not iterating over $validator->fields_with_lengths.
Even more, $fields_with_lengths, in your code is not initialized ever.
You would have to call $validator->setLengths().
A better way of doing is (you are using PHP5) declaring a constructor, which in PHP5 is done with the magic function __construct().
However, as you are just initializing the variable always with the same constant values, you can do that in the very definition, just like this:
So you would have something like:
class FormValidator {
pubilc $first;
pubilc $last;
pubilc $email;
pubilc $fields_with_lengths = array('first' => 2, 'last' => 2, 'email' => 8);
// Rest of code
}
What's more..
¿Why are the attributes marked as public?
OOP theory tells us to close the scope of visibility as much as possible. And as you have setters, you don't need to access the attributes directly.
You can in PHP have a magic__get($var)to return a private $attribute if you want the syntactic sugar ofecho $validator->first;`.
Example:
function __get($var) {
if(isset($this->{$var})) return $this->{$var}; // this $this->{$var} could even be written as $this->$var but I did it like that for clarity.
}
If you also want the syntactic sugar of $validator->first = "John"; you can do the same with the setter, as in:
function __set($var, $value) {
if(isset($this->{$var})) {
return $this->{$var} = $value;
} return null; // Or you can throw an exception if you like
}
In PHP, as opposed to other languages, such as Java, you must explicitly use $this as the current instance of an object within a function.
So your checkLength is misdefined.
You should also do something with the return value, instead of just dropping it.
With just that minor modifications, you could fix your code with
function checkLength() {
$length_errors = array();
foreach($this->fields_with_lengths as $fieldname => $maxlength) {
if (strlen(trim($_POST[$fieldname])) > $maxlength) {
$length_errors[] = $fieldname;
}
}
if (!empty($length_errors)) {
$message = 'There were no errors';
} else {
$message = 'There were errors';
}
return array($length_errors, $message);
}
You were using a variable ($length_errors otherwise inaccessible! so you were computing data which would never be useful).
And in the call to the function, something like
$validator = new FormValidator();
// You could leave the setXXX($_POST[xxx])
$validator->first = $_POST['first'];
$validator->last = $_POST['last'];
$validator->email = ($_POST['email'];
$errors = $validator->checkLength();
echo "<p> {$errors[1]} </p>";
if(!empty($errors[0])) {
// You could do something like foreach($errors[0] as $k => $fieldname) { echo "Error in $fieldname<br />" }
}
$errors is empty because you initialize it but never change it. $message is unset because you never assign anything to it.
You're defining $errors = array();, making the errors an empty array. Nowhere do you attempt to set it to any other value, thus it remains an empty array.