php - Laravel best strategy to serve requests from API and form at the same time

Using Laravel 7.*, I'm tasked with creating a simple app to send requests for payment, the user fill a form and send the data then I validate the user inputs and create a new Payment instance.
Then the user is redirected back to the same page. (Of course there are other requests for listing all payments and updating a payment):
//In PaymentController.php
public function store()
{
$inputData = $this->validateRequest();
$person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));
if ($person instanceof Person) {
$this->paymentRepository->create($inputData, $person);
return back()->with('successMessage', 'Your payment request registered successfully.');
} else {
return back()->with('failureMessage', 'Shoot! Cannot find a peron with the given Identification Number.')->withInput();
}
}
Everything is fine, but I need to implement a Restful API to do the same request and get a valid json response, Assuming there is no front-end JavaScript framework, what is the best approach to achieve this goal?
Should I create a separate controller? Or Simply check whether request is sent from a traditional form or an API client? Am I missing a design pattern?
Answer
Solution:
The simply way would be to check what kind of response you should send back:
//In PaymentController.php
public function store()
{
$inputData = $this->validateRequest();
$person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));
if ($person instanceof Person) {
$this->paymentRepository->create($inputData, $person);
if (request()->expectsJson()) {
return response('', 201); // Just a successfully created response
}
return back()->with('successMessage', 'Your payment request registered successfully.');
} else {
if (request()->expectsJson()) {
return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
}
return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
}
}
You can of course choose to encapsulate this in a class that implements the interface
class PaymentResponse implements Responsible {
private $success;
public function __construct($success) {
$this->success = $success;
}
public function toResponse($request) {
if ($this->success) {
if (request()->expectsJson()) {
return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
}
return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
}
if (request()->expectsJson()) {
return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
}
return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
}
}
then your code would be:
//In PaymentController.php
public function store()
{
$inputData = $this->validateRequest();
$person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));
if ($person instanceof Person) {
$this->paymentRepository->create($inputData, $person);
return new PaymentResponse(true);
} else {
return new PaymentResponse(false);
}
}
You can of course also extract the controller logic to a separate library and then have two separate controller methods and still use the responsible object if you want. It really is dependent on your use case and what works best for you
Answer
Solution:
I think another approach would be to make use of service-repository pattern. You wrap your application service in a separate class. A service is the interactor between controller and repository. The flow would look like[request] -> [controller] -> [service] -> [repository]
.
By leveraging this pattern you can then resuse your service in different areas in the app. For example you can have a controller specific for serving traditional web apps, and a controller for serving SPA, by returning JSON data, but serve the same business process.
E.g. :
Payment Response:
class PaymentStoreResponse
{
protected $message;
protected $code;
protected $extraData;
public function __construct($message, $code, $extraData = "")
{
$this->message = $message;
$this->code = $code;
$this->extraData = $extraData;
}
public function getMessage()
{
return $this->message;
}
public function getCode()
{
return $this->code;
}
public function getExtraData()
{
return $this->extraData;
}
}
Payment Service:
function store($data)
{
$person = $this->personRepository->findOneByAttribute('id_number', $data('id_number'));
if ($person instanceof Person) {
$this->paymentRepository->create($inputData, $person);
return new PaymentResponse("paymentSuccess", 201, "successMessage");
} else {
return new PaymentResponse("notFound", 404, "failureMessage");
}
}
Controller:
// App\Controllers\Web\PaymentController.php
function store(Request $request)
{
$inputData = $this->validateRequest();
$response = $this->paymentService->store($inputData);
return back()->with($response->getExtraData(), $response->getMessage())
}
// App\Controllers\Api\PaymentController.php
function store(Request $request)
{
// validation might be different because
// api might need to authenticate with jwt etc.
$inputData = $this->validateRequest();
$response = $this->paymentService->store($inputData);
return response()->json(['message' => $response->getMessage()], $response->getCode());
}
This will result in a cleaner controller because the controller will only handle request validation and response, while delegating business process to the service class (payment service). The business logic is also centralized in the service layer, which means if there is a change to the business, it will apply to the API controller and Web controller. Hence, it will save you from a refactoring nightmare.
You can explore different architectures such as Clean Architecture + DDD. It will make your development experience better, achieving a centralized domain logic, and low coupling between layers by depending on abstractions.
Share solution ↓
Additional Information:
Link To Answer People are also looking for solutions of the problem: object of class stdclass could not be converted to string
Didn't find the answer?
Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.
Similar questions
Find the answer in similar questions on our website.
Write quick answer
Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.