Building an array from unknown object properties - php

i'm trying to build an array from an Object in PHP. I only want certain properties from the object but I don;t know what they will be each time. The names of the properties I need are stored in an array. Here is how my code works currently:
// Hard-coded attributes 'colour' and 'size'
while ($objVariants->next())
{
$arrVariants[] = array
(
'pid' => $objVariants->pid,
'size' => $objVariants->size,
'colour' => $objVariants->colour,
'price' => $objVariants->price
);
}
Instead of hard coding the attributes (colour and size) I want to use variables, this is because it may not always be colour and size depending on what the user has set in the CMS. For example:
$arrVariantAttr = $this->getVariantAttr(); // Get the names of the custom variants and put them in an array e.g colour, size
while ($objVariants->next())
{
$arrVariants[] = array
(
'pid' => $objVariants->pid,
foreach($arrVariantAttr as $attr)
{
$attr['name'] => $objVariants-> . $attr['name']; // Get each variant out of the object and put into an array
}
'price' => $objVariants->price
);
}
The above code doesn't work, but hopefully it illustrates what i'm trying to do. Any help would be appreciated, thank you!

You could use get_object_vars() to get all variables of an object:
$arrVariants[] = get_object_vars($objVariants);
In order to exclude specific properties from the object you could do like this:
$arrVariants = get_object_vars($objVariants);
// array containing object properties to exclude
$exclude = array('name');
// walk over array and unset keys located in the exclude array
array_walk($arrVariants, function($val,$key) use(&$arrVariants, $exclude) {
if(in_array($key, $exclude)) {
unset($arrVariants[$key]);
}
});

You could create an array in the object containing the attributes:
$objVariants->attr['pid']
You can also use magic methods to make you object array like.

It sounds like what you really want is sub-classes or a Factory pattern.
For instance you could have a basic product object
class Product {
protected $_id;
protected $_sku;
protected $_name;
...
etc.
//getters and setters
etc.
}
... and then use sub-classes to extend that product
final class Book extends Product {
private $_isbn;
private $_language;
private $_numPages;
...
etc.
public function __construct() {
parent::__construct();
}
//getters and setters
etc.
}
That way your product types have all the attributes they need and you don't need to try and run around with an "attributes" array - though your CMS needs to be able to support product types (so that if someone wants to add a new book, the fields relevant to books appear in the CMS)... it's just a slightly more OO approach to the problem.
You could then factory pattern it; something like (a really basic example):
class ProductFactory {
const TYPE_BOOK = 'Book';
const TYPE_CD = 'CD';
const TYPE_DVD = 'DVD';
...
etc.
public static function createProduct($sProductType) {
if(class_exists($sProductType)) {
return new $sProductType();
}
else {
//throw an exception
}
}
}
You can then generate new products with something like:
$oWarAndPeace = ProductFactory::createProduct('Book')
or better yet:
$oWarAndPeace = ProductFactory::createProduct(ProductFactory::TYPE_BOOK)

Try something like this:
$arrVariants[] = Array(
'pid' => $objVariants->pid,
'price' => $objVariants->price
);
while( $objVariants->next() )
{
foreach( $arrVariantAttr as $attr )
{
end($arrVariants)[$attr['name']] = $objVariants->$attr['name'];
}
}

Related

PHP - create strict typed Map dynamically (multidimensional)?

I need to be able to create strict typed maps dynamically. Like this:
$map = new Map( 'string,array<string,int>', [
'foo' => [
'bar' => 1
]
];
I have seen a lot of solutions for separate cases. All guides are teaching to create a class for each map, like Users_Map (to keep users there), Products_Map (to keep products there), Comments_Map (to keep comments there), etc.
But I don't want to have 3 classes (dozens in fact - for a big project) for each type of the map. I want to create a single class Map and then use it like this:
$users = new Map( 'User', {users data goes here} );
$products = new Map( 'int,Product', {products data goes here} );
$comments = new Map( 'User,array<Comment>', {comments data goes here} );
I would appreciate if somebody can advice me any existing repos. Otherwise I'll probably implement this on my own and will put here a link to my solution as an answer.
What you're looking for is called generics. PHP doesn't support this, although there has been an RFC calling for support for a few years.
If you really want to enforce strict typing on a custom map, you'd have to build it yourself. You could, for example, do something like this:
class Map {
private string $keyType;
private string $valueType;
private array $items;
public function __construct(string $keyType, string $valueType) {
$this->keyType = $keyType;
$this->valueType = $valueType;
}
public function set($key, $value) {
if (gettype($key) !== $this->keyType && !($key instanceof $this->keyType)) {
throw new TypeError("Key must be of type " . $this->keyType);
}
if (gettype($value) !== $this->valueType && !($value instanceof $this->valueType)) {
throw new TypeError("Value must be of type " . $this->valueType);
}
$this->items[$key] = $value;
}
public function get($key) {
if (gettype($key) !== $this->keyType) {
throw new TypeError("Key must be of type " . $this->keyType);
}
return $this->items[$key] ?? null;
}
public function all() {
return $this->items;
}
}
(of course, this particular implementation uses a regular array internally, so keyType is limited to types that are valid array keys. If you want to support other object types, some more interesting logic might be required)
The combination of gettype and instanceof will ensure this works for both simple and complex types. For example:
$map = new Map("string", "array");
$map->set("name", ["Boris", "Johnson"]);
print_r($map->all());
/*
Array
(
[name] => Array
(
[0] => Boris
[1] => Johnson
)
)
*/
$map->set("job", "Prime Minister");
// Fatal error: Uncaught TypeError: Value must be of type array
Or with a class as value type:
class User {
public string $firstName;
public string $lastName;
}
$user = new User();
$user->firstName = "Boris";
$user->lastName = "Johnson";
$map = new Map("string", User::class);
$map->set("pm", $user);
print_r($map->all());
/*
Array
(
[pm] => User Object
(
[firstName] => Boris
[lastName] => Johnson
)
)
*/
If you also want to support nested generics, like in your example array<string,int>, that becomes more complicated. In that case, as soon as someone passes an array as a value, you'd have to manually check all items in the array to ensure all array keys are strings and all array values are integers. It's possible, but for larger arrays it will be a significant performance hit.
Although you could use a nested Map like this one if you extend it to enforce the types:
class StringIntMap extends Map {
public function __construct() {
parent::__construct("string", "integer");
}
}
$map = new Map("string", StringIntMap::class);

PHP Objects trying to Access an external array

I have an Object that needs to access a previously declared array in my statistics. I can of course create the entire array inside of the object, but since multiple objects use the exact same array there is no reason to clog up memory or time by making a call to the Database to create the same array every time I create a new object.
So, I understood that Objects cannot access global variables, but is there any work-around to access an external Array from within the object?
example Code:
global $stats = array();
$stats[1]['value']= 10;
$stats[1]['value1'] =2;
$stats[2]['value']= 12;
$stats[2]['value1'] =1;
class Obj() {
private $valueA;
private $valueB;
function __construct($user) {
//access Database lets call $SQL;
$valueA = SQL->value;
}
function showA() {
return ( $valueA * $stats[1]['value1']) + $stats[1]['value'];
}
}
Yes how about changing your class to look like this:
class Obj() {
private $valueA;
private $valueB;
private $stats;
function __construct($user, $stats) {
$this->stats = $stats;
//access Database lets call $SQL;
$valueA = SQL->value * $this->stats[1]['value1'] + $this->stats[1]['value'];
$valueB = SQL->value * $this->stats[2]['value1'] + $this->stats[2]['value'];
}
function showA() {
return $valueA;
}
}
You than just pass $stats to the object at instantiation. Or if you don't want it in the constructor, just make a setStats($stats) method that does the same.
I'll tell you three ways to do this:
pass the array into the constructor of the class. e.g.: $myObject =
new Obj( $stats );
make a class that serves up the $stats array: $stats = new Stats(); $statsArray = $stats->getStats();
use the term global inside of a public method in your class itself (not construct) to get that variable: 3:
function() somePublicMethod() {
global $stats;
$valueA = SQL->value * $stats[1]['value1'] + $stats[1]['value'];
$valueB = SQL->value * $stats[2]['value1'] + $stats[2]['value'];
}
You can access variables from within a class, i.e.
$stats[1]['value']= 10;
$stats[1]['value1'] =2;
$stats[2]['value']= 12;
$stats[2]['value1'] =1;
class Obj {
var $myStats;
function __construct() {
global $stats;
$this->myStats= $stats;
print_r($this->myStats);
}
}
$obj=new Obj; // Array ( [1] => Array ( [value] => 10 [value1] => 2 ) [2] => Array ( [value] => 12 [value1] => 1 ) )
DEMO.
Thanks to Mike, Sheikh and Kristian,
I can't in all Faith give a tick to your answers, because your words did not help me to understand the answer, Putting 'global $stats;' into the class results in an Error which I pointed out in my responses. but I will 'up' your scores when I permission from the site to do so.
For anyone looking for the answer to this, a Reminder, the key point is not to store the entire Array in the class, creating a huge waste of memory. The Key point is to gain access to the Variable which exists outside of the class.
While adding access to the global $stats by including it in the functions of the class, does produce the required results, It still requires that the Data is being stored in the class, which is again, against the point. Sorry I wasn't clear on this from the very beginning.
Instead:
example Code:
function showA(&$stats) {
return ( $valueA * $stats[1]['value1']) + $stats[1]['value'];
}
}
This, if I understand correctly, will use the pointer to the $stats variable, only within the scope of returning the $valueA after it has been modified using the stats array. not copying the entire array into another memory location, nor the class.

Declare one class variable via another

sorry for the vague title... I have a problem with a php4 class that seems hard to put to words.
My class has a "possibleValues" array, that holds information on what kind of values are acceptable for certain attributes that can be changed from the outside. In my pseudocode example below you can see that I have attributes that share the same acceptable values (colors). Obviously, the $this->colors in my code fails, because you can't define one class variable via another (can you?).
How would I go about setting a common colors array that I could reference like this so I don't have to repeat the same valid options for different fields that allow the same values?
Class MyTest {
var $colors = array('red', 'yellow', 'blue');
var $possibleValues = array(
'someAttribute', array(0,1),
'someEmotions', array('happy, 'sad'),
--> 'someColorableAttribute', $this->colors,
--> 'someOtherColorableAttribute', $this->colors,
);
...
}
In answer to your question, you should set the variable in the constructor. In PHP4, which you really shouldn't be using, the constructor should have the name of the class.
function MyTest()
{
$this->var = $this->colors;
}
If you don't want to set the $possibleValues in constructor, you can try this approach:
PHP 4
class MyTest {
var $colors = array('red', 'yellow', 'blue');
var $possibleValues = array(
'someAttribute' => array(0,1),
'someEmotions' => array('happy', 'sad'),
'someColorableAttribute' => 'colors',
'someOtherColorableAttribute' => 'colors',
);
function getPossibleValues($attr) {
//no attribute, empty list
if (!array_key_exists($attr, $this->possibleValues))
return array();
$possible_values = $this->possibleValues[$attr];
//value is a string, check for object variable with such name
if (is_string($possible_values)) {
if (!array_key_exists($possible_values, get_object_vars($this)))
return array();
return $this->$possible_values;
}
return $possible_values;
}
}
$a = new MyTest();
var_dump($a->getPossibleValues('someAttribute'));
var_dump($a->getPossibleValues('someEmotions'));
var_dump($a->getPossibleValues('someColorableAttribute'));
var_dump($a->getPossibleValues('someOtherColorableAttribute'));
I'm using get_object_vars since property_exists don't exist in PHP 4.
PHP 5 (class constant)
class MyTest {
const COLORS = 'red|yellow|blue';
private $possibleValues = array(
'someAttribute' => array(0,1),
'someEmotions' => array('happy', 'sad'),
'someColorableAttribute' => self::COLORS,
'someOtherColorableAttribute' => self::COLORS,
);
public function getPossibleValues($attr) {
//no attribute, empty list
if (!array_key_exists($attr, $this->possibleValues))
return array();
$possible_values = $this->possibleValues[$attr];
//value is a string, explode it around |
if (is_string($possible_values)) {
return explode('|', $possible_values);
}
return $possible_values;
}
}

Indirect modification of overloaded property

I'm creating a forum, and I want to keep track of which threads have been updated since the user last visited. So I have an array that I keep in $_SESSION that is basically structured as [$boardid][$threadid] = 1. If the threadid and boardid are set, then the thread has not been read and the board contains unread threads. When a user views a thread, I just unset() the appropriate board and thread id. However, I've having problems with getting unset to work with arrays like this.
Firstly, I have a session class to make handling session data a little nicer
class Session {
private $_namespace;
public function __construct($namespace = '_default') {
$this->_namespace = $namespace;
}
/**
* Erase all variables in the namespace
*/
public function clear() {
unset($_SESSION[$this->_namespace]);
}
public function __set($name, $value) {
$_SESSION[$this->_namespace][$name] = $value;
}
public function __get($name) {
if(isset($_SESSION[$this->_namespace]) && array_key_exists($name, $_SESSION[$this->_namespace])) {
return $_SESSION[$this->_namespace][$name];
}
return null;
}
public function __isset($name) {
return isset($_SESSION[$this->_namespace][$name]);
}
public function __unset($name) {
unset($_SESSION[$this->_namespace][$name]);
}
};
Then I have a CurrentUser class representing the current user. The CurrentUser class has a member named _data which is-a Session object. In the CurrentUser class I override the __get and __set methods to use the _data member.
public function __set($name, $value) {
$this->_data->$name = $value;
}
public function __isset($name) {
return isset($this->_data->$name);
}
public function __get($name) {
if(isset($this->_data->$name)) {
return $this->_data->$name;
}
return null;
}
Now to keep track of which threads have been unread, I fetch all threads whose date is >= the user's last_seen date. I also have methods to remove board and threads from the array.
public function buildUnreadList($since) {
// Build a "new since last visit" list
$forumModel = new Model_Forum();
$newThreads = $forumModel->fetchThreadsSinceDate($since);
foreach($newThreads as $thread) {
$tmp =& $this->unreadThreadsList;
$tmp[$thread['board']][$thread['id']] = 1;
}
}
public function removeThreadFromUnreadList($boardid, $threadid) {
$threads =& $this->unreadThreadsList;
unset($threads[$boardid][$threadid]);
}
public function removeBoardFromUnreadList($boardid) {
$threads =& $this->_data->unreadThreadsList;
unset($threads[$boardid]);
}
This is where I'm running into problems. I'm getting a Indirect modification of overloaded property Session::$unreadThreadsList has no effect error on $threads =& $this->_data->unreadThreadsList; How can I either fix this problem or design a better solution? I thought about creating a class that keeps track of the array so I don't have to have an array of arrays of arrays of arrays, but I'm not certain on persisting objects and creating an object just to manage an array feels really dirty to me.
Sorry if I'm a little bit off base; I'm trying to understand how the variables are being used (as their initialization is not shown). So $this->unreadThreadsList is an array where the indices (if value set to 1). Why not set everything directly?
Looking at what you're doing, here is an idea I had. It does the same thing but just does some extra checking on $this->unreadThreadsList and it accesses the variable directly.
Assuming I figured out the array structure properly, this should work.
public function buildUnreadList($since) {
// Build a "new since last visit" list
$forumModel = new Model_Forum;
$newThreads = $forumModel->fetchThreadsSinceDate($since);
foreach($newThread as $thread)
{
// Avoid an error if no list pre-exists
if(is_array($this->unreadThreadsList))
if(array_key_exists($thread['board'],$this->unreadThreadsList))
if(array_key_exists($thread['id'],$this->unreadThreadsList[$thread['board']]))
// Skip this result, already in
if($this->unreadThreadsList[$thread['board']][$thread['id']] == 1) continue;
$this->unreadThreadsList[$thread['board']][$thread['id']] = 1;
}
}
This assumes an array structure like:
array(
1 => array(
'board' => 1,
'id' => 2
),
2 => array(
'board' => 3,
'id' => 1
),
3 => array(
'board' => 7,
'id' => 2
));
for the result of "fetchThreadsSinceData($since)" and an array structure of
array(
1 => array(
2 => 1
),
2=> array(
2 => 1
),
3=> array(
2 => 1
));
for the $this->unreadThreadsList where the first index is the board and the second index is the thread id.
For the other functions why not simply unset them directly as well?
unset($this->unreadThreadsList[$boardid][$threadid]);
unset($this->unreadThreadsList[$boardid]);
Good luck!
Dennis M.

PHP Object Validation

I'm currently working on an OO PHP application. I have a class called validation which I would like to use to check all of the data submitted is valid, however I obviously need somewhere to define the rules for each property to be checked. At the moment, I'm using arrays during the construction of a new object. eg:
$this->name = array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter
)
One array for each property.
I would then call a static method from the validation class which would carry out various checks depending on the values defined in each array.
Is there a more efficient way of doing this?
Any advice appreciated.
Thanks.
I know the associative array is used commonly to configure things in PHP (it's called magic container pattern and is considered bad practice, btw), but why don't you create multiple validator classes instead, each of which able to handle one rule? Something like this:
interface IValidator {
public function validate($value);
}
$validators[] = new StringLengthValidator(2, 10);
$validators[] = new NotNollValidator();
$validators[] = new UsernameDoesNotExistValidator();
This has multiple advantages over the implementation using arrays:
You can document them (very important), phpdoc cannot parse comments for array keys.
Your code becomes typo-safe (array('reqiured' => true))
It is fully OO and does not introduce new concepts
It is more readable (although much more verbose)
The implementation of each constraint can be found intuitively (it's not in a 400-line function, but in the proper class)
EDIT: Here is a link to an answer I gave to a different question, but that is mostly applicable to this one as well.
Since using OO it would be cleaner if you used classes for validating properties. E.g.
class StringProperty
{
public $maxLength;
public $minlength;
public $required;
public $value;
function __construct($value,$maxLength,$minLength,$required)
{
$this->value = $value;
$this-> maxLength = $maxLength;
$this-> minLength = $minLength;
$this-> required = $required;
}
function isValidat()
{
// Check if it is valid
}
function getValidationErrorMessage()
{
}
}
$this->name = new StringProperty($namefromparameter,10,2,true);
if(!$this->name->isValid())
{
$validationMessage = $this->name-getValidationErrorMessage();
}
Using a class has the advantage of encapsulating logic inside of it that the array (basically a structure) does not have.
Maybe get inspired by Zend-Framework Validation.
So define a master:
class BaseValidator {
protected $msgs = array();
protected $params = array();
abstract function isValid($value);
public function __CONSTRUCT($_params) {
$this->params = $_params;
}
public function getMessages() {
// returns errors-messages
return $this->msgs;
}
}
And then build your custom validators:
class EmailValidator extends BaseValidator {
public function isValid($val=null) {
// if no value set use the params['value']
if ($val==null) {
$val = $this->params['value'];
}
// validate the value
if (strlen($val) < $this->params['maxlength']) {
$this->msgs[] = 'Length too short';
}
return count($this->msgs) > 0 ? false : true;
}
}
Finally your inital array could become something like:
$this->name = new EmailValidator(
array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter,
),
),
);
validation could then be done like this:
if ($this->name->isValid()) {
echo 'everything fine';
} else {
echo 'Error: '.implode('<br/>', $this->name->getMessages());
}

Categories