I'm designing a PHP Products system which is required to be very general, in that each product may have a number of options ("height", "width", "colour", "gender", etc.) but that some products may not have those options, and some may have none.
For example, a chest of drawers may have "size" (list of appropriate sizes set somewhere by the website user), "polished" (yes/no) and "painted" (yes/no). However, a new TV may have no options at all.
Can anyone recommend a way for the user to set the options they require for their products (for example, the "polished" would not be relevant at all for an electrical retailed) and then to store prices for various combinations of options as they specify for each product?
`options` text null,
//DONT run this directly, it needs escaping...but you get the idea.
insert into tablename values({['polished':(int)1, 'size':'oh_its_big']}), ({['color':'hotpink']});
//run your query
$query = $db->select("* from tablename");
//loop query, json_decode the options string and assign to array
foreach($query as $q)
{
$tmp[] = (array)json_decode($q->options);
foreach($tmp as $options)
{
//run some conditional statements
if(array_key_exists('size', $options))
{
//do something
}
}
}
IMO, This is a Very general question and you need to provide more information on specifics.
One solution that pops up in my mind is as follows
public class myAwesomeClass {
$typeOfMyClass; //got from a DB when a user creates a new type.
$keyAndValuePair; //holds the key=>value pair of the type's data
}
and to access it in the outside world.
$obj = new myAwesomeClass();
//based on $obj->typeOfMyClass value process
foreach ($obj->keyValuePair as $key=>$value) {
//perform your operations
}
Related
I have a function in my API to update the name of a person in an SQLite database. You give it the ID of the name you wish to change and the new name.
How can I build a function in a way that allows me to update a wide range of fields in the database? even things from different tables?
I started off trying to use parameters to switch which SQL query is executed, but this feels a bit clunky and not scalable. Is there a better way?
Current code:
private function json_update_authors() {
$input = json_decode(file_get_contents("php://input"));
$query = "UPDATE authors SET name = :name WHERE authorId = :authorId";
$params = ["name" => $input->name, "authorId" => $input->authorId];
$res = $this->recordset->getJSONRecordSet($query, $params);
return json_encode(array("status" => 200, "message" => "ok"));
}
Prependix
You can achieve what you want, but before reading the details, I recommend contemplating about what you would like to restrict this to, because if there is a file your function blindly trusts, then, should malicious input be inside that file, your database can easily be hacked. So, you should have a whitelist of tables/fields that you allow to be updated and apply that.
Decoding JSON
json_decode decodes your JSON into an object that you do not foresee its members. However, according to the documentation you can iterate this object like:
foreach ($obj as $key => $value) {
echo "$key => $value\n";
}
However, json_decode can decode your JSON into an array as well, like:
$input = json_decode(file_get_contents("php://input"), true);
I personally prefer to decode JSON into arrays, but you can operate with the first approach as well. In both cases, you can iterate the array in a similar manner as described above.
Recommended format
Your update has an anatomy as follows:
table
fields
filter
So, I would recommend that you could use a JSON representation of your input, that has a tableName field, which is a string, a fields field, which is an array of key-value pairs, the keys representing the fields to be updated and the values representing the values to update to and finally a filter field, which, if we intend to be very elegant, could also be an array of objects of key-value pairs, the keys representing the fields you are to filter by and the values representing the values you would filter with. A sample Javascript object that would comply to this format would look like the following:
[
{ //Your query
tableName: 'authors',
fields:
[
{
name: 'somename'
}
],
filters:
[
{
authorId: 123
}
]
},
{ //Some other example
tableName: 'books',
fields:
[
{
isbn: 'someisbn',
title: 'sometitle'
}
],
filters:
[
{
pageNumber: 123,
weight: '5kg'
}
]
},
]
I have given an example above, of two objects, so you can see that:
several updates can be notified in the JSON
you can update several fields in a single command
you can filter by several fields
I should mention that this is a rabbit hole, because you might want to vary the operator as well, but since this is a mere answer, I do not write a full elegant project for its purpose. Instead of that, let me just tell you that there is a lot of room for improvement, operator dynamicity springs to mind instantly as an improvement that you may need.
How to generate an update query:
//assuming that $JSON is a variable holding such values as describe in the previous chapter
foreach ($JSON as $obj) {
$tableName = $obj['tableName'];
$fields = [];
$filters = [];
$params = [];
$toExecute = isset($whiteList['tables'][$tableName]);
foreach ($obj['fields'] as $key => $value) {
$fields[]=($key.'=:field_value'.$key);
$params['field_value'.$key] = $value;
$toExecute = $toExecute && isset($whiteList['fields'][$key]);
}
foreach ($obj['filters'] as $key => $value) {
$filters[]=($key.'=:filter_value'.$key);
$params['filter_value'.$key] = $value;
$toExecute = $toExecute && isset($whiteList['filters'][$key]);
}
}
I have used a whitelist above to make sure that the queries will not update tables/fields using filters where the name of the table/field/filter is either badly formatted, malicious or unwanted. This code is untested, it might well contain typos, but the idea should be a good starting point.
I am trying to implement the "Edit Application Settings" feature. After a bit of thinking, my configuration values are stored in the DB with key -> value structure, like this:
id
key
value
1
logo_path
img/logo.png
As you can see, for each setting, there is only a key & value column. I made an App Service provider to cache them forever, and a helper function (config('setting_key')) to get the value, but now I'd like to update it in the most efficient way.
The user interface consists of the <form action="post" ...> and input with a corresponding name, like this: <input name="setting_key_name" ... />. As you can see, the name attribute here has the value of the key column value and the actual value of the input would be the value column value (a bit of confusion here).
First thing that came to my mind, was to make a foreach loop and find & update every row in DB, but IMHO it is very unoptimized way, cause if the page has a form with 10 values, it is 10 SQL queries. But till now, this is what I've done:
$keys = collect($request->except('_token'))->keys()->toArray();
// get all settings if the key name matches the request's input name
$setting = Setting::whereIn('key', $keys)->get();
$logo = self::GENERAL_APP_LOGO; // contant with a key-name (general_application_logo);
if($request->has(self::GENERAL_APP_LOGO) && $request->$logo) {
// Processing uploaded image here;
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name); // Using an upload trait
$setting->where('key', $logo)->value = self::LOGO_IMAGE_PATH . $name; // just a try to update the DB this way
}
foreach ($keys as $key) {
$setting->where('key', $key)->value = $request->$key; // putting all request's input values to corresponding key
}
$setting->save(); // saving the DB.
As you can see, this won't work and will throw an Exception, like Call to undefined method ...\Eloquent\Builder::save(). I tried the same code with an update, but the difficult part here is to update it multiple times (since the if section should have the update as well, for the logo), as well as binding the key to value.
So, a little bit of your help would be appreciated - what the logic should be here? How can I update a DB rows with corresponding column's value? I mean - like this (update where key = 'general_app_name' set value, 'some_setting_value'), but using the optimized and clear way?
Working solution
As #miken32 stated in his answer, I used hid version of code, but with slight changes:
// Changed the $request->settings->keys() to PHP native method array_keys():
$settings = Settings::whereIn('key', array_keys($request->settings))->get()->groupBy('id');
// Also, here I changed the `whereIn('id', ...)` to `whereIn('key', ...)`, since it was my primary index.
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
// Here I added the ->first() to get the first element from the retrieved collection;
$setting = $settings->get($k)->first();
$setting->value = $v;
$setting->save();
}
Since I was fetching the configuration values via helper, that only returns the value of the current key (and no id column), I changed the id to key and made the key as my PK in a model. Works like a charm!
With each setting in a separate row, there's no way to avoid multiple database queries – one to get the current values for all settings, and other to update each one. Looking up items by primary key is more efficient, so I'd recommend putting the contents of the id column in your blade view, like this:
<label for="setting_{{$setting->id}}">{{$setting->key}}</label>
<input name="settings[{{$setting->id}}]" id="setting_{{$setting->id}}" value="{{$setting->value}}"/>
Now in your controller, $request->settings will be an array you can loop through. You can continue treating your file upload separately, but now you've got the id column to look up, so change your constant to that.
$settings = Settings::whereIn('id', $request->settings->keys())->get()->groupBy('id');
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
$setting = $settings->get($k);
$setting->value = $v;
$setting->save();
}
Note that Laravel does offer methods to bulk-update multiple models at once, but they are doing separate queries to the database in the background. IIRC, the save() method doesn't do anything if the value hasn't changed, which will spare you some hits.
You could try creating a text field, or a json field if your database supports it, and storing all of your settings as a JSON string in that field.
id
settings
1
{ "logo_path" : "img/logo.png", "foo" : "bar", "thing_count" : 17 }
2
{ "logo_path" : "img/logo2.png", "foo" : "baz", "thing_count" : 4 }
In your Laravel model, you can cast it as an array
protected $casts = ["settings" => "array"];
and then use it from the model
echo $theModel->settings['logo'];
echo $theModel->settings['foo'];
or you can cast it as a fully fledged object if you need to using value object casting.
One gotcha that can be confusing for people is the setting of the values in the array to update it. This will not work:
$theModel->settings['foo'] = "boz";
The reason is due to the way the Laravel mutators work. Instead, you make a value copy of the settings, change that, and reassign it to the model:
$settings = $theModel->settings;
$settings['foo'] = "boz";
$theModel->settings = $settings;
This approach has the capacity to infinitely expandable in the future as you just add new keys to your json. Be sure to do checks on the settings array to ensure fields you are looking for are set (which is why value objects can be very handy to do validation).
It also solves your database query problem - it's only ever one.
You don't need to put
$setting->where('key', $logo)->value = ...;
Just call
$setting->where('key', $logo)->update($request->toArray());
$setting->save(); called when you instantiated setting class like :
$setting = new Setting();
Or
$setting = Setting::whereIn('key', $keys)->get()->first();
Then
$setting->val = ...;
$setting->save(); // then it work's
I have list of items that need to be processed and printed to the user.
Each item on the list needs to have a unique set of calculations to it.
The only thing is, the user also supplies me with the order of how items will be printed & handled first - for example: item4,item2,item1,item3
How can one approach this in PHP?
I thought of running a for loop against the user submitted ordered list and tackle each calculation and print it to the user, the problem is that this will be hard to maintain because the user will have to edit the for loop each time he will want to add a new item or calculation.
Given a list of 1-n items:
$items['item1'] = $item1;
With a co-related list of 1-n functions:
$functions['item1'] = function($item) {return ...;};
You can sort items according to the user-input:
$subset = orderAndFilterItems($items, $userInput);
and then iterate:
foreach($subset as $key => $item)
{
$function = $functions[$key];
$value = $function($item);
}
Naturally you can encapsulate this further on, but it should give you the idea.
I have a set of Organizations and their Board Members.
All organizations have board members and many board members are on the board of more than one organization.
I am using JIT Hypertree to illustrate their relationships. The JIT Hypertree schema requires that one item be the parent of all and is drawn based on a single JSON array.
I would love to have the re-centering event query and re-populate the graph based on the change. Then 2 levels would be fine but I have not been able to work out how to do that.
The code I have at present recurses manually for three levels from the starting organization but what I want is to re-curse through all related records.
So it would start with an Org and add Org's array of children (board members). Then fetch all of the boards (other than current Org) for each board member and add those as children of the board member.
This would continue until each trail dead ends - presumably at a board member who only belongs to one board.
Anyone have advice on how to create this array and avoid duplicates?
$board = $center->board();
$top['id'] = $center->ID;
$top['name'] = $center->Org;
$top['children'] = array();
if ($board) {
foreach ($board as $b) {
$child['id'] = $b->ID;
$child['name'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$child['data']['orgname'] = $center->Org;
$child['data']['relation'] = $b->Role;
$child['data']['occupation'] = $b->Occupation;
$child['children'] = array();
$childboards = $b->boards();
if ($childboards) { foreach ($childboards as $cb) {
$gchild['id'] = $cb->ID;
$gchild['name'] = $cb->Org;
$gchild['data']['orgname'] = (strlen(trim($b->Last)) > 0) ? $b->First . ' ' . $b->Last : 'Unknown';
$gchild['children'] = array();
$childboardmembers = $cb->board();
if ($childboardmembers) { foreach ($childboardmembers as $cbm) {
$ggchild['id'] = $cbm->ID;
$ggchild['name'] = (strlen(trim($cbm->Last)) > 0) ? $cbm->First . ' ' . $cbm->Last : 'Unknown';
$ggchild['data']['orgname'] = $cb->Org;
$ggchild['data']['relation'] = $cbm->Role;
$ggchild['data']['occupation'] = $cbm->Occupation;
$ggchild['children'] = array();
$gchild['children'][]= $ggchild;
}}
$child['children'][]= $gchild;
}}
$top['children'][] = $child;
}
}
$top['data'] = array();
$top['data']['description'] = $center->Desc;
echo json_encode($top);
// Edit 2011.10.24 In Re hakre response
My data structure is a table of Organizations with unique IDs, a table of People with Unique IDs, and then a bridging table for the two specifying Organization (Entity) and Person and the Role the Person is playing in the Entity. A typical many-to-many. No sub-boards at all. I made an image of it which now seems kind of pointless but I'll add it at the bottom.
The JIT library data structure is a little nuts (to me) in that it goes like this in their band example:
Top: Nine Inch Nails
Child: Jerome Dillon
Child: Howlin Maggie (another band)
{all the bands' members and then all of their bands...}
So the organization (band) is treated as though it is a Person even though it is comprised of a number of Persons. And when I recurse using the code above I get (I think) terrible bloat but the JSON it makes works correctly despite bloat.
Example JSON and Example Visualization
// End Edit
Your question is hard to answer in the sense that your data-structure is mainly unknown.
For the graphical represenation you only need to provide simple relationships if I understand that correctly:
*- Parent
+- Child
+- Child
...
`- Child
Your data structure has a different format, I don't know specifically but it's something like:
Org <-- 1:n --> Board
Board <-- n:n --> Board # If you have sub-boards
Board <-- n:n --> Member
Whichever your data is represented, to map or transpose your data onto the required structure for the graphical representation, you need some functions that take care of that.
To do that you need to share classification/type between both and specific keys, so that you can look-up the needed data from the event to return the data. For example:
if (request_parent_is_org())
{
$id = request_parent_id();
$parent = data_get_board($id);
$parent->addChildrent(data_get_board_children($id));
}
else
{
... # All the other decisions you need to take based on request type
}
view_response_to_json($parent);
What you have with your many-to-many data model is a graph. JIT is designed for trees.
To put it another way, JIT will not correctly show the crossing lines that are represented in the data whenever a single person is connected to multiple organizations.
I'd recommend a proper network graph visualization - D3.js has a great implementation for modern browsers.
The JSON data format it uses is actually easier to implement given your table structure - for all the organizations and people, you define objects:
{
"name": "Mme.Hucheloup",
"group": 1
},
{
"name": "Afton School Board",
"group": 2
}
And for each association in your association table you define connection objects that wire them together:
{
"source": 1,
"target": 2
},
The fancy coding in D3 takes care of the rest. Good luck!
You can use function below:
function get_orgs_and_childs ($child_id, $found = array())
{
array_push ($found, $child['name']);
if($child['children'])){
$found[] = get_parents($child['id'], $found);
}
return $found;
}
Call it using:
$orgs = get_orgs_and_childs($child['id']);
I have a smaller web app that republishes content from one social source to another. Users have, let's say 5 options for how to filter that content. Their prefs are stored in my user DB as 1 or 0 markers.
When I do my major "publish" action hourly, what's the best way to break up my code to implement these preferences? To make it more clear, my current implementation is something like this:
mysql_query(SELECT * FROM users);
foreach ($user as $row){
get_json_data($userID);
if ($pref1 == 1){
/code that enacts preference 1, adds results to array $filtereddata
}
if ($pre2 == 1 ){
/code that filters out preference 2, adds results to $filtereddata
{
postfinalarray($filtereddata);
}
Obviously this is mock code, but that's the general flow I've been using. Is there a better way to implement customizing a function with user's preferences? Should I design the function to accept these preferences as parameters? Will that save processing time or be more maintainable?
Sorry if this is too general, please ask questions so I can clarify.
Well of course there are many possibilities to improve this kind of code. Just imagine, if you would add another preference you would have to rewrite your whole code.
I always try to use the object oriented way so creating a user class always makes sense:
<?php
class User
{
private $_data;
public function __construct($userdata, $preferences)
{
$this->_data = $userdata;
}
public function getPreferencesData($preferences)
{
$result = array();
$prefs = $this->_data['preferences'];
foreach($preferences as $key => $value)
{
if($value == "1")
$result[] = $this->_data[$key];
}
return $result;
}
}
$mypreferences = array("name" => 1, "birthdate" => 0); // show e.g. name and birthdate
$mydata = array("name" => "Gaius Julius Caesar", "birthdate" => "July 13th -100");
$testuser = new User($mydata);
print_r($test->getPreferencesData($mypreferences));
?>
Then you can easily adapt to whatefer preferences the user has chosen without having to change your code.
From the limited info you've provided, your method seems fine.
I would do something slightly different probably. I would have a table of your 5 prefs (or would just hard code them if they're very unlikely to change). I would create a cross-reference table linking the prefs and users. Then you can do something like:
foreach($users as $user)
{
$filtereddata = array();
foreach($user->getPreferences() as $pref)
{
$filtereddata += $this->getFilteredData($user, $pref);
}
}
This is more overhead than what you're using, but more easily extensible if/when you add preferences/features.