Building a data dictionary in web - php

Not a dictionary for language, but data. I.e Car1[Make="Honda",Model="Accord",Colour="Red"]
That's how I'd do it in Python, but by the look of things, it's harder to do on the web. I'm using PHP (not ASP).
Has anyone had any experience with writing this kind of thing on web? I'm open to JS etc if needs be. I've seen a couple of hacks for PHP, but would like to know anything more suited.

Dictionary from Python can be compared to associative arrays in (or, less probable, objects) PHP and simple objects (with similar notation as in Python, called JSON) in JavaScript, if you asked for that.

Data structures could be saved as an array;
$car1 = array( 'make' => 'Honda', 'model' => 'Accord', 'colour' => 'Red' );
Or you could make an object;
class cars{
private $make;
private $model;
private $colour;
public function __construct($make, $model, $colour)
{
$this->make = $make
$this->model = $model;
$this->colour = $colour;
}
}
$car1 = new cars('Honda', 'Accord', 'Red');
Or you save it an a database.
Or as an YAML and/or JSON document.
Or as an XML document.
All depending on what and how much data you have.

You could store it as a PHP array:
$car = array(
'make' => 'Honda',
'model' => 'Accord',
'colour' => 'Red'
);

That looks like a regular associative array to me

Related

Accessing nested property on stdClass Object with string representation of the node

Given a variable that holds this string:
$property = 'parent->requestdata->inputs->firstname';
And an object:
$obj->parent->requestdata->inputs->firstname = 'Travis';
How do I access the value 'Travis' using the string? I tried this:
$obj->{$property}
But it looks for a property called 'parent->requestdata->inputs->firstname' not the property located at $obj->parent->requestdtaa->inputs->firstname`
I've tried various types of concatenation, use of var_export(), and others. I can explode it into an array and then loop the array like in this question.
But the variable '$property' can hold a value that goes 16 levels deep. And, the data I'm parsing can have hundreds of properties I need to import, so looping through and returning the value at each iteration until I get to level 16 X 100 items seems really inefficient; especially given that I know the actual location of the property at the start.
How do I get the value 'Travis' given (stdClass)$obj and (string)$property?
My initial searches didn't yield many results, however, after thinking up a broader range of search terms I found other questions on SO that addressed similar problems. I've come up with three solutions. All will work, but not all will work for everyone.
Solution 1 - Looping
Using an approach similar to the question referenced in my original question or the loop proposed by #miken32 will work.
Solution 2 - anonymous function
The string can be exploded into an array. The array can then be parsed using array_reduce() to produce the result. In my case, the working code (with a check for incorrect/non-existent property names/spellings) was this (PHP 7+):
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
$property = 'parent->requestdata->inputs->firstname';
$name = array_reduce(explode('->', $property), function ($previous, $current) {
return is_numeric($current) ? ($previous[$current] ?? null) : ($previous->$current ?? null); }, $obj);
var_dump($name); //outputs Travis
see this question for potentially relevant information and the code I based my answer on.
Solution 3 - symfony property access component
In my case, it was easy to use composer to require this component. It allows access to properties on arrays and objects using simple strings. You can read about how to use it on the symfony website. The main benefit for me over the other options was the included error checking.
My code ended up looking like this:
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
//don't forget to include the component at the top of your class
//'use Symfony\Component\PropertyAccess\PropertyAccess;'
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
//NOTE: syfony uses dot notation. I could not get standard '->' object notation to work.
$property = 'parent.requestdata.inputs.firstname';
//create symfony property access factory
$propertyAccessor = PropertyAccess::createPropertyAccessor();
//get the desired value
$name = $propertyAccessor->getValue($obj, $property);
var_dump($name); //outputs 'Travis'
All three options will work. Choose the one that works for you.
You're right that you'll have to do a loop iteration for each nested object, but you don't need to loop through "hundreds of properties" for each of them, you just access the one you're looking for:
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
$property = "parent->requestdata->inputs->firstname";
$props = explode("->", $property);
while ($props && $obj !== null) {
$prop = array_shift($props);
$obj = $obj->$prop ?? null;
}
var_dump($obj);
Totally untested but seems like it should work and be fairly performant.

PHP: Sending a list of options as an argument (alternative to named parameters/ argument bag)

I wish to give a list of options as an argument to a function.
The Ideal Scenario: Named Parameters
If PHP has named parameters it would be done like so:
function setOptions($title, $url, $public = true, $placeholder = "type here...") {
...
}
setOptions($title = "Hello World", $url = "example.com", $placeholder = "hi");
Unfortunately PHP does not have named parameters (please tell me if PHP7 is planned to have some as a comment).
The solution everyone else is using: Associative Array
Most PHP scripts I have seen use an alternative array approach like so:
function setOptions($options) {
...
}
setOptions(array(
'title' => "Hello World",
'url' => "example.com",
'placeholder' => "hi"
));
Drawbacks of Associative Array Approach
Although this works fine, there are the following drawbacks:
The user does not benefit from autocompletion (taking a long time to write)
The user can easily makes mistakes in spellings
The don't know what options is available, so may frequently revert back to documentation
Is there a better way?
Is there a better way that can address these issues (either in current PHP or PHP7 or maybe even hacklang(?)).
In Hack, you can use Shapes. Shapes define a structure for associative arrays so that things can be autocompleted (depending on IDE support) and spelling mistakes are picked up by the type checker.
For instance, your example could be reworked like:
function setOptions(shape(
'title' => string,
'url' => string,
'public' => ?bool,
'placeholder' => ?string,
) $options) {
$title = $options['title'];
$url = $options['url'];
$public = Shapes::idx($options, 'public', true);
$placeholder = Shapes::idx($options, 'placeholder', 'type here...');
...
}
setOptions(shape(
'title' => 'Hello World',
'url' => 'example.com',
'placeholder' => 'hi',
));
This marks title and url to both be required options and public and placeholder are optional (all nullable types in shapes are considered to be optional). Shapes::idx is then used to get the value provided, or the default value (the third argument) if a value was not passed in.
Solution: Using fluent setters
A potential solution I have found to this problem is to use classes and fluent setters like so:
class PostOptions {
protected
$title,
$url,
$public = TRUE,
$placeholder = "type here..."; //Default Values can be set here
static function getInstance(): PostOptions {
return new self();
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setUrl($url) {
$this->url = $url;
return $this;
}
public function setPublic($public) {
$this->public = $public;
return $this;
}
public function setPlaceholder($placeholder) {
$this->placeholder = $placeholder;
return $this;
}
}
You can then send the options like so:
function setOptions(PostOptions $postOptions) {
//...
}
setOptions(
PostOptions::getInstance()
->setTitle("Hello World")
->setUrl("example.com")
->setPlaceholder("hi")
);
Doing it quickly! (This looks long)
Although this may look long, it can actually be implemented VERY quickly using IDE tools.
e.g. In InteliJ or PHPStorm, just type ALT+INS > Select setters > Select the fields you want to set and check the checkbox for fluent setters > click OK
Why Fluent Setters? Why Not just make all the fields public?
Using public fields is a LOT slower. This is because fluent setters can make use of chained methods, whilst the public fields way must be written like this:
$options = new PostOptions();
$options->title = "hello";
$options->placeholder = "...";
$options->url "..."
setOptions($options);
Which is a lot more typing compared to the proposed solution
Why is this better?
It's faster in IDE's when using autocomplete than the array approach
Unlikely to make mistakes in spellings (thanks to autocomplete)
Easy to see what options is available (again thanks to autocomplete)
Can give individual documentation for individual fields using PHPDoc
Can use nested options more easily e.g. If you had a list of options, and that option also had more list of options
Other OOP advantages e.g. Inheritance & Abstract Classes
How much faster is this approach?
I implemented a quick class for Wordpress labels array in: https://codex.wordpress.org/Function_Reference/register_post_type
I found that setting a property for each value (with the documentation next to you on a 2nd monitor) that the fluent setters approach is approximately 25% faster than the array approach thanks to autocomplete! However, if the documentation was not next to you, I expect this approach will far exceed 25%, as discovery of options is much quicker!
Alternative approaches are welcome
Declaration from array
This is how I normally declare my class structure. The only drawback is that it takes a while longer to write, but it allows optional parameters, defaults values, etc.
public static $defaults = array(
'user_id' => null,
'username' => null,
'avatar' => null,
'email' => null,
'description' => null,
);
public function __construct(array $args = array()) {
$this->dbc = Database::connection();
$defaults = self::$defaults;
$args = array_merge($defaults, $args);
//Assign the object properites
$this->user_id = (is_numeric($args['user_id'])) ? $args['user_id'] : null;
$this->username = $args['username'];
$this->avatar = AVATAR_DIR . $args['avatar'];
$this->email = $args['email'];
$this->description = $args['description'];
}
This way, you can declare an object like $x = new User(), and it will work perfectly fine. Let's say you've only selected a few columns from your SQL statement. You can make the keys in the public static $defaults into the same name as the columns you've selected, that way to instantiate your object, you can easily do:
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
$object = new User($row);
The array_merge takes care of having any extraneous keys that you don't need in the argument they provided. If you need to change options, you can declare them the same way for __construct() with a default array and array_merge to catch arguments and mimic named parameters and defaults values (like in Python)
With Syntactic: https://github.com/topclaudy/php-syntactic
you can just do:
function foo($a = 1, $b = 2, $c = 3, $d = 4){
return $a * $b * $c * $d;
}
And call it with the arguments you want:
//Call with argument b only
echo s('foo')->in('b', 5)->out(); //Outputs 60
//Call with argument a and argument at index/position 1 (b),
echo s('foo')->in('a', 7)->in(1, 5)->out(); //Outputs 420
//Call with argument c only through dynamic method
echo s('foo')->c(9)->out(); //Outputs 72
If U have that much parameters I'd think about creating an object that you'll pass to class instead of n parameters and every parameter is one field there. In constructor you put required parameters and this is then clean solution.

How to show dirty values when debugging entity

I need use _get so I just did it at User entity just for test:
protected function _getName($name)
{
return $name . ' - FOOBAR';
}
So in the view I did Debug($user), and heres the result:
'properties' => [
'id' => (int) 32,
'name' => 'Daniel Pedro', //<- Clean Value
'email' => 'daniel#gmail.com',
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
As you can notice the property name is with the original value Daniel Pedro, so I thought I did something wrong at _getName but when I look at the input at form the value was Daniel Pedro - FOOBAR.
My question is, how can I show the mutated values at Debug?
Debug the values separately
The most simple way to check the properties with their possible muatated values, is by extracting the visible properties, something like
debug($entity->extract($entity->visibleProperties()));
This won't include the ones that have been defined as "hidden" in the $_hidden property, if you need them too, then you'll have to explicitly include them
debug($entity->extract(array_merge($entity->visibleProperties(), $entity->hidden())));
Extend the debug info
If you'd wanted to somehow include this in the debug output of entities in general, then you'll have to overwrite the EntityTrait::__debugInfo() method and add the mutated properties in there.
Simple example, in your entity class (you can create a base entity class that all your entites extend so that you have this functionality in all entities):
public function __debugInfo()
{
$info = parent::__debugInfo();
$info['propertiesIncludingPossiblyMutatedValues'] =
$this->extract(array_keys($this->_properties));
return $info;
}
Or if you'd wanted to see only the ones that have really been mutated:
public function __debugInfo()
{
$info = parent::__debugInfo();
$info['mutated'] = array_diff(
$this->extract(array_keys($this->_properties)),
$this->_properties
);
return $info;
}
This should give you a hint of how things work.

bad practice? what's it 'called'

in PHP, I'm considering doing something like this:
function foo(){
echo 'bar';
}
$fn = 'foo';
$fn();
It works, but is it considered bad practice?
I have an multidimensional array of elements that each have a corresponding function. I would like to store that function name, and call the corresponding functions for each element when traversing the array.
something like:
function render_el1(){ echo 'et';}
function render_el2(){ echo 'to';}
$elements = array(
'el_1' => array(
'name' => 'Element One'
, 'func' => 'render_el1'
)
, 'el_2' => array(
'name' => 'Element Two'
, 'func' => 'render_el2'
)
);
foreach($elements as $element => $options){
$fn = $options['func'];
echo '<h1>'.$options['name'].'</h1>';
if (function_exists($fn)) {
$fn();
}
}
Any comments to this approach is highly welcome, and I'd also like to know what this method is called in programming terms.
Not sure it is bad practice, but it makes your code hard to understand : to understand your short (5 lines) example, I've had to think :-(
Using call_user_func() and other functions of the same kind could have at least one advantage : looking at the code, one would immediatly understand you are calling a function in a way that's not the one we're generally used to.
You want to register functions into an array in your second example and then call them for what looks like a render process. This is similar to using function pointers in C (or paint event callbacks etc). It is an okay approach if you don't want to/can't use polymorphism (the feature that makes OOP worthwhile).
Your approach is simpler at that stage, but will probably get more bloated if you are adding more sophisticated code.

Constructing Child Objects in PHP

I have an abstract parent class Item, from which different types of items extend: ItemTypeA, ItemTypeB, and ItemTypeC. From my database result, I have an array:
array(
'item_name' => 'This is the item name',
'item_type' => 'ItemTypeA'
);
In PHP, what's the best way to create the item? Should I do something similar to the following?
static function constructFromDatabase($result){
$type = $result['item_type'];
$item = new $type;
return $item;
}
Yes, $item = new $type; should work just fine, source
You can even do return new $result['item_type'];, for performance -and maybe clarity/simplicity- reasons, if you are not going to use the intermediate variables anywhere else.

Categories