Getting rid of ?: operator in PHP - php

I have a problem with PHP code for survey. The code only takes one value, "answer" and if answer is not available it takes "otherAnswer". I need it to take both "answer" and "otherAnswer"
Here is the code. Thanks for help.
protected function convertRequestToUserAnswersArray()
{
$answers = [];
if ($this->request->hasArgument('answers')) {
/** #noinspection PhpUnhandledExceptionInspection */
$requestAnswers = $this->request->getArgument('answers');
/** #noinspection PhpWrongForeachArgumentTypeInspection */
foreach ($requestAnswers as $questionUid => $requestAnswer) {
$answers[$questionUid] = $requestAnswer['answer'] ?: $requestAnswer['otherAnswer'];
}
}
return $answers;
}

Try to append both answers. If you want both answers you don't need to use ternary operator
protected function convertRequestToUserAnswersArray()
{
$answers = [];
if ($this->request->hasArgument('answers')) {
/** #noinspection PhpUnhandledExceptionInspection */
$requestAnswers = $this->request->getArgument('answers');
/** #noinspection PhpWrongForeachArgumentTypeInspection */
foreach ($requestAnswers as $questionUid => $requestAnswer) {
$answers[$questionUid] = $requestAnswer['answer'] +' '+ $requestAnswer['otherAnswer'];
}
}
return $answers;
}

We do not know how the $answers is going to be used. Because returning both answer and otherAnswer requires $answers to be array. Which means any user of $answers must be refactored.
Something like this might work:
$answers[$questionId] = ["answer" => $requestAnswer["answer"], "otherAnswers" => requestAnswer["otherAnswer"] ]
Just add some checking mechanism for the key existence.
Or you can concatenate it as suggested in another SO answer.

As you said you want both values then you can store into array as like below
$answers[$questionUid][] = $requestAnswer['answer'];
$answers[$questionUid][] = $requestAnswer['otherAnswer'];
By using above script $answers would have both values against $questionUid

Assuming that $answers[$questionUid] is an array, You can try to push the values to it:
/** #noinspection PhpWrongForeachArgumentTypeInspection */
foreach ($requestAnswers as $questionUid => $requestAnswer) {
$answers[$questionUid] = $requestAnswer['answer'] . '----' . $requestAnswer['otherAnswer'];
}
Hope this helps.

Related

return result of foreach loop(s)

I have the some code which looks something like this (I've simplified it):
function process_something($a){
foreach($a as $b){
// Some logic here
return $something;
}
}
$input=[]; // An array of some kind
echo process_something($input);
I expect that final line to echo what the loops have returned but I get nothing. Maybe the above code will not work. I just put it in for illustration. I have a lot of nested loops working together to return various things.
If I have the loops echo data out, it works. However, I need this function to just return the end result to me for further processing, rather than echoing out to the user.
How do I proceed?
In this case this loop will only run once, because return jumps out of a function on the first occurrence.
It should be more like:
function process_something($a){
foreach($a as $b){
$something = 'Some math or other logic here';
}
return $something;
}
$input=[]; // An array of some kind
echo process_something($input);
Please post your code, we will try to figure out what's wrong with it.
This is the perfect case for a generator, a concept that I rarely seen used.
function process_something($a){
foreach($a as $b){
// Some logic here
yield $something;
}
}
$input=[]; // An array of some kind
/**
* The generator returns an Iterator instance
* so you'd need to loop over it
*/
foreach(process_something($input) as $item){
// do stuff here
// echo $item
// echo $item->something
// your call
}
The major advantage here is that you can "return without actually returning", you're yielding a value and the generator continues on with it's work.
Not to mention it's very memory efficient, altough 99% of the times memory is not an issue.
AND, this is the nice part, you can apply specific logic for each of the items in the $input without needing to do some weird hack-ish function.
Alternatives
function process_something($a){
$return = [];
foreach($a as $b){
// Some logic here
$return[] = $something;
}
return $return;
}
The only reason this answer isn't a comment is that it's very rare that I see a question that would legitimately benefit from a generator. This is one of them.
More on generators:
http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html
https://scotch.io/tutorials/understanding-php-generators
I did get you properly . But I think you are looking for the last value of the array .
to do that just do it :
function process_something($a){
$b = array_reverse ($a);
return $b[0];
}
class StrHelper
{
/**
* Check for $string starts with $query
* #param $string
* #param $query
* #return bool
*/
public static function startsWith($string,$query){
return substr($string, 0, strlen($query)) === $query;
}
}
class ModelSearchHelper
{
/**
* filter where $key of $data arr startsWith $modelName
* #param string $modelName
* #param array $data
* #return array
*/
public static function searchFor(string $modelName,array $data){
// this code do same
// $out=[];
// foreach ($data as $key=>$value){
// if(StrHelper::startsWith($key,$modelName)){
// $out[$key]=$value;
// }
// }
// return $out;
// of this
return array_filter($data,function ($value,$key) use ($modelName){
return StrHelper::startsWith($key,$modelName);
},ARRAY_FILTER_USE_BOTH );
}
}
$test=[
'limit'=>10,
'offset'=>5,
'customer.name'=>'Fox',
'customer.address.street'=>'forest st.'
];
//filter where $key of $model startsWith $modelName
var_dump(ModelSearchHelper::searchFor('customer',$test));
result:
array(2) {
["customer.name"]=>
string(3) "Fox"
["customer.address.street"]=>
string(10) "forest st."
}

How to generate unique random value for each user in laravel and add it to database

I am developing a event organization website. Here when the user registers for an event he will be given a unique random number(10 digit), which we use to generate a barcode and mail it to him. Now,
I want to make the number unique for each registered event.
And also random
One solution is to grab all the random numbers in an array and generate a random number using Php rand(1000000000, 9999999999) and loop through and check all the values. Grab the first value that doesn't equal to any of the values in the array and add it to the database.
But I am thinking that there might be a better solution to this. Any suggestion?
You can use php's uniqid() function to generate a unique ID based on the microtime (current time in microseconds)
Example:
<?php
echo uniqid();
?>
Output:
56c3096338cdb
Your logic isn't technically faulty. However, if your application attracts lots of users, fetching all of the random numbers may well become unnecessarily expensive, in terms of resources and computation time.
I would suggest another approach, where you generate a random number and then check it against the database.
function generateBarcodeNumber() {
$number = mt_rand(1000000000, 9999999999); // better than rand()
// call the same function if the barcode exists already
if (barcodeNumberExists($number)) {
return generateBarcodeNumber();
}
// otherwise, it's valid and can be used
return $number;
}
function barcodeNumberExists($number) {
// query the database and return a boolean
// for instance, it might look like this in Laravel
return User::whereBarcodeNumber($number)->exists();
}
This is good:
do {
$refrence_id = mt_rand( 1000000000, 9999999999 );
} while ( DB::table( 'transations' )->where( 'RefrenceID', $refrence_id )->exists() );
To avoid the problem of having to check to see if a matching code exists every time a new one is created, I just catch MySQL's duplicate record exception (error code 1062). If that error is caught, I just call the function again until the save is successful. That way, it only has to generate a new code if it collides with an existing one. Runs a lot faster -- but obviously gets a bit slower as your number of users approaches the number of possible barcodes.
function generateBarcode($user_id) {
try {
$user = User::find($user_id);
$user->barcode = mt_rand(1000000000, 9999999999);
$user->save();
} catch (Exception $e) {
$error_info = $e->errorInfo;
if($error_info[1] == 1062) {
generateBarcode($user_id);
} else {
// Only logs when an error other than duplicate happens
Log::error($e);
}
}
}
So just loop through all the users you want to assign a code to:
foreach(User::all() as $user) {
generateBarcode($user->id);
}
You could also add some logic to escape the function loop if a maximum number of attempts are made, but I've never bothered because collisions are unlikely.
Looping through the array won't be that efficient. If your database becomes too large then it slow down the entire process and also there might be a rare situation when 2 threads are looping through the array for the same random number and it will be found available and return same number to both the tickets.
So instead of looping through the array you can set the 10 digit registration id as primary key and instead of looping through the array you can insert the registration details along with randomly generated number, if the database insert operation is successful you can return the registration id but if not then regenerate the random number and insert.
Alternate solution which will be more effective
Instead of 10 digit random numbers you can use timestamp to generate a 10 digit unique registration number and to make it random you can randomize the first 2 or 3 digits of the timestamp
One Solution could be like this:
use Illuminate\Support\Facades\Validator;
private function genUserCode(){
$this->user_code = [
'user_code' => mt_rand(1000000000,9999999999)
];
$rules = ['user_code' => 'unique:users'];
$validate = Validator::make($this->user_code, $rules)->passes();
return $validate ? $this->user_code['user_code'] : $this->genUserCode();
}
Its generating a random number between 1000000000 and 9999999999. After that, it validates the number against the table. If true then it returns the number, otherwise runs the function again.
I made something like this
/**
* Generate unique shipment ID
*
* #param int $length
*
* #return string
*/
function generateShipmentId($length)
{
$number = '';
do {
for ($i=$length; $i--; $i>0) {
$number .= mt_rand(0,9);
}
} while ( !empty(DB::table('shipments')->where('id', $number)->first(['id'])) );
return $number;
}
<?php
declare(strict_types=1);
namespace App\Helpers;
use App\Exceptions\GeneratorException;
class GeneratorHelper
{
public static $limitIterations = 100000;
/**
* #param string $column
* #param string $modelClass
* #return string
* #throws GeneratorException
*/
public static function generateID(string $modelClass, string $column): string
{
return self::run(
$modelClass,
$column,
self::IDGenerator(),
'Generation id is failed. The loop limit exceeds ' . self::$limitIterations
);
}
/**
* #param string $modelClass
* #param string $column
* #param \Generator $generator
* #param string $exceptionMessage
* #param array $whereParams
* #return string
* #throws GeneratorException
*/
protected static function run(string $modelClass, string $column, \Generator $generator, string $exceptionMessage, array $whereParams = []): string
{
try {
foreach ($generator as $id) {
$query = $modelClass::where([$column => $id]);
foreach ($whereParams as $param) {
$query->where(...$param);
}
if (!$query->first()) {
return $id;
}
}
} catch (\Throwable $e) {
$exceptionMessage = $e->getMessage();
}
throw new GeneratorException($exceptionMessage);
}
protected static function IDGenerator(): ?\Generator
{
for ($i = 1; $i <= self::$limitIterations; $i++) {
yield (string)random_int(1000000000, 9999999999);
}
return null;
}
}
sample usage
$card->number = GeneratorHelper::generateID(Card::class, 'number');
for me, I prefer using MySQL way, because when you have a large amount of data in your DB, you will have too much quires to check your number uniqueness,
for example, a code like this:
do {
$code = random_int(100000, 99999999);
}
while (AgentOrder::where("order_number", "=", $code)->exists());
so , this "do while" loop would be excueted too many times.
to avoid that, you can use MySQL way like:
private function getUniqueCodeNumber()
{
$value = AgentOrder::query()->selectRaw('FLOOR(1000000 + RAND() * 10000000) AS generatedCode')
->whereRaw("'generatedCode' NOT IN (SELECT order_number FROM agent_orders WHERE agent_orders.order_number IS NOT NULL)")
->limit(1)->first();
if ($value == null) return 100000000;
$value = (int)$value->generatedCode;
return $value;
}
this answer is inspired from this answer.
Helper (app/Helpers/Constants.php)
<?php
namespace App\Helper;
class Constants
{
public static function getUniqueId($model){
$id = mt_rand(1000000000, 9999999999);
if($model->find($id))
return self::getUniqueId($model);
return $id;
}
}
?>
Model (app/Models/User.php)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public static function boot(){
parent::boot();
$creationCallback = function ($model) {
if (empty($model->{$model->getKeyName()}))
$model->{$model->getKeyName()} = Constants::getUniqueId(new self());
};
static::creating($creationCallback);
}
}
?>
Explanation: Instead of calling the getUniqueId method in every controller. You can write it inside model.
From the #Joel Hinz answer :
public function set_number() {
$number = mt_rand(1000000000, 9999999999);
return User::where('number', $number)->exists() ? $this->set_number() : $number;
}
Hope that helped.

How to avoid crash when key doesnt exist in array?

Im having a lot of difficulty with arrays in PHP. They require me to write a lot of codes such as isset(), empty(), array_key_exist(); And I really dont want to deal with these. If the key doesnt exist just handle it as a null.
$arr = [
'location' => 'Paris'
]
$arr['country'] // boom crash. How to walkaround this?
Any suggestions?
EDIT
I dont want to use any if condition. No isset(), array_key_exist, exceptions, etc. I just want them to be null if the key doesn't exist? Is this possible in PHP? The application is very abstract and data may vary on each request.
function getValue(array $array, $key) {
return isset($array[$key]) ? $array[$key] : null;
}
echo getValue($mysteryArray, 'mysteryKey');
Or:
$array += array_fill_keys(array('foo', 'bar', 'baz'), null);
echo $array['foo'];
My own function inspired from deceze. Works perfectly.
/**
* Fill array with null on nonexistent keys
*
* #param array $arg
* #param array $possible_keys
*/
function fillNull(array $arg, array $possible_keys){
foreach($possible_keys as $key){
$result[$key] = empty( $arg[$key] ) ? null : $arg[$key];
}
return $result;
}
You can use ArrayIterator or some class that gives you the interface you desire.
<?php
class MyArrayIterator extends ArrayIterator {
public function __construct($array, $flags=0) {
parent::__construct($array, $flags);
}
public function offsetGet($index) {
if (!$this->offsetExists($index)) {
return null;
}
return parent::offsetGet($index);
}
}
$arr = [
'location' => 'Paris'
];
$arrIt = new MyArrayIterator($arr);
echo $arrIt['country'];
echo "Only this is echoed";
#$arr['country'] - suppress errors, bad pratice.
&$arr['country'] - use reference, could add additional elements to array, bad pratice.

How to merge two php Doctrine 2 ArrayCollection()

Is there any convenience method that allows me to concatenate two Doctrine ArrayCollection()? something like:
$collection1 = new ArrayCollection();
$collection2 = new ArrayCollection();
$collection1->add($obj1);
$collection1->add($obj2);
$collection1->add($obj3);
$collection2->add($obj4);
$collection2->add($obj5);
$collection2->add($obj6);
$collection1->concat($collection2);
// $collection1 now contains {$obj1, $obj2, $obj3, $obj4, $obj5, $obj6 }
I just want to know if I can save me iterating over the 2nd collection and adding each element one by one to the 1st collection.
Thanks!
Better (and working) variant for me:
$collection3 = new ArrayCollection(
array_merge($collection1->toArray(), $collection2->toArray())
);
You can simply do:
$a = new ArrayCollection();
$b = new ArrayCollection();
...
$c = new ArrayCollection(array_merge((array) $a, (array) $b));
If you are required to prevent any duplicates, this snippet might help. It uses a variadic function parameter for usage with PHP5.6.
/**
* #param array... $arrayCollections
* #return ArrayCollection
*/
public function merge(...$arrayCollections)
{
$returnCollection = new ArrayCollection();
/**
* #var ArrayCollection $arrayCollection
*/
foreach ($arrayCollections as $arrayCollection) {
if ($returnCollection->count() === 0) {
$returnCollection = $arrayCollection;
} else {
$arrayCollection->map(function ($element) use (&$returnCollection) {
if (!$returnCollection->contains($element)) {
$returnCollection->add($element);
}
});
}
}
return $returnCollection;
}
Might be handy in some cases.
$newCollection = new ArrayCollection((array)$collection1->toArray() + $collection2->toArray());
This should be faster than array_merge. Duplicate key names from $collection1 are kept when same key name is present in $collection2. No matter what the actual value is
You still need to iterate over the Collections to add the contents of one array to another. Since the ArrayCollection is a wrapper class, you could try merging the arrays of elements while maintaining the keys, the array keys in $collection2 override any existing keys in $collection1 using a helper function below:
$combined = new ArrayCollection(array_merge_maintain_keys($collection1->toArray(), $collection2->toArray()));
/**
* Merge the arrays passed to the function and keep the keys intact.
* If two keys overlap then it is the last added key that takes precedence.
*
* #return Array the merged array
*/
function array_merge_maintain_keys() {
$args = func_get_args();
$result = array();
foreach ( $args as &$array ) {
foreach ( $array as $key => &$value ) {
$result[$key] = $value;
}
}
return $result;
}
Add a Collection to an array, based on Yury Pliashkou's comment (I know it does not directly answer the original question, but that was already answered, and this could help others landing here):
function addCollectionToArray( $array , $collection ) {
$temp = $collection->toArray();
if ( count( $array ) > 0 ) {
if ( count( $temp ) > 0 ) {
$result = array_merge( $array , $temp );
} else {
$result = $array;
}
} else {
if ( count( $temp ) > 0 ) {
$result = $temp;
} else {
$result = array();
}
}
return $result;
}
Maybe you like it... maybe not... I just thought of throwing it out there just in case someone needs it.
Attention! Avoid large nesting of recursive elements. array_unique - has a recursive embedding limit and causes a PHP error Fatal error: Nesting level too deep - recursive dependency?
/**
* #param ArrayCollection[] $arrayCollections
*
* #return ArrayCollection
*/
function merge(...$arrayCollections) {
$listCollections = [];
foreach ($arrayCollections as $arrayCollection) {
$listCollections = array_merge($listCollections, $arrayCollection->toArray());
}
return new ArrayCollection(array_unique($listCollections, SORT_REGULAR));
}
// using
$a = new ArrayCollection([1,2,3,4,5,6]);
$b = new ArrayCollection([7,8]);
$c = new ArrayCollection([9,10]);
$result = merge($a, $b, $c);
Combine the spread operator to merge multiple collections, e.g. all rows in all sheets of a spreadsheet, where both $sheets and $rows are ArrayCollections and have a getRows(): Collection method
// Sheet.php
public function getRows(): Collection { return $this->rows; }
// Spreadsheet.php
public function getSheets(): Collection { return $this->sheets; }
public function getRows(): Collection
return array_merge(...$this->getSheets()->map(
fn(Sheet $sheet) => $sheet->getRows()->toArray()
));
Using Clousures PHP5 > 5.3.0
$a = ArrayCollection(array(1,2,3));
$b = ArrayCollection(array(4,5,6));
$b->forAll(function($key,$value) use ($a){ $a[]=$value;return true;});
echo $a.toArray();
array (size=6) 0 => int 1 1 => int 2 2 => int 3 3 => int 4 4 => int 5 5 => int 6

PHP memory references

I am wondering this question for a long time, how does PHP handle references are they a good idea to use and I can't explain better than using an example, lets look at the following class and then # the comment of the setResult method.
Lets imagine we are using a model view controller framework and we are building a basic AjaxController, we only got 1 action method (getUsers) so far. Read the comments, and I hope my question is clear, how does PHP handle these kind of situations and is it true what I wrote about the x times in the memory # the setResult docblock.
class AjaxController{
private $json = array(
'result' => array(),
'errors' => array(),
'debug' => array()
);
/**
* Adds an error, always displayed to users if any errors.
*
* #param type $description
*/
private function addError($description){
$this->json['errors'][] = $description;
}
/**
* Adds an debug message, these are displayed only with DEBUG_MODE.
*
* #param type $description
*/
private function addDebug($description){
$this->json['debug'][] = $description;
}
/**
* QUESTION: How does this go in memory? Cause if I use no references,
* the array would be 3 times in the memory, if the array is big (5000+)
* its pretty much a waste of resources.
*
* 1st time in memory # model result.
* 2th time in memory # setResult ($resultSet variable)
* 3th time in memory # $this->json
*
* #param array $resultSet
*/
private function setResult($resultSet){
$this->json['result'] = $resultSet;
}
/**
* Gets all the users
*/
public function _getUsers(){
$users = new Users();
$this->setResult($users->getUsers());
}
public function __construct(){
if(!DEBUG_MODE && count($this->json['debug']) > 0){
unset($this->json['debug']);
}
if(count($this->json['errors']) > 0){
unset($this->json['errors']);
}
echo json_encode($this->json);
}
}
Another simple example: What would be better to use technique A:
function example(){
$latestRequest = $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$_SESSION['abc']['test']['abc'] = 'test';
}
}
Or technique B:
function example(){
$latestRequest =& $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$latestRequest = 'test';
}
}
Thanks for reading and advise :)
In short: don't use references.
PHP copies on write. Consider:
$foo = "a large string";
$bar = $foo; // no copy
$zed = $foo; // no copy
$bar .= 'test'; // $foo is duplicated at this point.
// $zed and $foo still point to the same string
You should only use references when you need the functionality that they provide. i.e., You need to modify the original array or scalar via a reference to it.

Categories