PHP: array search when array doesn't start on zero - php

I'm trying to find a key in an array that doesn't start with zero.
This is my not so elegant solution:
private $imagetypes = [
1 => [
'name' => 'banner_big',
'path' => 'campaigns/big/'
],
2 => [
'name' => 'banner_small',
'path' => 'campaigns/small/'
],
// ...
If i use $key = array_search('banner_big', array_column($this->imagetypes, 'name')); the result is 0
I came up with this solution but I feel like I needlessly complicated the code:
/**
* #param string $name
* #return int
*/
public function getImagetypeFromName($name)
{
$keys = array_keys($this->imagetypes);
$key = array_search($name, array_column($this->imagetypes, 'name'));
if ($key !== false && array_key_exists($key, $keys)) {
return $keys[$key];
}
return -1;
}
Is there a better solution then this.
I can't change the keys in.

Just save indexes
$key = array_search('banner_big',
array_combine(array_keys($imagetypes),
array_column($imagetypes, 'name')));
demo on eval.in

The problem is array_column will return a new array (without the existing indexes)
So in your example.
$key = array_search('banner_big', array_column($this->imagetypes, 'name'));
var_dump($key);
//$key is 0 as 0 is the key for the first element in the array returned by array_column.
You can mitigate against this by creating a new array with the existing keys.

That's because array_column() generates another array (starting at
index zero), as you may have imagined. An idea to solve this would be to
transform the array with array_map(), reducing it to key and image
name (which is what you're searching for). The keys will be the same,
and this can be achieved with a simple callback:
function($e) {
return $e['name'];
}
So, a full implementation for your case:
public function
getImagetypeFromName($name)
{
$key = array_search($name, array_map(function($e) {
return $e['name'];
}, $this->imagetypes));
return $key ?: -1;
}

Related

Laravel Remove [data] from collection

I've tried to query using eloquent and fractal
$lists = Category::all();
$result = Fractal::collection($lists, new CategoryTransformer())->getArray();
and return it
return response()->json((['code' => "200", 'results' => $result]));
the json result is this:
{"code":"200","results":{"data":[{"id":"1","name":"Cafe","logo":null,"cover":""},{"id":"2","name":"SPA","logo":null,"cover":""},{"id":"3","name":"Hotel","logo":null,"cover":""}]}}
How to remove "data" after result?. So i can just get the array without "data".
I've tried:
$result = Fractal::collection($lists, new CategoryTransformer(), 'results')->getArray();
return (['code' => "200", $result]);
it return me :
{"code":"200","0":{"results":[{"id":"1","name":"Cafe","logo":"","cover":""},{"id":"2","name":"SPA","logo":"","cover":""},{"id":"3","name":"Hotel","logo":"","cover":""}]}}
There is leading '0' before results. how can i remove it?
Thanks
Try this:
return (['code' => "200", "results" => $result['results']);
I think the array method can't deal with a given array.
An other solution would be to add your results:
$result['code'] = 200;
return $result;
The data is just the key, I think it won't make any issues. If you still need to remove it, update getArray() function.
Put these Collection Macros in your AppServiceProvider::boot() method:
/**
* Remove the unnecessary nested 'data' keys
*
* #param string $case For consistency, define the type of keys that should be returned
*/
Collection::macro('fractal', function ($case = 'snake_case') {
//Handle this as a nested function to block access to the $depth flag.
//It's purpose is to indicate how deep the recursion is, and,
//more importantly, when it's handling the top-level instance
$recursion = function ($case = 'snake_case', array $items = [], $depth = 0) use (&$recursion) {
//If the array has only one element in it, and it's keyed off 'data', remove the wrapper.
//However, if it has a sibling element, such as 'meta', leave it alone
if (array_key_exists('data', $items) && count($items) == 1) {
$items = $items['data'];
}
$items = (new static($items))->mapWithKeys_v2(function ($item, $key) use (
$case,
$recursion,
$depth
) {
$key = $case ? $case($key) : $key;
//If the nested item is itself an array, recursively perform the same transformation
return is_array($item) ?
[$key => $recursion($case, $item, ++$depth)] : [$key => $item];
})->toArray();
//Maintain the top-level 'data' wrapper.
//This can easily be removed later in the controller if that's not needed either
$items = (!$depth && !array_key_exists('data', $items)) ?
['data' => $items] : $items;
return $items;
};
//Return the results in the form of an instance of Collection
return new static($recursion($case, $this->items));
});
/**
* Maintain non-sequential numeric keys when performing
* \Illuminate\Support\Collection::mapWithKeys() functionality
*
* Source: https://github.com/laravel/framework/issues/15409#issuecomment-247083776
*/
collect()->macro('mapWithKeys_v2', function ($callback) {
$result = [];
foreach ($this->items as $key => $value) {
$assoc = $callback($value, $key);
foreach ($assoc as $mapKey => $mapValue) {
$result[$mapKey] = $mapValue;
}
}
return new static($result);
});
Then run your Fractal results through it:
$results = collect($fractalResults)->fractal('camel_case')->get('data', []);

PHP function to validate array elements for n level

I have tried out with the google and tried myself to get done for the below functionality. I need a function that will validate each array element whether it is scalar or not. So i wrote a simple function that will iterate each element of the array and checks for scalar or not.
But the real requirement, the array could be a multi dimentional array. So i have modified the array and called the function recursively as below, But it will not go-through all elements in the array.
function validate_scalar($params)
{
foreach ($params as $key => $arg)
{
if (is_array($arg))
{
validate_scalar($arg);
}
else
{
if (!is_scalar($arg))
{
// throwing an exception here if not scalar.
}
}
}
return true;
}
Is there any method to achieve this functionality? Please help me on this.
array_walk_recursive
You could use something like this:
<?php
$array = array(
'kalle' => 'asdf',
'anka' => array(
123,
54324,
new stdClass()
)
);
array_walk_recursive($array, function ($item, $key) {
if (!is_scalar($item)) {
echo $key . " => : Is not scalar\n";
return false;
}
echo $key . " => : Is scalar\n";
return true;
});
array_walk_recursive ignores values that are arrays
output:
kalle => : Is scalar
0 => : Is scalar
1 => : Is scalar
2 => : Is not scalar

Php, check if object with property = value exists in array of objects

I think I could do this with a foreach loop like this:
foreach ($haystack as $item)
if (isset($item->$needle_field) && $item->$needle_field == $needle)
return true;
}
but i was wandering if it could be done without a loop?
something like:
if(in_array($item->$needle_field == $needle,$haystack)
return true;
Yes, in modern PHP you can determine if a specific object property contains a specific value without a classic loop by combining the forces of array_column() (which has evolved to also handle arrays of objects) and in_array().
Code: (Demo)
$objects = [
(object)['cats' => 2],
(object)['dogs' => 2],
(object)['fish' => 10],
(object)['birds' => 1],
];
$needleField = 'cats';
$needleValue = 2;
var_export(
in_array($needleValue, array_column($objects, $needleField))
);
// output: true
The advantage of this technique is the obviously concise syntax. This is a perfectly acceptable approach for relatively small volumes of data.
A possible disadvantage to this technique is that array_column() will be generating a new array of all of values that relate to the $needleField.
In my above demo, array_column() will only generate a single-element array because there is only one cats property in all of the objects. If we were processing a relatively large volume of data, then it would be inefficient to bother collecting all of the qualifying cats values and then run in_array() when only one match is necessary to return true.
For "large" volumes of data where performance is a primary criterion for script design, a classic foreach loop would be a better choice and as soon as an object satisfies the rules, then the loop should be halted via return or break.
Code: (Demo)
function hasPropertyValue(array $objects, $property, $value): bool {
foreach ($objects as $object) {
if (property_exists($object, $property) && $object->{$property} === $value) {
return true;
}
}
return false;
}
var_export(
hasPropertyValue($objects, $needleField, $needleValue)
);
It's possible, but it's not any better:
<?php
function make_subject($count, $success) {
$ret = array();
for ($i = 0; $i < $count; $i++) {
$object = new stdClass();
$object->foo = $success ? $i : null;
$ret[] = $object;
}
return $ret;
}
// Change true to false for failed test.
$subject = make_subject(10, true);
if (sizeof(array_filter($subject, function($value) {
return $value->foo === 3;
}))) {
echo 'Value 3 was found!';
} else {
echo 'Value 3 was not found.';
}
Outputs Value 3 was found!.
I advise you remain with the for loop: it's legible, unlike any tricks to save a line that you might find.
This will not work if the array you are searching is out of your control. But, if you are the one building the array of objects to be searched, you can structure it using the needle as array keys to be used with array_key_exists when you are searching.
For example, instead of making your $haystack array like this:
[
{
'needle_field' => $needle
},
...
]
Make it like this:
[
$needle => {
'needle_field' => $needle
},
...
]
And search like this:
if (array_key_exists($needle, $haystack)) {
return true;
}
Finally, if you need to, you can convert back to an integer indexed array by using array_values
$haystack = array_values($haystack);
This may not work in all situations but it worked great for me.
Maybe with array_key_exists:
if (array_key_exists($needle_field, $haystack) {
if ($haystack[$needle_field] == $needle) {
echo "$needle exists";
}
}

Reference PHP array by multiple indexes

This may be some sort of weird longer shortcut, and please correct me if I'm mistaken in this train of thought...
I have a matrix of data that looks like:
unique_id | url | other random data...
unique_id | url | other random data...
unique_id | url | other random data...
I want to be able to reference an item by either it's url, or it's unique_id - is there a fancy way to do this?
I suppose the cheating solution would be to just make two arrays, but I was wondering if there is a better way.
Only way I can think of that doesn't involve iterating the array for each search (see Jacob's answer) is to store references to each item in two arrays.
Edit: As the URLs and IDs cannot collide, they may be stored in the same reference array (thanks Matthew)
$items; // array of item objects
// Use objects so they're implicitly passed by ref
$itemRef = array();
foreach ($items as $item) {
$itemRef[$item->unique_id] = $item;
$itemRef[$item->url] = $item;
}
// find by id
$byId = $itemRef[$id];
// find by url
$byUrl = $itemRef[$url];
You could probably encapsulate this nicely using a collection class that implements getById() and getByUrl(). Internally, it could store the references in as many arrays as is necessary.
Of course, what you're essentially doing here is creating indexed result sets, something best left to database management systems.
Try something like this:
function selectByIdOrURL($array, $data) {
foreach($array as $row) {
if($row['unique_id'] == $data || $row['url'] == $data) return $row;
}
return NULL;
}
$array = array(
array('unique_id' => 5, 'url' => 'http://blah.com'),
array('unique_id' => 3, 'url' => 'http://somewhere_else.com')
);
$found = selectByIdOrURL($array, 5); //array('unique_id' => 5, 'url' => 'http://blah.com')
$nfound = selectByIdOrURL($array, 10); //NULL
It appears your fancy solution was only available as of PHP 5.5.
You can combine the use of array_search and array_column to fetch your entry in a single line of code:
$items = [
[
'unique_id' => 42,
'url' => 'http://foo.com'
],
[
'unique_id' => 57,
'url' => 'http://bar.com'
],
[
'unique_id' => 36,
'url' => 'http://example.com'
],
];
$bar = $entries[array_search(57, array_column($items, 'unique_id'))];
var_dump($bar);
//outputs
array (size=2)
'unique_id' => int 57
'url' => string 'http://bar.com' (length=14)
Surely an object would be the easy way?
class Item {
public $unique_url;
public $url;
public $other_data;
public function __construct($unique_url, $url, $other_data)
{
$this->unique_url = $unique_url;
$this->url = $url;
$this->other_data = $other_data;
}
}
class ItemArray {
private $items = array();
public function __construct()
{
}
public function push(Item $item)
{
array_push($items, $item); //These may need to be reversed
}
public function getByURL($url)
{
foreach($items as $item)
{
if($item->url = $url)
{
return $item;
}
}
}
public function getByUniqueURL($url)
{
foreach($items as $item)
{
if($item->unique_url = $unique_url)
{
return $item;
}
}
}
}
Then use it with
$itemArray = new ItemArray();
$item = new Item("someURL", "someUniqueURL","some other crap");
$itemArray->push($item);
$retrievedItem = $itemArray->getItemByURL("someURL");
This technique has a little extra overhead due to object creation, but unless you're doing insane numbers of rows it would be fine.

Find array key in php and change its value or contents?

Given a multi-dimensional array that you don't necessarily know the structure of; how would one search for a key by name and change or add to it's contents? The value of the key could be either a string or an array and the effects should be applied either way--I had looked at array_walk_recursive, but it ignores anything that contains another array...
Does this work?
function arrayWalkFullRecursive(&$array, $callback, $userdata = NULL) {
call_user_func($callback, $value, $key, $userdata);
if(!is_array($array)) {
return false;
}
foreach($array as $key => &$value) {
arrayWalkFullRecursive($value);
}
return true;
}
arrayWalkFullRecursive($array,
create_function( // wtb PHP 5.3
'&$value, $key, $data',
'if($key == $data['key']) {
$value = $data['value'];
}'
),
array('key' => 'foo', 'value' => 'bar')
);
Array keys in PHP are ints and strings. You can't have an array array key. So yeah, array_walk_recursive() is what you want.

Categories