Laravel Query Builder Issue Remove Null Values - laravel

Hi all you smart individuals i'm hitting a solid wall here with someone else code that i'm trying to fix a issue.
The issue is that when this query builder has got all the results there seems to be some coming back with null values and i'm not sure how to remove them before I paginate the data, I know how to do it if it was a collection however maybe some of you might be-able to help me.
so currently the logic goes into this pagination
$return = $tld->paginate($request->get('limit'))->toArray();
$tld being the eloquent builder.
Which then gets sent into this function that was created..
$return = $this->makePagination($return);
public function makePagination($object = [], $filters = []) {
return [
'data' => [
'items' => $object['data'],
'pagination' => [
'from' => $object['from'],
'to' => $object['to'],
'total' => $object['total'],
'per_page' => $object['per_page'],
'first_page' => [
'number' => 1,
'url' => $object['first_page_url']
],
'last_page' => [
'number' => $object['last_page'],
'url' => $object['last_page_url']
],
'next_page' => [
'number' => $object['current_page'] + 1,
'url' => $object['next_page_url']
],
'prev_page' => [
'number' => $object['current_page'] - 1,
'url' => $object['prev_page_url']
]
],
'params' => $filters
]
];
}
But then i'm getting a response like this with Null values and I would like to remove them before any of this pagination happens
{
"data": {
"items": [
{
"id": 13771,
},
null,
{
"id": 4125,
},
Side note if I run $tld->get() I can see all the results and there are null values in there so if anyone can show me how to remove the null values that would be a great help <3 you all if you can help me ...
Update
Ive also tried $tld->get()->filter(); and thats also not removing the null values I still get this response
[
{
"id": 13771,
},
null,
{
"id": 789,
}
]

I think I fixed it with a little hack
$filtered = collect(array_values(array_filter($tld->get()->toArray())));
return $this->paginate($filtered, $request->get('limit') ?? 15 , $page = null, $options = []);
and then created a collection paginator
public function paginate($items, $perPage = 15, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}

#Bob Hiller Please use whereNotNull in query to remove null entries.
$tld=$query->get();
$tld->filter(function ($value) { return !is_null($value); });
$return = $tld->paginate($request->get('limit'))->toArray();

Related

Failed asserting that a row in the table student.sections matches the attributes

Hello im new to PHPUnit with minimum knowledge in laravel.
Im trying to test this method that mass create student section
public function setStudentsSection(Request $request)
{
$enrollments = Enrollment::whereIn('student_id', $request->students)->where('session_id', $request->session_id)->get();
$program_section = ProgramSection::withCount('students')->find($request->program_section_id);
if(($program_section->students_count + count($enrollments)) <= $program_section->max_students) {
foreach($enrollments as $enrollment) {
$response = StudentSection::create([
'student_id' => $enrollment->student_id,
'enrollment_id' => $enrollment->id,
'section_id' => $request->program_section_id,
'created_at' => Carbon::now()
]);
return $response;
}
}
return response()->json(['errors' => ['message' => 'Selected Section is full.']], 405);
}
UPDATE
Here's the test case. Im trying to match the method that i've modified with my test, but im failing to do so.
public function testCanMassAssignSection()
{
$sectioning_data = $this->setMassSectioning(10);
$this->json('POST', 'api/enrollments/set-students-section', $sectioning_data['data'])
->assertStatus(201);
$student_section_data = ['student_id' => $sectioning_data['student_ids'], 'section_id' => $sectioning_data['program_section']->id];
$this->assertDatabaseHas('student.sections', $student_section_data);
}
private function setMassSectioning($max_students)
{
$session = Session::factory()->create();
$program_section = ProgramSection::factory()->create(['session_id' => $session->id, 'max_students' => $max_students]);
$enrollments = Enrollment::factory(['session_id' => $session->id])->count(3)->create();
$student_ids = array();
foreach($enrollments as $enrollment) {
array_push($student_ids, $enrollment->student_id);
}
return [
'data' => ['program_section_id' => $program_section->id, 'session_id' => $session->id, 'students' => $student_ids],
'student_ids' => $enrollment->student_id,
'program_section' => $program_section
];
}
UPDATE
Here's the error the i get.
1) Test\Feature\EnrollmentTest::testCanMassAssignSection
Failed asserting that a row in the table [student.sections] matches the attributes {
"student_id": 2765,
"section_id": 1649
}.
Found: [
{
"id": 262,
"student_id": 2763,
"section_id": 1649,
"created_at": "2022-08-24 09:32:05",
"updated_at": "2022-08-24 09:32:05",
"enrollment_id": 1740
}
].
Still can't make it to match. I do not know what im doing wrong.
Solve! I just create $student data and added to $enrollments now assert in database match. Although don't know what exactly is happening on the background.
I think when i added to enrollments variable the 'student_id' => $student->id it creates those 3 records.
private function setMassSectioning($max_students)
{
$session = Session::factory()->create();
$student = Student::factory()->create();
$program_section = ProgramSection::factory()->create(['session_id' => $session->id, 'max_students' => $max_students]);
$enrollments = Enrollment::factory(['session_id' => $session->id, 'student_id' => $student->id])->count(3)->create();
$student_ids = array();
foreach($enrollments as $enrollment) {
array_push($student_ids, $enrollment->student_id);
}
return [
'data' => ['program_section_id' => $program_section->id, 'session_id' => $session->id, 'students' => $student_ids],
'student_ids' => $enrollment->student_id,
'program_section' => $program_section
];
}

how to change query so it only shows name instead whole array (laravel 8)

I got a query that gets the data into a collection, problem is that it shows foreign id but i want it to display what i have given in the url parameters.
columnsGiven is the parameter from url. contains column names with child: "language.name". so column=active,title,language.name
For example i get this:
"name": "george",
"active": 1,
"language": 1,
and this is what i want:
"name": "george",
"active": 1,
"language": "Dutch",
this is my code:
public function index(Request $request){
$columnsGiven = explode(',', $request->columns);
$tableName = $request->table_name; //example: 'support_guide_translations'
$modelName = $request->model_name; //example: "App\Models\SupportGuideTranslation";
if($request->search){
$query = $modelName::search($request->search);
} else{
$query = $modelName::query();
if($request->sort){
$sort = explode('?', $request->sort);
$query->orderBy($sort[0], $sort[1]);
}
}
foreach ($request->query() as $key => $value) {
// dd($value);
if(!$value == ""){
if(Schema::hasColumn($tableName, $key)){
$query->where($key, $value);
}
if(in_array($key, $columnsGiven)){
dd('true');
}
// $searchWord = Str::contains('account123',$request->search);
}
}
$guides = $query->get();
return GuideResource::collection($guides);
}
}
this is GuideResource, it sends data to vue by making it json first. Not allowed to make changes here, it has to be done in the function index. :
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'active' => $this->active,
'language' => $this->language,
'support_guide' => $this->support_guide,
'support_guide_group' => $this->support_guide_group,
];
}
"language": {
"id": 1,
"name": "Dutch",
"code": "NL",
"created_at": "2021-06-14T10:10:32.000000Z",
"updated_at": "2021-06-14T10:10:32.000000Z"
},
I believe "language" has a relationship with another model.
Isn't this what you are looking for?
return [
// your other attributes...
'language' => $this->language->relationshipName->name,
];
where relationshipName is the method name in the Language model.

Laravel Http Client 419 unknown status

For testing reasons, I want to make the following Post Request with the Laravel HTTP Client:
$test = Http::post(route('users.leads.store', ['user' => $user->id]), [
"company_name" => "TESTCOMPANY",
"zip" => "49685",
"city" => "BÜHREN",
"street" => "MÜHLENKAMP 3",
"contact_name" => "FABIANLUKASSEN",
"phone1" => "017691443785",
"email" => "FABIANLUKASSEN#TESTEN.DE",
"website" => "www.fabianlukassen.de",
"category" => "Hotel",
"closed_until" => now(),
"appointment_end" => now()->addDays(1),
"appointment_comment" => "HALLO ICH BIN FABIAN",
"additional_contacts" => "",
"phone2" => "",
"sub_category" => "",
"expert_status" => 0
]);
I know that the route is working just fine. However, with debugging in phpStorm, I can see that the $test variable contains a 419 error (unknown status). Does anyone know what's wrong?
(I'm using laravel 8)
I agree with #ElektaKode that the issue is likely due to lack of csrf token.
In order to disable CSRF middleware while testing,
switch off CSRF token for this route at /app/Http/Midddleware/VerifyCsrfToken.php, by updating:
protected $except = [ 'your-route-url' ];
Then you can use api authentication to follow it up.
The simplest way to use api authentication, follow this doc,
The other ways are either using Laravel passport or using jwt for api.(both will consume more time to set up, as you are using for testing using api authentication is your go to method.)
Usually in Laravel, 419 Page Expired error comes from CSRF middleware meaning there was a failure while validating CSRF token. Add your CSRF token to your test request or consider disabling CSRF middleware while testing.
Post Request with Laravels HTTP Client
$test = Http::post(route('users.leads.store', ['user' => $user->id]), [
"company_name" => "TESTCOMPANY",
"place_id" => null,
"street" => "MÜHLENKAMP 3",
"zip" => "49685",
"city" => "BÜHREN",
"title" => null,
"contact_name" => "FABIANLUKASSEN",
"additional_contacts" => null,
"phone1" => "+49 163 3006603",
"phone2" => null,
"email" => "FABIANLUKASSEN#TESTEN.DE",
"category" => "Hotel",
"sub_category" => null,
"website" => "www.fabianlukassen.de",
"status" => 1,
"expert_status" => 0,
"coordinates" => null,
"expert_id" => 1,
"agent_id" => null,
"blocked" => 0,
"important_note" => null,
]);
Route
Route::apiResource('users.leads', UserLeadController::class);
Store Method in the UserLeadController
public function store(User $user, CreateLeadRequest $request)
{
//TODO: Relocate validation to request class
if(!UserLeadController::isPhone("test", $request->phone1)) {
abort(400, "Keine gültige Telefonnummer!");
return;
}
if(!UserLeadController::isPhoneNumberUnique("test", $request->phone1)) {
abort(400, "Die Telefonnummer existiert bereits!");
return;
}
/**
* The logged in User
* #var User $agent
*/
$agent = Auth::user();
$phoneUtil = PhoneNumberUtil::getInstance();
$lead = new Lead();
$lead->fill($request->except(['appointment_end', 'appointment_comment']));
// Leads created by experts will be blocked
if ($user->id === $agent->id) {
$lead->blocked = true;
}
$numberProto = $phoneUtil->parse($lead->phone1, 'DE');
$lead->phone1 = $phoneUtil->format($numberProto, PhoneNumberFormat::INTERNATIONAL);
try {
$lead->save();
} catch (QueryException $e) {
//$message = 'Lead besteht bereits.';
//return Response::json(['errors' => $message], 422);
abort(422, "Lead besteht bereits!");
return;
}
if ($request->closed_until) {
$lead->closed_until = Carbon::create($request->closed_until);
$event_end = $request->appointment_end
? Carbon::parse($request->appointment_end)
: Carbon::parse($request->closed_until)->addMinutes(90);
$lead->calendarEvents()->save(new CalendarEvent([
'body' => $request->appointment_comment ?? "Wurde von {$this->roleDescriptor($agent->roles)}" . $agent->name . " angelegt.",
'type' => CalendarEventType::CALLCENTER_APPOINTMENT,
'event_begin' => $lead->closed_until,
'event_end' => $event_end,
]));
$lead->status = LeadState::APPOINTMENT;
$lead->expert_status = LeadExpertAcceptance::ACCEPTED;
} else {
$lead->status = LeadState::OPEN;
}
if (isset($request->agent)) {
$lead->agent_id = $request->agent;
}
try {
$user->leads()->save($lead);
$lead->comments()->save(new Comment([
'body' => "Wurde von {$this->roleDescriptor($agent->roles)}" . $agent->name . " angelegt.",
'user_id' => $agent->id,
'commentable_type' => 'lead',
'commentable_id' => $lead->id,
'reason' => 'CREATED',
'date' => now('Europe/Berlin'),
]));
if ($request->closed_until) {
$lead->comments()->save(new Comment([
'body' => "Termin wurde von {$this->roleDescriptor($agent->roles)}" . $agent->name . " vereinbart.",
'user_id' => $agent->id,
'commentable_type' => 'lead',
'commentable_id' => $lead->id,
'reason' => 'APPOINTMENT',
'date' => now('Europe/Berlin')->addMinute(),
]));
}
} catch (QueryException $e) {
//not sure if this works
$message = $e->getMessage();
abort(400, $message);
return;
}
if (empty($message)) {
return Response::json(['message' => 'Lead saved', 'lead' => new LeadSingleResource($lead)]);
} else {
return Response::json(compact('message'), 500);
}
}
//TODO: relocate function to rule object
protected static function isPhoneNumberUnique($attribute, $value) {
$withSpace = PhoneFormatter::formatInternational($value);
$withoutSpace = preg_replace('/ /', '', $withSpace);
$protos = [$withSpace, $withoutSpace]; // Necessary because legacy (25.06.2020).
$booleanTest = Company::query()->whereIn('phone', $protos)->doesntExist()
|| Lead::query()->whereIn('phone1', $protos)->orWhereIn('phone2', $protos)->doesntExist();
return $booleanTest;
}
//TODO: relocate function to rule object
protected static function isPhone($attribute, $value) {
if (!$value) {
return false;
}
$phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance();
$test = $phoneUtil->isValidNumber($phoneUtil->parse($value, 'DE'));
return $test;
}
fillable variable in the Lead Model
protected $fillable = [
'company_name',
'place_id',
'street',
'zip',
'city',
'title',
'contact_name',
'additional_contacts',
'phone1',
'phone2',
'email',
'category',
'sub_category',
'website',
'status',
'expert_status',
'coordinates',
'expert_id',
'agent_id',
'blocked',
'important_note'
];
As mentioned before, I receive a 200 OK status. Also, in a Vue.js component, I have done the following axios post request, which also just works fine.
axios
.post(`/api/users/${this.user_id}/leads`, {
"company_name": this.companyName,
"zip": this.zipCode,
"city": this.city,
"street": this.streetAndHouseNumber,
"contact_name": this.contactPartner,
"phone1": this.contactPartnerPhoneNumber,
"email": this.contactPartnerEmail,
"website": this.website,
"category": this.category,
"closed_until": this.appointmentStart,
"appointment_end": this.appointmentEnd,
"appointment_comment": this.comment,
//not used but needed (don't know the purpose)
"additional_contacts": "",
"phone2": "",
"sub_category": "",
"expert_status":this.expert_status,
}).then(() => {
window.location.href = this.routeSuccess;
}).catch((error) => {
this.showErrorAlert = true;
this.errorAlertMessage = error.response.data.message;
});
}

Payfort Return Signature Mismatch For Apple Pay on Web

I'm integrating Apple Pay on our website using payfort.
Payfort ask to calculate signature which work fine if input values are string. But the problem is what if value is array.
As shown in following image from there documentation. They require apple_header and apple_paymentMethod field to be List data type. Now how should i calculate the signature in this case since it takes key and value as string. but in our case value is array. (i.e. apple_paymentMethod AND apple_paymentMethod)
I tried these 2 fields using json_encode function.
If i send these 2 fields as string then payfort api return invalid format for field: apple_header.
If i send these 2 fields as array then payfort api return Signature Mismatch.
I don't have any idea what am i missing.
Here is my code:
Controller Code:
$parameters = [
'digital_wallet' => 'APPLE_PAY',
'command' => 'PURCHASE',
'access_code' => config('payfort.APPLE_ACCESS_CODE'),
'merchant_identifier' => config('payfort.APPLE_MERCHANT_IDENTIFIER'),
'merchant_reference' => date( 'Y' ).str_pad($request->iOrderId, 6, 0, STR_PAD_LEFT) . '_' . time(),
'amount' => 7000,
'currency' => 'SAR',
'language' => 'en',
'customer_email' => 'test#gmail.com',
'apple_data' => $request->token['paymentData']['data'],
'apple_signature' => $request->token['paymentData']['signature'],
'apple_header' => json_encode([
'apple_transactionId'=> $request->token['paymentData']['header']['transactionId'],
'apple_ephemeralPublicKey'=> $request->token['paymentData']['header']['ephemeralPublicKey'],
'apple_publicKeyHash'=> $request->token['paymentData']['header']['publicKeyHash']
]),
'apple_paymentMethod' => json_encode([
'apple_displayName'=> $request->token['paymentMethod']['displayName'],
'apple_network'=> $request->token['paymentMethod']['network'],
'apple_type'=> $request->token['paymentMethod']['type']
]),
'customer_ip' => $request->ip(),
'customer_name' => 'ABC',
'merchant_extra' => $request->iOrderId,
'merchant_extra1' => $request->order_number,
'merchant_extra2' => $request->locale_info,
];
// canculate signature
$parameters['signature'] = $this->payfortCoreHelpers->calculateSignature($parameters, 'request', true);
// Add Array or List fields in back to parameters before send
$parameters['apple_header'] = [
'apple_transactionId'=> $request->token['paymentData']['header']['transactionId'],
'apple_ephemeralPublicKey'=> $request->token['paymentData']['header']['ephemeralPublicKey'],
'apple_publicKeyHash'=> $request->token['paymentData']['header']['publicKeyHash']
];
$parameters['apple_paymentMethod'] = [
'apple_displayName'=> $request->token['paymentMethod']['displayName'],
'apple_network'=> $request->token['paymentMethod']['network'],
'apple_type'=> $request->token['paymentMethod']['type']
];
calculateSignature function
public function calculateSignature($arrData, $signType = 'request', $isAppleRequest = false)
{
$shaString = '';
ksort($arrData);
foreach ($arrData as $k => $v) {
$shaString .= "$k=$v";
}
$shaString = config('payfort.SHA_REQUEST_PHRASE') . $shaString . config('payfort.SHA_REQUEST_PHRASE');
$signature = hash(config('payfort.SHA_TYPE'), $shaString);
return $signature;
}
Payfort Response
{
"amount": "7000",
"response_code": "00008",
"digital_wallet": "APPLE_PAY",
"signature": "****",
"merchant_identifier": "****",
"access_code": "****",
"customer_ip": "::1",
"language": "en",
"command": "PURCHASE",
"merchant_extra": "****",
"response_message": "Signature mismatch",
"merchant_reference": "20201599817035025",
"customer_email": "test#gmail.com",
"merchant_extra1": "1599817035025",
"merchant_extra2": "SAR",
"currency": "SAR",
"customer_name": "ABC",
"status": "00"
}
You might find this link helpful: https://github.com/devinweb/payment/blob/fa595fe60df4dff3c4f2189e37fd64fffdc8421b/src/Traits/Payfort/ApplePay.php#L51
This shows calculateSignature method that generates the signature for your request.
Particularly, line 67 shows that you need to have a comma followed by space between the items in the nested array.
ksort($arrData);
foreach ($arrData as $key => $value) {
if(is_array($value)){
$shaSubString = '{';
foreach ($value as $k => $v) {
$shaSubString .= "$k=$v, ";
}
$shaSubString = substr($shaSubString, 0, -2).'}';
$shaString .= "$key=$shaSubString";
}else{
$shaString .= "$key=$value";
}
}

Map array values to collection of items

How would one do the following elegantly with laravel collections ?
Map the values of the $baseMap as keys to the collection.
The baseMap :
$baseMap = [
'name' => 'new_name',
'year' => 'new_year',
];
The collection :
$items = collect([
[
'name' => 'name1',
'year' => '1000',
'not_in_basemap' => 'foo'
],
[
'name' => 'name2',
'year' => '2000',
'not_in_basemap' => 'foo'
],
//...
]);
The end result :
$result =[
[
'new_name' => 'name1',
'new_year' => '1000',
],
[
'new_name'=> 'name2',
'new_year' => '2000',
],
];
I know how to do it in plain php , just wondering what a nice collection version would be. Thanks!
I tried to find collection methods, or php functions, but without success. Some dirty code that works with different keys from both sides (items and basemap).
$result = $items->map(function($item) use ($baseMap) {
$array = [];
foreach($baseMap as $oldKey => $newKey){
if(isset($item[$oldKey])){
$array[$newKey] = $item[$oldKey];
}
}
return $array;
});
$result = $result->toArray();
Thanks to #vivek_23 and #IndianCoding for giving me idea's I ended up with the following :
I made a small edit to make sure the mapping and the items keys lined up.
so you don't have to worry of misalignment and all in laravel collection !
$baseMap = collect($baseMap)->sortKeys();
$result = $items->map(function ($item) use ($baseMap) {
return $baseMap->values()
->combine(
collect($item)->sortKeys()->intersectByKeys($baseMap)
)
->all();
});
Use intersectByKeys to filter your baseMap keys with $items values.
$result = $items->map(function($item,$key) use ($baseMap){
return array_combine(array_values($baseMap),collect($item)->intersectByKeys($baseMap)->all());
});
dd($result);
Update:
In a pure collection way,
$baseMapCollect = collect($baseMap);
$result = $items->map(function($item,$key) use ($baseMapCollect){
return $baseMapCollect->values()->combine(collect($item)->intersectByKeys($baseMapCollect->all())->values())->all();
});
dd($result);
Here are my two cents, using map. Don't know how dynamic your collection should be, but knowing the keys I would do the following:
$baseMap = [
'name' => 'new_name',
'year' => 'new_year',
];
$items = collect([
[
'name' => 'name1',
'year' => '1000',
'not_in_basemap' => 'foo'
],
[
'name' => 'name2',
'year' => '2000',
'not_in_basemap' => 'foo'
],
])->map(function($item, $key) use ($baseMap) {
return [
$baseMap['name'] => $item['name'],
$baseMap['year'] => $item['year']
];
});

Resources