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.
Related
I want to get the function that can accept a string parameter and return it to json format.
for example if i call $ballcolor->getBall("orange");
then the output// should be:
{ "color":"orange", "ball": ["basketball"]}
and if a call does not have any color for that ball:
{ "color":"black", "ball": []}
class BallColor
{
private $ballcolor;
function BallColor($ballcolor)
{
$this->ballcolor = $ballcolor;
}
public function getBall($color)
{
return NULL;
}
}
$ballcolor = new BallColor(array(
"orange" => array("basketball"),
"white" => array("football")
));
echo $ballcolor->getBall("orange");
echo "\n";
echo $ballcolor->getBall("black");
Get the desired value out of $this->ballcolor after checking it's there, then build the array you need to return, then json_encode that:
class BallColor
{
private $ballcolor;
function __construct($ballcolor)
{
$this->ballcolor = $ballcolor;
}
public function getBall($color)
{
// condensed into oneliners:
if(isset($this->ballcolor[$color])) {
return json_encode(Array("color" => $color, "ball" => $this->ballcolor[$color]));
} else {
return json_encode(Array("color" => "black", "ball" => []));
}
}
}
$ballcolor = new BallColor(array(
"orange" => array("basketball"),
"white" => array("football")
));
echo $ballcolor->getBall("orange");
echo "\n";
echo $ballcolor->getBall("black");
Output:
{"color":"orange","ball":["basketball"]}
{"color":"black","ball":[]}
you should use
function __construct($ballcolor)
instead of
function BallColor($ballcolor)
as you constructor, unless your php version is old.
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')
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);
}
Here are my two test cases:
<?php
require_once(dirname(__FILE__) . '/../simpletest/unit_tester.php');
class Tests extends UnitTestCase {
function test_1() {
$this->assertTrue(true);
}
function test_2() {
$this->assertTrue(true);
}
}
?>
and my test driver:
<?php
require_once(dirname(__FILE__) . '/../simpletest/simpletest.php');
$test = new TestSuite();
$test->addFile(dirname(__FILE__) . '/ex1.php');
$test->run(new TextReporter());
?>
I get this output:
TestSuite
OK
Test cases run: 1/2, Passes: 2, Failures: 0, Exceptions: 0
when I run the driver file (ex2.php) like this from a terminal:
curl 'http://localhost/~marc/simpletestexample/ex2.php'
Now, why does it report "Test cases run: 1/2" and not "Test cases run: 1/1"? It seems that there is a phantom test case somewhere that's not being run.
<?php
require_once(dirname(__FILE__) . '/simpletest/autorun.php');
require_once('/classes/hello.php');
class Testhello extends UnitTestCase {
function testViewhelloWithEntries()
{
$hello= new hello();
$hello->add("Bob", "Hi, I'm Bob.");
$hello->add("Tom", "Hi, I'm Tom.");
$hello->add("jack", "Hi, I'm Jack.");
$entries = $hello->viewAll();
$count_is_greater_than_zero = (count($entries) > 0);
$this->assertTrue($count_is_greater_than_zero);
$this->assertIsA($entries, 'array');
foreach($entries as $entry) {
$this->assertIsA($entry, 'array');
$this->assertTrue(isset($entry['name']));
$this->assertTrue(isset($entry['message']));
}
}
public function hi($name ,$message)
{
self::$_entries[] = array('name' => $name, 'message' => $message ); //fixed!
return true;
}
function testViewhelloWithNoEntries()
{
$hello = new hello();
$hello->deleteAll(); // Delete all the entries first so we know it's an empty table
$hello->jay();
$entries = $hello->viewAll();
$this->assertEqual($entries, array());
}
}
?>
create a hello.php in classes folder
<?php
class hello
{
private static $_entries = array(
array (
'name' => 'Kirk',
'message' => 'Hi, I\'m Kirk.'
),
array (
'name' => 'Ted',
'message' => 'Hi, I\'m Ted.'
)
);
public function viewAll() {
// Here, we should retrieve all the records from the database.
// This is simulated by returning the $_entries array
return self::$_entries;
}
public function add( $name, $message ) {
// Here, we simulate insertion into the database by adding a new record into the $_entries array
// This is the correct way to do it: self::$_entries[] = array('name' => $name, 'message' => $message );
// print_r( self::$_entries[] = array('name' => $name, 'message' => $message )); //oops, there's a bug here somewhere
echo " <br> ";
echo "My name is a {$name}"." "."{$message}";
return true;
}
public function deleteAll() {
// We just set the $_entries array to simulate
self::$_entries = array();
return true;
}
public function jay() {
// We just set the $_entries array to simulate
//echo "hello world";
self::$_entries = array();
return true;
}
}
?>
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.