How create validation of nested model to call in request in Laravel - php

I have a database table structure like the following (in laravel):
user 1-1 profile
partner 1-1 profile
user 1-N department
I want to send a save request (post) and have the user validated in UserRequest and have this class call a ProfileRequest.
Is this possible to do?
Is there any way to perform validations of related models?
Class of User request example:
class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [
'required',
Rule::unique('users')->ignore($this),
],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required'
//Validation of profile
];
}
}
Example of controller User
public function store(AdAszaUserRequest $request)
{
$input = $request->all();
$validated = $request->validated();
$input['password'] = \Hash::make($request['password']);
//
$departmentidList = array_column($input['departments'], 'id');
$AszaUser = AdAszaUser::create($input);
$models = [];
foreach ($input['departments'] as $model) {
$models[] = new AdDepartment($model);
}
///important: this line add departments without validation
$AszaUser->departments()->saveMany($models);
$AszaUser->departments()->sync($departmentidList);
return response($AszaUser, 201);
}
And Request Of deparment:
<?php
namespace App\Http\Requests\AD;
use Illuminate\Foundation\Http\FormRequest;
class AdDepartmentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|string|unique:ad_departments',
'internal_name' => 'required|string|unique:ad_departments'
];
}
}
Example of Json send in post:
{
"id":2,
"name": "Admin2",
"email": "test#gmail.com",
"lastname": "test",
"user": "test",
"password": "test",
"password_confirmation": "test",
"headquarter_id": 1,
"lastname":"test",
"remember_token": "1",
"email_verified_at": "test",
"headquarter": {
"id": 1,
"name": "ASZA ZARAGOZA",
"description": "Sede en Zaragoza",
},
"departments": [
{
"id": 1,
"name": "Intérpretes",
"internal_name": "Interprete",
"description": "Departamento de Intérpretes",
"display_id": "01",
"pivot": {
"user_id": 1,
"department_id": 1
}
},
{
"id": 10,
"name": "Psicología"
}
]
}
Can I call the DepartmentRequest to validate the elements passed in the department array?

UPDATE: 1
I don't think it is necessary, but of course it is possible
public function store(AdAszaUserRequest $request)
{
$input = $request->all();
$validated = $request->validated();
$input['password'] = \Hash::make($request['password']);
//
$departmentidList = array_column($input['departments'], 'id');
$AszaUser = AdAszaUser::create($input);
$models = [];
foreach ($input['departments'] as $model) {
/** To check validation for single item */
$validator = Validator::make($model, (new StoreEventRequest)->rules());
if (!$validator->fails()) {
$models[] = new AdDepartment($model);
} else {
/** Something wrong */
/** $errors = $validator->errors(); */
}
}
/** To check validation for array of data
$validator = Validator::make($request->only(['departments']), collect(array_map(function ($rules, $field): array {
return ['departments.*.' . $field => $rules];
}, (new StoreEventRequest)->rules()))
->collapse()
->toArray()); */
/**
* And then do what you want to do with this object
* $errors = $validator->errors();
*
if ($validator->fails()) {
return redirect('some_url')
->withErrors($validator);
} */
$AszaUser->departments()->saveMany($models);
$AszaUser->departments()->sync($departmentidList);
return response($AszaUser, 201);
}
For more information see documentation https://laravel.com/docs/6.x/validation#manually-creating-validators
UPDATE: 2
If you need to separate your request classes, you also can do it like so
Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return array_merge([
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [
'required',
Rule::unique('users')->ignore($this),
],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required'
//Validation of profile
/** Validate of departments */
'departments' => 'nullable|array',
], collect(array_map(function ($rules, $field): array {
return ['departments.*.' . $field => $rules];
}, (new StoreEventRequest)->rules()))
->collapse()
->toArray())
->toArray();
}
}

Yes you can do it like this
public function rules() {
return [
'name' => 'required|string',
'lastname' => 'required|string',
'user' => [ 'required', Rule::unique('users')->ignore($this), ],
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
'headquarter_id' => 'required',
//Validation of profile
'profile.some_field' => 'required',
//For array of objects
'profile.*.some_field' => 'required',
];
}

Related

Laravel 9 query builder where clause has empty model id

I'm working on a Laravel 9 project and have created a custom validation rule called ValidModelOwnership which should check that the field a user is trying to add is owned by a model based on some values passed to it.
I've written the rule, but when debugging and outputting $model->toSql() the id is empty?
What am I missing?
My rule:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Log;
class ValidModelOwnership implements Rule
{
/**
* The model we're checking
*/
protected $model;
/**
* Array of ownership keys
*/
protected $ownershipKeys;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct($model, $ownershipKeys)
{
$this->model = $model;
$this->ownershipKeys = $ownershipKeys;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$model = $this->model::query();
$model = $model->where($this->ownershipKeys);
Log::debug($model->toSql());
if (!$model->exists()) {
return false;
}
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return "The :attribute field doesn't belong to you and/or your company.";
}
}
And my usage in my controller:
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store($company_id, $buyer_id, Request $request)
{
$this->authorize('create', BuyerTier::class);
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
Rule::unique(BuyerTier::class)
->where('buyer_id', $buyer_id)
->where('company_id', $company_id)
],
'country_id' => [
'required',
'numeric',
new ValidModelOwnership(Country::class, [
['company_id', 80]
])
],
'product_id' => [
'required',
'numeric',
new ValidModelOwnership(Product::class, [
['company_id', 80]
])
],
'processing_class' => 'required|string',
'is_default' => [
'required',
'boolean',
new ValidDefaultModel(BuyerTier::class, $buyer_id)
],
'is_enabled' => 'required|boolean'
]);
if ($validator->fails()) {
return response()->json([
'message' => 'One or more fields has been missed or is invalid.',
'errors' => $validator->messages(),
], 400);
}
try {
$tier = new BuyerTier;
$tier->user_id = Auth::id();
$tier->company_id = $company_id;
$tier->buyer_id = $buyer_id;
$tier->country_id = $request->input('country_id');
$tier->product_id = $request->input('product_id');
$tier->name = trim($request->input('name'));
$tier->description = $request->input('description') ?? null;
$tier->processing_class = $request->input('processing_class');
$tier->is_default = $request->boolean('is_default');
$tier->is_enabled = $request->boolean('is_enabled');
$tier->save();
return response()->json([
'message' => 'Buyer tier has been created successfully',
'tier' => $tier
], 201);
} catch (\Exception $e) {
return response()->json([
'message' => $e->getMessage()
], 400);
}
}
I've hard-coded my id's to illustrate that even when set statically, it's not passed through:
[2023-01-19 09:40:59] local.DEBUG: select * from products where (company_id = ?) and products.deleted_at is null
Laravel (and most other frameworks) extract out variables when building SQL queries to prevent SQL injection.
So the following eloquent query:
User::where('name', 'Larry');
will become:
SELECT * FROM `users` WHERE `name` = ?
and it will also pass an array of bindings: ['Larry']. When SQL processes the query it replaces replaces the ? with the values in the bindings.
So if you want to see the full query you need to log the SQL and the bindings:
Log::debug($model->toSql());
Log::debug($model->getBindings());

How to store user details after paypal payment approval?

I'm building an online courses website, where students can buy a course via PayPal. The payment process is working successfully, But I'm confused on where or how to store course and user data after PayPal Payment Approval.
I have a users table, courses table and pivot table: course_students where I store the id of the course and the id of the student:
-------------
course_students
--------------
student_id course_id
1 2
----------
This is PayPalService Class:
class PayPalService
{
use ConsumesExternalServices;
protected $baseUri;
protected $clientId;
protected $clientSecret;
public function __construct()
{
$this->baseUri = config('services.paypal.base_uri');
$this->clientId = config('services.paypal.client_id');
$this->clientSecret = config('services.paypal.client_secret');
}
public function resolveAuthorization(&$queryParams, &$formParams, &$headers)
{
$headers['Authorization'] = $this->resolveAccessToken();
}
public function decodeResponse($response)
{
return json_decode($response);
}
public function resolveAccessToken()
{
$credentials = base64_encode("{$this->clientId}:{$this->clientSecret}");
return "Basic {$credentials}";
}
public function handlePayment(Request $request)
{
$order = $this->createOrder($request->value, $request->currency);
$orderLinks = collect($order->links);
$approve = $orderLinks->where('rel', 'approve')->first();
session()->put('approvalId', $order->id);
return redirect($approve->href);
}
public function handleApproval()
{
if (session()->has('approvalId')) {
$approvalId = session()->get('approvalId');
$payment = $this->capturePayment($approvalId);
$name = $payment->payer->name->given_name;
$payment = $payment->purchase_units[0]->payments->captures[0]->amount;
$amount = $payment->value;
$currency = $payment->currency_code;
return redirect()
->route('success')
->with('payment', "Thanks, {$name}. We received your {$amount}{$currency} payment.");
}
// $errorMessage = 'We cannot capture the payment. Try again, please';
return redirect()
->route('paymentform')
->with('error','We cannot capture the payment. Try again, please');
}
public function createOrder($value, $currency)
{
return $this->makeRequest(
'POST',
'/v2/checkout/orders',
[],
[
'intent' => 'CAPTURE',
'purchase_units' => [
0 => [
'amount' => [
'currency_code' =>strtoupper($currency),
'value' => round($value * $factor = $this->resolveFactor($currency)) / $factor,
]
]
],
'application_context' => [
'brand_name' => config('app.name'),
'shipping_preference' => 'NO_SHIPPING',
'user_action' => 'PAY_NOW',
'return_url' => route('approval'),
'cancel_url' => route('cancelled'),
]
],
[],
$isJsonRequest = true,
);
}
public function capturePayment($approvalId)
{
return $this->makeRequest(
'POST',
"/v2/checkout/orders/{$approvalId}/capture",
[],
[],
[
'Content-Type' => 'application/json'
],
);
}
public function resolveFactor($currency)
{
$zeroDecimalCurrencies = ['JPY'];
if (in_array(strtoupper($currency), $zeroDecimalCurrencies)) {
return 1;
}
return 100;
}
}
PaymentController:
public function paymentForm($course_uuid)
{
$currencies = Currency::all();
$platforms = PaymentPlatform::get();
$course = Course::where('uuid', $course_uuid)->where('status',1)->where('availability',1)->first();
return view('public.payment.paypalform', compact('currencies','platforms','course'));
}
/**
* implment Payemnt process
*
* #param Request $request
*/
public function pay(Request $request)
{
// dd($request->all());
$rules = [
'value' => ['required', 'numeric', 'min:5'],
'currency' => ['required', 'exists:currencies,iso'],
'payment_platform' => ['required', 'exists:payment_platforms,id'],
];
$request->validate($rules);
$paymentPlatform = $this->paymentPlatformResolver
->resolveService($request->payment_platform);
session()->put('paymentPlatformId', $request->payment_platform);
return $paymentPlatform->handlePayment($request);
}
protected function approval()
{
if (session()->has('paymentPlatformId')) {
$paymentPlatform = $this->paymentPlatformResolver
->resolveService(session()->get('paymentPlatformId'));
return $paymentPlatform->handleApproval();
}else{
return redirect()
->route('courses.levels')
->withErrors('We cannot retrieve your payment platform. Try again, please.');
}
}
protected function canceled()
{
return redirect()
->route('courses.levels')
->withErrors('You cancelled the payment.');
}
In your payment controller you can save you the course information in session. After payment when user will redirect, save the information to your desired database table..
//save course info or other info
session()->put('courseInfo', [your data]);
your controller function:
/**
* implment Payemnt process
*
* #param Request $request
*/
public function pay(Request $request)
{
// dd($request->all());
$rules = [
'value' => ['required', 'numeric', 'min:5'],
'currency' => ['required', 'exists:currencies,iso'],
'payment_platform' => ['required', 'exists:payment_platforms,id'],
];
$request->validate($rules);
$paymentPlatform = $this->paymentPlatformResolver
->resolveService($request->payment_platform);
session()->put('paymentPlatformId', $request->payment_platform);
//save course info or other info
session()->put('courseInfo', [your data]);
return $paymentPlatform->handlePayment($request);
}
you another function: approval() call another function $paymentPlatform->handleApproval(); inside of it after payment done.
Go to handleApproval() function and hopefully you will find there user account creation codes. Now get the session value session()->get('courseInfo') and save in your desired table.

Validation-messages inside a FormRequest for an integer-array are not accepted

I'm using FormRequest to validate, also overriding messages function to set custom messages.
Somehow, I can't get integer-array validation to work. This is where I'm at:
This is what my request should look like:
// ACCEPTED:
{
"name": "my name",
"items": [20, 7, 1, 2]
}
// REJECTED
{
"name": "Meine neue Kampagne",
"items": ["ANY STRING HERE", 1, 2]
}
So I want my items to only be an array of integers. These are my rules, which are fully working:
return [
'name' => 'required|max:191',
'items' => 'array',
'items.*' => 'int|exists:jobpostings,id'
];
So far, I tried the following (all found in online tutorials):
return [
'items.*.int' => "item is not int",
];
return [
'items.*[
'int' => "item is not int"
]
];
They all just gave the common "The items.0 must be an integer." message.
Can anyone help me maybe?
Here is the complete FormRequest:
<?php
namespace App\Http\Requests\Campaign;
use Illuminate\Foundation\Http\FormRequest;
class CreateCampaignRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:191',
'items' => 'array',
'items.*' => 'int|exists:items,id'
];
}
public function messages()
{
return [
'name.required' => __('campaign.createCampaign.name_required'),
'name.max' => __('campaign.createCampaign.name_max'),
'items.array' => __('campaign.createCampaign.items_array'),
'items.*.int' => __('campaign.createCampaign.items_*_int'),
'items.*.exists' => __('campaign.createCampaign.items_*_exists'),
];
}
}

Validation of JSON Post using Laravel 5.6 api

Having a struggle with validating JSON input for an api on Laravel 5.6. I've been trying the
solution at How to get and validate application/json data in Laravel?
but still doesn't resolve.
The supporting class:
<?php
namespace App\Http\Controllers\API;
class ResponseObject
{
const status_ok = "OK";
const status_fail = "FAIL";
const code_ok = 200;
const code_failed = 400;
const code_unauthorized = 403;
const code_not_found = 404;
const code_error = 500;
public $status;
public $code;
public $messages = array();
public $result = array();
}
and the Controller:
namespace App\Http\Controllers\API;
use App\Http\Resources\MyItemsResource;
use App\MyItem;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use \Illuminate\Http\Response;
use \Illuminate\Support\Facades\Response as FacadeResponse;
use \Illuminate\Support\Facades\Validator;
use App\Http\Controllers\API\ResponseObject;
class MyItemsController extends Controller
{
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$response = new ResponseObject;
/*
sending a body of
{"code": "45678", "description": "My great item"}
then:
// print_r($request->json()->all()); die();
produces:
Array
(
[code] => 45678
[description] => My great item
)
so data is getting to server
*/
$validator = Validator::make($request->json()->all(), [
'code', 'required',
'description', 'required',
]);
if($validator->fails()){
$response->status = $response::status_failed;
$response->code = $response::code_failed;
foreach ($validator->errors()->getMessages() as $item) {
array_push($response->messages, $item);
}
} else {
$myItem = new MyItem();
$myItem->code = $request->code;
$myItem->description = $request->description;
$response->status = $response::status_ok;
$response->code = $response::code_ok;
$response->result = $myItem;
}
return FacadeResponse::json($response);
/*
Returns:
{
"status": "FAILED",
"code": 400,
"messages": [
[
"The 1 field is required."
],
[
"The 3 field is required."
]
],
"result": []
}
*/
}
}
Why does it refer to the fields as 1 field and 3 field?
What am I missing that it doesn't pick up the fields as they are getting to the controller in the $request?
Any guidance would be greatly appreciated.
It refers to the keys of the validation rules.
Here you just have an array of 4 words (indexes 1 and 3 are valid rules that fail):
$validator = Validator::make($request->json()->all(), [
'code', 'required',
'description', 'required',
]);
^ This equals to:
$validator = Validator::make($request->json()->all(), [
0 => 'code',
1 => 'required',
2 => 'description',
3 => 'required',
]);
And looks for inputs 0, 1, 2, 3.
Instead you should make an associative array:
$validator = Validator::make($request->json()->all(), [
'code' => 'required',
'description' => 'required',
]);
^ This will look for inputs code and description and validate them with the rule required

Updating Data to db in Laravel

I have a problem while updating data to the database in laravel. Normally the data gets inserted to the database with store function(POST request). But the update POST request doesn't make a update to the database. And my code is
routes.php
Route::resource('Customer','CustomerDetailController');
Route::get('/', function () {
return view('welcome');
});
Model
CustomerDetail.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CustomerDetail extends Model
{
//
public $table = "customer_details";
protected $fillable = [
'Name', 'Contact_Number','Address','NoOfCans','Price'
];
}
Controller
CustomerDetailController.php
<?php
namespace App\Http\Controllers;
use App\CustomerDetail;
use Illuminate\Http\Request;
use App\Http\Requests;
class CustomerDetailController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
//
$Customers=CustomerDetail::all();
if($Customers){
$response = [
'msg' => 'Customer Found',
'Customer' => $Customers
];
return response()->json($response, 201);
}
else {
$response=[
'msg'=>'No Customer Found',
];
return response()->json($response, 404);
}
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
$this->validate($request, [
'Name' => 'required',
'Contact_Number' => 'required',
'Address' => 'required',
'NoOfCans' => 'required',
'Price' => 'required',
]);
$Name = $request->input('Name');
$Contact_Number = $request->input('Contact_Number');
$Address = $request->input('Address');
$NoOfCans = $request->input('NoOfCans');
$Price = $request->input('Price');
$CustomerDetail = new CustomerDetail([
'Name' => $Name,
'Contact_Number' => $Contact_Number,
'Address' => $Address,
'NoOfCans' => $NoOfCans,
'Price' => $Price
]);
if ($CustomerDetail->save()) {
$response = [
'msg' => 'Customer Created',
'Customer' => $CustomerDetail
];
return response()->json($response, 201);
}
$response = [
'msg' => 'An Error Occured',
];
return response()->json($response, 502);
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
//
$Customer=CustomerDetail::find($id);
if($Customer){
$response = [
'msg' => 'Customer Found',
'Customer' => $Customer
];
return response()->json($response, 201);
}
else {
$response=[
'msg'=>'Customer Not Found',
];
return response()->json($response, 404);
}
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
$this->validate($request, [
'Name' => 'required',
'Contact_Number' => 'required',
'Address' => 'required',
'NoOfCans' => 'required',
'Price' => 'required',
]);
$CustomerUpdate = CustomerDetail::find($id);
$CustomerUpdate->Name = $request->input('Name');
$CustomerUpdate->Contact_Number = $request->input('Contact_Number');
$CustomerUpdate->Address = $request->input('Address');
$CustomerUpdate->NoOfCans = $request->input('NoOfCans');
$CustomerUpdate->Price = $request->input('Price');
if ($CustomerUpdate->save()) {
$response = [
'msg' => 'Customer Updated',
'Customer' => $CustomerUpdate
];
return response()->json($response, 201);
}
$response = [
'msg' => 'An Error Occured',
];
return response()->json($response, 502);
}
}
And the data gets inserted when i make a post request for store as
{
"Name":"MRK",
"Contact_Number":"1234567890",
"Address":"newyork",
"NoOfCans":"3",
"Price":"20"
}
with Output
{
"msg": "Customer Created",
"Customer": {
"Name": "MRK",
"Contact_Number": "1234567890",
"Address": "newyork",
"NoOfCans": "3",
"Price": "20",
"updated_at": "2016-11-20 17:17:55",
"created_at": "2016-11-20 17:17:55",
"id": 4
}
}
But when I try to Update the same data with PUT or PATCH request
Input
{
"Name":"MRK",
"Contact_Number":"9876543210",
"Address":"Chennai",
"NoOfCans":"2",
"Price":"20"
}
I get the JSON Response as Updated but I don't see the values got Updated in the database
{
"msg": "Customer Updated",
"Customer": {
"ID": 4,
"created_at": "2016-11-20 17:17:55",
"updated_at": "2016-11-20 17:20:44",
"Name": "MRK",
"Contact_Number": "9876543210",
"Address": "Chennai",
"NoOfCans": "2",
"Price": "20"
}
}
The database contains the values which are originally inserted at first. No updated values are inserted into the database
enter image description here
Update code
public function update(Request $request, $id)
{
//
$this->validate($request, [
'Name' => 'required',
'Contact_Number' => 'required',
'Address' => 'required',
'NoOfCans' => 'required',
'Price' => 'required',
]);
$CustomerUpdate = CustomerDetail::find($id);
$CustomerUpdate->Name = $request->input('Name');
$CustomerUpdate->Contact_Number = $request->input('Contact_Number');
$CustomerUpdate->Address = $request->input('Address');
$CustomerUpdate->NoOfCans = $request->input('NoOfCans');
$CustomerUpdate->Price = $request->input('Price');
if ($CustomerUpdate->save()) {
$response = [
'msg' => 'Customer Updated',
'Customer' => $CustomerUpdate
];
return response()->json($response, 201);
}
$response = [
'msg' => 'An Error Occured',
];
return response()->json($response, 502);
}
The problem is with the model. I have not mentioned the primary key in my model so the database is not updated
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CustomerDetail extends Model
{
//
public $table = "customer_details";
Protected $primaryKey = "ID";
protected $fillable = [
'Name', 'Contact_Number','Address','NoOfCans','Price'
];
}

Categories