PHP - Perform parsing rules on nested array - php

So I have a nested array, that mimics a table layout (columns and rows):
{
"1": [
{
"row": "My name is Trevor\n"
},
{
"row": "Can you see me?\n"
},
{
"row": "\f"
}
],
"2": [
{
"row": Hey there! Some other text.\n"
},
{
"row": "What is up?\n"
},
{
"row": "\f"
}
],
"3": [
{
"row": "Some text on the third column. First row."
},
{
"row": "\f"
}
]
}
So "1", "2", "3" are the columns and then under each column, there can be any number of rows.
Now I am trying to do, so my users can perform various parsing rules on either:
All columns and all rows.
Specific columns and all rows.
Whenever a column / row has been parsed, it should be returned to the "original array".
For this, I have created a class that will apply the different parsing rules I have in specified. Getting the parsing rule works fine. I am currently stuck in the actual text transformation/parsing aspect.
Consider I have a parsing rule called "regexTextReplace", that looks like this:
class regexTextReplace
{
private $pattern;
private $replacement;
public function __construct(array $arguments)
{
$this->pattern = $arguments['pattern'];
$this->replacement = $arguments['replacement'];
}
public function apply(array $table, $column = false): array
{
$table = $column ? $table[$column] : $table;
return array_map('self::regex_replace', $table);
}
public function regex_replace(array $table)
{
return preg_replace($this->pattern, $this->replacement, $table);
}
}
This is how I'm using it:
$options = [
'pattern' => '/Trevor/i',
'replacement' => 'Oliver',
];
$engine = new regexTextReplace($options);
$columns = $engine->apply($document->content, 1); //"1" is the specific column.
$columns returns:
[
{
"row": "My name is Oliver\n"
},
{
"row": "Can you see my?\n"
},
{
"row": "\f"
}
]
Two problems here:
It successfully apply the parsing rule (Trever is replaced with Oliver). But it only returns the first column, but I want the entire original array to be transformed.
If I remove the 1 from the apply() method, I get below error:
Array to string conversion
on below line:
return preg_replace($this->pattern, $this->replacement, $table);
Can anyone guide me in the right direction, so I can perform my parsing rule on any column or on all columns, and return the transformed data back to my original array?

I would rewrite the apply function to loop over the entire table, processing each column if the column argument is not set, or if it matches the current table column:
public function apply(array $table, $column = false): array
{
$out = array();
foreach ($table as $col => $rows) {
if ($column === false || $col == $column) {
$out[$col] = array_map('self::regex_replace', $rows);
}
else {
$out[$col] = $rows;
}
}
return $out;
}
Demo on 3v4l.org

You could rewrite your apply method to this:
public function apply(array $table, $columns = false): array
{
$columns = $columns === false ? array_keys($table) : (array)$columns;
return array_map(function ($column) use ($table, $columns) {
return in_array($column, $columns) ? array_map('self::regex_replace', $table[$column]) : $table[$column];
}, array_keys($table));
}
You can pass either a single column, or an array of columns, or nothing (false) to specify the columns you want adjusted.
Demo: https://3v4l.org/Kn4FY

Just loop it and do the regex on all subarrays:
$content = json_decode($json, true);
$options = [
'pattern' => '/Trevor/i',
'replacement' => 'Oliver',
];
$engine = new regexTextReplace($options);
foreach($content as $key => $v){
$columns[$key] = $engine->apply($content, $key);
}
var_dump($columns);
Working demo:
https://3v4l.org/Pk2rC
The benefit of looping in the "PHP" side instead of in the class is that you can still apply the regex to only one or two of the subarrays.
If you loop in the class then you need to pass more arguments to restrict the looping or do some type of array slicing.

Related

Array merge on basis of same keys but different keys value should add as separate array

I have data like I have pasted bellow. I want to combine on the basis of name i.e abc name template is in different language i want it in the information name, category and All its languages on the same index.
[{
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"de": "Deutsch",
"de_status": "APPROVED"
}]
}, {
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"en": "English",
"en_status": "APPROVED"
}]
}, ....
As mentioned above I want the result as json pasted below.
[{
"name": "abc",
"category": "new_cat",
"selectedLanguage": [{
"de": "Deutsch",
"de_status": "APPROVED"
}, {
"en": "English",
"en_status": "APPROVED"
}]
},...{
"name": "unique_temp",
"category": "TICKET_UPDATE",
"selectedLanguage": [{
"en": "English",
"en_status": "REJECTED"
}, {
"fr": "French",
"fr_status": "REJECTED"
}]
}]
I have written a code for it as
$trimArr; //this data array
$finalTempArr = array();
$finalArr = array();
foreach ($trimArr as $tr) {
$checkIfExist = $this ->in_array_r($wr['name'], $finalArr);
if($checkIfExist == false){
$finalTempArr['name'] = $tr['name'];
$finalTempArr['category'] = $tr['category'];
$finalTempArr['selectedLanguage'] = $tr['selectedLanguage'];
}else {
array_push($finalTempArr['selectedLanguage'],$tr['selectedLanguage']);
}
array_push($finalArr, $finalTempArr);
}
echo json_encode($finalArr);
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item)
&& $this->in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
You can just use array_reduce to accomplish it:
$result = array_reduce($array, static function(array $carry, array $item) {
$key = $item['name'] . $item['category'];
if (isset($carry[$key])) {
$carry[$key]['selectedLanguage'] = array_merge($carry[$key]['selectedLanguage'], $item['selectedLanguage']);
} else {
$carry[$key] = $item;
}
return $carry;
}, []);
if you need fresh keys after reducing, then you can use array_values on the result:
$result = array_values($result);
A simple foreach loop will do.
I want to combine on the basis of name
So just use the name as the temporary key while you group and merge the data.
Code: (Demo)
$result = [];
foreach (json_decode($json, true) as $row) {
if (!isset($result[$row['name']])) {
$result[$row['name']] = $row;
} else {
$result[$row['name']]['selectedLanguage'] = array_merge($result[$row['name']]['selectedLanguage'], $row['selectedLanguage']);
}
}
var_export(array_values($result));
The more I think about, your subarray structure is probably not fit for purpose. If you are eventually going to be searching for available languages to return the language status, it will be better to set up the array structure to be a "lookup". In other words, each selectedLanguage subarray should be a flat associative array using the language as the key and the language_status as the value. This would look like "en" => "APPROVED".
Alternative, if your scripts are already relying on the en key to find the appropriate values, then keep en as the key and make a flat associative subarray from en. I can't really say with 100% confidence what you should do, but I am reasonably sure that your data structure could be improved. Once you ditch the indexed subarray structure, you then may be able to implement a recursive merge/replace technique.

Implementing filter of nested arrays in PHP/Wordpress

I have in the process of moving some code from the front-end (in JavaScript) to the server-side (which is PHP) where it will be filtered and sent out in an API call, and I can't seem to get the filter working properly on the back-end. The code takes an array of objects and filters it for the objects where a certain nested field (which is also an array of objects) contains certain values. The basic shape of the API:
{
"id": 1217,
"name": "Best product ever",
"tags": [
{
"id": 125,
"name": "Important Value",
"slug": "important-value"
},
{
"id": 157,
"name": "Value",
"slug": "value"
},
{
"id": 180,
"name": "Value",
"slug": "value"
},
{
"id": 126,
"name": "Value",
"slug": "value"
},
{
"id": 206,
"name": "Other Important Value",
"slug": "other-important-value"
}
}
The working JS code:
let productAttributes = ['important-value', 'value', 'value', 'value', 'other-important-value'];
filterResults(results) {
let filteredResults = results.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[0];
});
});
if (this.productAttributes[0] !== 'certain important value') {
filteredResults = filteredResults.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[4];
});
});
}
return filteredResults;
}
And the (not yet working) PHP code:
function get_awesome_products() {
$baseRequest = 'https://myawesomeapi/wp-json/wc/v3/products/?
consumer_key=xxxx&consumer_secret=xxxx&per_page=100&page=';
for ($count = 1; $count <= 9; $count++ ) {
$request = wp_remote_get( $baseRequest . (string)$count);
$body = wp_remote_retrieve_body( $request );
$data = array_values( json_decode( $body, true ));
if ($count < 2) {
$completeProductList = $data;
} else {
$completeProductList = array_merge($completeProductList, $data);
}
}
// The code above this comment is doing what I expect, the code below is not.
$filteredProducts = null;
foreach ($completeProductList as &$product) {
$tagArray = $product['tags'];
if (in_array($reg_test_array[0], $tagArray, true) &&
in_array($reg_test_array[4], $tagArray, true))
{
array_push($filteredProducts, $product);
}
unset($product);
return new WP_REST_Response($filteredProducts, 200);
The impression I get is that I need to write a custom function to take the place of Array.prototype.find(), but I'm not strong in PHP and am having trouble wrapping my head around it.
EDIT: Edited to add example of object being filtered and additional PHP code
You could also use the PHP equivalent function array_filter (among a few other array-specific functions) for this task.
Example:
// Your 0 and 4 index values from $reg_test_array
$importantTags = [ "important-value", "other-important-value" ];
$filteredProducts = array_filter($completeProductList, function($product) use ($importantTags) {
return (bool)array_intersect($importantTags, array_column($product['tags'], 'slug'));
});
return new WP_REST_Response($filteredProducts , 200);
Sandbox
This should be equivalent to the JavaScript code you posted, but done without looping through the filtered results twice.
Without knowing the context of important-value and other-important-value, and how they come to be ordered in the $attributes array, it's a little difficult to improve upon the conditional checks used. What I've written thus far however feels like a code smell to me, because it's reliant hard coded values.
function filterResults(array $results, array $attributes)
{
return array_reduce($results, function ($filteredResults, $result) use ($attributes) {
// Extract tag slugs from result
$tagSlugs = array_column($result['tags'], 'slug');
// Append result to filtered results where first attribute exists in tag slugs;
// Or first attribute is not *other-important-value* and fourth attribute exists in tag slugs
if (in_array($attribute[0], $tagSlugs) && ($attribute[0] === 'other-important-value' || in_array($attribute[4], $tagSlugs))) {
$filteredResults[] = $result;
}
return $filteredResults;
}, []);
}

How to show array in Json with Fractal?

I want to save REST API data with array to database, its work, all value save to the table, but i still get error when i want to view the result in json.
Here is my error
"message": "Argument 1 passed to App\\Transformers\\NewLoanOfferTransformer::transform() must be an instance of App\\Model\\AntiAttrition, array given, called in /home/insko23/testing.insko.my/vendor/league/fractal/src/Scope.php on line 338",
My Controller
public function new_loan_offer(Request $request, AntiAttrition $antiattrition)
{
$new = array_map(null, $request->mo_id, $request->id_clrt,$request->TaskID,$request->ACID,$request->CustIDNo);
foreach($new as $new) {
$request = new AntiAttrition;
$request->mo_id = $new[0];
$request->id_clrt = $new[1];
$request->TaskID = $new[2];
$request->ACID = $new[3];
$request->CustIDNo = $new[4];
$request->save();
}
$response = fractal()
->item($new)
->transformWith(new NewLoanOfferTransformer)
->toArray();
return response()->json($response,201);
}
My App\Transformer
<?php
namespace App\Transformers;
use App\Model\AntiAttrition;
use App\Model\SettlementInfo;
use League\Fractal\TransformerAbstract;
class NewLoanOfferTransformer extends TransformerAbstract
{
public function transform (AntiAttrition $antiattrition)
{
return[
'id' => $antiattrition->id,
'mo_id'=> $antiattrition->mo_id,
'assign_by'=> $antiattrition->assigned_by,
'id_clrt' => $antiattrition->id_clrt,
'TaskID'=> $antiattrition->TaskID,
'ACID'=> $antiattrition->ACID,
'CustIDNo'=> $antiattrition->CustIDNo
];
}
}
I want to show the result in json, like below
{
"data": [
{
"mo_id": "123",
"id_clrt": "10000000049",
"ACID": "123",
.....
},
{
"mo_id": "1234",
"id_clrt": "10000000045",
"ACID": "1235",
.....
},
{
"mo_id": "124",
"id_clrt": "10000000044",
"ACID": "1245",
.....
},
],
}
Please help me to solve this problem
In the foreach loop, avoid using same name for array and elements of the array you are iterating over, you can rename foreach($new as $new) to foreach($newArray as $new), or something meaningful with your code logic. Also rather than using $new[0] use $new['mo_id'] for referring to the key of the array

JSON Structure changed, breaks methods

Currently, I have below JSON stored in my database:
{
"1": [
{
"row": "My name is Trevor"
}
],
"2": [
{
"row": "Hey there! Some other text."
}
],
"3": [
{
"row": "And more."
}
]
}
Now, the third party API that I am using have changed their output format to:
[
{
"0":"My name is Trevor"
},
{
"0":"Hey there! Some other text."
},
{
"0":"And more."
}
]
I have a PHP function that reads the array-like colums and transforms each column/row. I can call it like:
public function apply(array $table) : array
{
return $this->applyRule($table);
}
Which calls this:
public function applyRule(array $table): array
{
$out = [];
foreach ($table as $col => $rows) {
$out[$col] = array_map([$this, 'rule'], $rows);
}
return $out;
}
Which ultimately calls the parsing rule, like so:
public function rule($content) : array
{
return preg_replace($this->pattern, $this->replacement, $content);
}
However, running above gives me below error:
regexTextReplace::rule() must be of the type array, string returned
I suspect that due to the change in the JSON structure, my parsing functions no longer work.
I am not sure what needs to be changed - can someone assist me?
Edit:
So looking at the answer below, adding [$rows] instead of $rows fixes the error, but ultimately creates a nested array it seems.
If I do a die-dump like:
dd($rows);
It actually does return an array:
array:3 [▼
0 => "My name is Trevor"
1 => ""
2 => ""
]
So why is it seen as a string?
You can send $rows as an array to the rule() function, just wrapping it in []:
array_map([$this, 'rule'], [$rows]);
Then the function will receive an array, not a string.
Otherwise, you can refactor your code and use a string instead, but I can't see much advantage.

PHP/Laravel: How to insert a new json field when creating JSON in PHP?

I am returning JSON to my frontend like this:
public function newFlavorOrders()
{
$orders = request()->user()->ordersPaid;
return response()->json(['flavor_orders' => $orders]);
}
and right now, that returns this to the frontend:
{ orders: [
{
color: "Green"
size: "Large",
order_products: [ {'itemNum': 3, 'imgUrl': "zera.jpg"}, {'itemNum': 5, 'imgUrl': "murto.jpg"} ]
},
{
color: "Blue"
size: "Large",
order_products: [ {'itemNum': 3, 'imgUrl': "mcue.jpg"}, {'itemNum': 5, 'imgUrl': "cloa.jpg"} ]
}
]
}
But I want to alter the controller PHP function to add a field to each order_products item. I have the imgURL, but I want to add a processedImgUrl and stub it with true right now. How can I add the field to the above php function when returning the JSON?
Without the dataset this may not be exactly accurate but the way to do this is either to perform an array push or do a foreach loop and add create the index to be appended.
For example:
public function newFlavorOrders()
{
// CREATE A NEW ARRAY TO ADD THE MODIFIED DATA TO
$modified = array();
$orders = request()->user()->ordersPaid;
// LOOP THROUGH AND ADD THE VALUE TO THE ITERATION
foreach($orders as $row) {
foreach($row['order_products'] as $val){
$modified = $val;
if(!empty($val['imgUrl'])){
$modified['processedImgUrl'] = TRUE;
} else {
$modified['processedImgUrl'] = FALSE;
}
}
}
return response()->json(['flavor_orders' => $modified]);
}
Something like this should work. You need to loop through the first array, then get down to the next level array (order_products).
public function newFlavorOrders()
{
$orders = request()->user()->ordersPaid;
$orders = $orders->map(function ($order) {
$order->order_products = $order->order_products->map(function ($products) {
$products['processedImgUrl'] = true;
return $products;
});
return $order;
});
return response()->json(['flavor_orders' => $orders]);
}

Categories