Handling Single Array Values - php

This is using WordPress lingo - but is more of a core PHP based question. I have an option in my site backend that I am storing a specific Location (a WordPress custom post type) as the "headquarters". I am storing the value as such:
function options_headquarters() {
foreach(get_field('option_headquarters','options') as $post_object) {
$options_headquarters = $post_object->ID;
return $options_headquarters;
}
}
What I am fuzzy on is - since my option is only allowed to grab one value (option is configured to just be a single dropdown) -- is there an alternative to using a foreach statement (to grab just a specific array value) and still applying it to the post_object?
Thanks!

function options_headquarters() {
$options = get_field('option_headquarters','options');
$options_hq = isset($options[0]) ? $options[0]->ID : NULL;
return $options_hq;
}

Just get the array and access the first element:
$post_object = array_pop(get_field('option_headquarters','options'));
Reference: array_pop

Related

Best way to use multiple URL paramemetrs for advacned filtering with PHP?

I wonder if there is a "simple" PHP solution to create multiple URL parameters. Until now I am able to create a "one click" filter like this:
$url_param = ($_GET["sort"]);
Sort by this
and then:
if($url_param == 'rank-chipset') {do this}
This works great as a "one click" URL parameter for filtering! I like this simple solution to do things with URL parameters, but can't figure out how to do something similar so i can give the option to users to select multiple parameters, like brand1, brand2, year 2021 and more.
I would prefer this to work like this: If users clicks brand1 filter then instantly reload page and show brand1 products, after if also clicks brand2 then show also brand1 and brand2 products. If users clicks again brand1 remove brand1 from filtering.
Make the filter parameter a comma-delimited list of filters. Then combine the existing value of $_GET['filter'] with the filter for that link.
function add_or_remove_filter($new, $filters) {
$pos = array_search($new, $filters);
if ($pos === false) {
// add if not found
$filters[] = $new;
} else {
/remove if found
unset($filters[$pos]);
}
return implode(',', $filters);
}
$filters = explode(',', $_GET['filter'] ?? '');
?>
Brand 1
Brand 2
Year 2021
If you are going to design a solution that writes the navigation history directly in the url, then set up the storage with the goal of easy/swift data mutation.
Code: (untested)
function toggleItem($item, $cache) {
$cache[$item] = isset($cache[$item]) ? null : 1;
return $cache;
}
// show array_keys($_GET) as you wish
$allItems = ['a', 'b', 'c', 'd'];
foreach ($allItems as $item) {
printf(
'%s',
http_build_query(toggleItem($item, $_GET)),
$item
);
}
The advantage in this is that the values are always stored as array keys. This means checking for their existence is optimized for speed and http_build_query() will ensure that a valid querystring is generated.
A special (perhaps unexpected for the unknowing developer) behavior of http_build_query() ensures that an element with a null value will be stripped from its output (the key will not be represented at all). This acts as if unset() was used on the array.
If you want keep these item values in a specific subarray of $_GET, you can adjust the snippet for that too. You would set this up with $_GET['nav'] ?? [] and access array_keys($_GET['nav'] ?? []). One caveat to this performance boost is that PHP's array keys may not be floats -- they get automatically converted to integers. (I don't know the variability of your items.)

Laravel Eloquent: any way to update DB with dynamic fields?

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

Adding items to a php array without replacing the previous items

Am building a shopping cart and would like to append new items to a wishlist by adding them to a session but they shouldnt replace the existing items
This is what i have tried but returns an error of
[] operator not supported for strings
This is the code:
public function addItem($value, (int)$id){
if(isset($_SESSION[$value])){
$_SESSION[$value][] = array();
array_push($_SESSION[$value],array(
'id'=>$id
));
return true;
}
}
The values of $value is a string
I have also followed on This yii link and also on This link but still am getting the same error
By doing it this way
public function addItem($value, $id){
if(isset($_SESSION[$value])){
$_SESSION[$value] = array();
array_push($_SESSION[$value],array(
'id'=>$id
));
return true;
}
}
Adds the items but replaces whatever i had previously
WHAT DO I NEED TO CHANGE FOR IT TO WORK
You get this error when attempting to use the short array push syntax on a string. demo here.
$_SESSION[$value] is a string. so you cannot use something like $_SESSION[$value][]= 's'
So when the first time you use the $_SESSION[$value], make it an array. Not a string. Then you can use as $_SESSION[$value][]= 's';

Drupal field value via php

I am trying to filter a view of Biblio nodes via Author ID. On the staff profiles, I have a field (field_author_id_ccsi) which contains the ID (an integer). How can I reference that via PHP in the contextual filter for views (under Advanced). Here's what I have so far but it's not working:
$node = node_load(arg(1));
if($node->field_author_id_ccsi[und][0]->value)
return $node->field_author_id_ccsi[und][0]->value;
}
else {
return;
}
You will need to open up you array object $node->field_author_id_ccsi to see where you ID is located and then drill down to it.
For example:
Let $node->field_author_id_ccsi['und'] be your array of interest.
You will need to investigate the contents of said array to see what is actually in that array. If you ID is the first element in the array then:
echo $node->field_author_id_ccsi['und'][0];
will show the integer on the screen in your view.
However a var_dump($node->field_author_id_ccsi['und']); will show you exactly where you ID is located - if it is there, as will a print_r();. You may need to use a foreach() to traverse the array if it is a multi-dimentional array.
Here's the code I used which works:
$node = node_load(arg(1));
if($node && isset($node->field_author_id_ccsi)) {
return $node->field_author_id_ccsi['und'][0]['value'];
}
return;

How can I call a function specified in a GET param?

I have a simple link like so: Accept
The idea is that this will run a function called wp_accept_function and pass in the id of 10 how do I do this? Thanks
Here is the code I have so far, but I feel I'm going wrong and need to pass the number into the function and then be able to use it within the function. Thanks
if ( isset ( $_GET ['wp_accept_function'] ) )
{
function wp_accept_favor ( $id )
{
// JAZZ
}
}
I think you want this:
First you need to define the function.
function wp_accept_favor($id) {
// do something
}
Then, you have to check if the parameter is set and call the function.
if (isset($_GET['wp_accept_function'])) {
// call the function passing the id casted to an integer
wp_accept_favor((int)$_GET['wp_accept_function']);
}
The cast to (int) is for avoid passing a non-integer type for wp_accept_favor() function, but you can handle it as you want.
If you are trying to build something generic...
// Add a white list of functions here which can be called via GET.
$safeFunctions = array('wp_accept_function');
foreach($_GET as $function => $argument) {
if (in_array($function, $safeFunctions)
AND function_exists($function)) {
$function($argument);
}
}
However, make sure you have a whitelist of safe functions, otherwise your app will no doubt have security issues.

Categories