Skip to content

Instantly share code, notes, and snippets.

@tisuchi
Last active September 12, 2019 10:57
Show Gist options
  • Save tisuchi/0697044ead825a2c452393eda81db60d to your computer and use it in GitHub Desktop.
Save tisuchi/0697044ead825a2c452393eda81db60d to your computer and use it in GitHub Desktop.
Handling InputExcpetion on the package side.
<?php
namespace App\Http\Controllers;
use App\Exceptions\InputException;
use Bugsnag\BugsnagLaravel\Facades\Bugsnag;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/**
* Global Error Response
* @param [type] $message [description]
* @param [type] $errorcode [description]
* @return [type] [description]
*/
public function errorResponse($message, $errorcode)
{
return \Response::json([
'status' => 'error',
'code' => $errorcode,
'message' => $message, //Bad Request
], $errorcode);
}
public function facebookGraphErrors(\Facebook\Exceptions\FacebookResponseException $e)
{
return [
'graph-error-code' => $e->getCode(),
'source' => 'facebook-graph',
'message' => "Graph returned an error: " . $e->getMessage()
];
}
public function facebookSDKErrors(\Facebook\Exceptions\FacebookSDKException $e)
{
return [
'facebook-sdk-error-code' => $e->getCode(),
'source' => 'facebook-sdk',
'message' => "Facebook SDK returned an error: " . $e->getMessage()
];
}
/**
* @param mixed $response
* @return \Illuminate\Http\JsonResponse
*/
protected function generateResponse($response="")
{
if (!is_array($response) && !is_object($response)) {
$response = ['message' => $response];
} else {
$response = json_decode(json_encode($response,true),true);
$response['status'] = 'success';
}
if(request()->method() == 'DELETE') {
return response()->json(null, 204);
} else {
return response()->json($response, 200);
}
}
/**
* @param \Throwable $exception
* @param bool|string $userMessage
* @return \Illuminate\Http\JsonResponse
*/
protected function generateErrorResponse(\Throwable $exception, $userMessage = false, $inputNamespace = false)
{
$response = [
'status' => 'error',
'message' => $userMessage ? $userMessage : $exception->getMessage()
];
$statusCode = $exception->getCode() == 0 ? 500 : $exception->getCode();
if (env('APP_ENV') == 'production' && $statusCode == 500) {
Bugsnag::notifyException($exception);
}
$response = array_merge($response, [
'error_code' => $statusCode,
'error_message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
]);
if ($inputNamespace === false){
if ($exception instanceof InputException){
$response['message'] = $exception->getUserMessage();
$response['fields'] = $exception->getValidatorError();
}
} else {
if ($exception instanceof $inputNamespace.'\InputException' ) {
$response['message'] = $exception->getUserMessage();
$response['fields'] = $exception->getValidatorError();
}
}
return response()->json($response, $statusCode);
}
public static function generateFatalErrorResponse()
{
$error = error_get_last();
if (!$error)
return;
$response = [
'error' => $error['type']
];
if (env('APP_ENV') == 'production')
{
}
}
protected function getOptionalParameters(Request $request, $parameters)
{
$params = [];
foreach ($parameters as $key => $parameter) {
if (!$request->has($parameter)) {
continue ;
}
$newKey = is_numeric($key) ? $parameter : $key;
$params[$newKey] = $request->$parameter;
}
return $params;
}
}
<?php
namespace Avana\Shipping\Exceptions;
class InputException extends \Exception
{
protected $validatorError;
public function __construct($validatorError)
{
$this->validatorError = $validatorError;
parent::__construct("Please refer to \"fields\" for more details.", 400);
}
public function getValidatorError()
{
return $this->validatorError;
}
public function getUserMessage()
{
return "Input is invalid or missing required parameters.";
}
}
<?php
namespace App\Http\Controllers\V3;
use Response;
use Validator;
use Exception;
use Illuminate\Http\Request;
use Avana\Shipping\ShippingZone;
use App\Exceptions\InputException;
use App\Http\Controllers\Controller;
class ShippingZoneController extends Controller
{
public function index($shopId)
{
try{
$shippingZone = (new ShippingZone())->index($shopId);
return $this->generateResponse($shippingZone);
} catch(\Throwable $e){
return $this->generateErrorResponse($e);
}
}
public function store(Request $request, $shopId)
{
try{
$shippingZone = (new ShippingZone)->createShippingZone($request, $shopId);
return $this->generateResponse($shippingZone);
} catch(\Throwable $e){
return $this->generateErrorResponse($e, false, 'Avana\Shipping\Exceptions\InputException');
}
}
public function update(Request $request, $shopId, $zoneId)
{
try{
$shippingZone = (new ShippingZone)->updateShippingZone($request, $shopId, $zoneId);
return $this->generateResponse($shippingZone);
} catch(\Throwable $e){
return $this->generateErrorResponse($e, false, 'Avana\Shipping\Exceptions\InputException');
}
}
/**
* Delete a shipping destination
*
* @param $shopId
* @param $zoneId
* @return \Illuminate\Http\JsonResponse
*/
public function delete($shopId, $zoneId)
{
try{
$shippingZone = (new ShippingZone())->deleteShippingZone($shopId);
return $this->generateResponse($shippingZone);
} catch(\Throwable $e){
return $this->generateErrorResponse($e);
}
}
}
<?php
namespace Avana\Shipping\Repositories;
use App\Models\Shipping;
use App\Models\ShippingCountry;
use App\Models\ShippingRate;
use Avana\Shipping\Exceptions\InputException;
use Rakit\Validation\Validator;
class ShippingZoneRepository
{
public function index($shopId)
{
$shipping = $this->hasShipping($shopId);
return $this->formattedDataForIndex($shipping);
}
public function hasShipping($shopId)
{
return Shipping::where('shop_id', $shopId)->first();
}
public function hasShippingRate($shippingId)
{
return ShippingRate::where('shipping_id', $shippingId)->first();
}
protected function formattedDataForIndex($shippingData)
{
if ($shippingData){
$shippingRate = $this->hasShippingRate($shippingData->shipping_id);
$data['id'] = optional($shippingRate)->shipping_rate_id ;
$data['name'] = optional($shippingRate)->name;
$data['type'] = optional($shippingRate)->type;
$data['freeshipping_enabled'] = optional($shippingRate)->freeshipping_enabled;
$data['freeshipping_min_price'] = optional($shippingRate)->freeshipping_min_price;
$data['handling_fee'] = optional($shippingRate)->handling_fee;
$data['handling_fee_type'] = optional($shippingRate)->handling_fee_type;
return $data;
}
$data['id'] = '';
$data['name'] = '';
$data['type'] = '';
$data['freeshipping_enabled'] = '';
$data['freeshipping_min_price'] = '';
$data['handling_fee'] = '';
$data['handling_fee_type'] = '';
return $data;
}
public function storeShippingZoneMethod($request, $shopId, $zoneId)
{
$hasShippingRate = ShippingRate::where('shipping_rate_id', $zoneId)->firstOrFail();
if ($hasShippingRate) {
$hasShippingRate->update([
'dynamic_first_unit' => $request->dynamic_first_unit,
'dynamic_first_rate' => $request->dynamic_first_rate,
'dynamic_addon_unit' => $request->dynamic_addon_unit,
'dynamic_addon_rate' => $request->dynamic_addon_rate,
'flat_rate' => $request->flat_rate,
'range_fallback_rate' => $request->range_fallback_rate,
'courier_code' => $request->courier_code
]);
ShippingCountry::create([
'shipping_id' => $hasShippingRate->shipping_id,
'country_id' => $request->country_id,
'total_states_selected' => count(explode(",", $request->states))
]);
}
return $this->formattedDataForShippingMethod($hasShippingRate);
}
protected function formattedDataForShippingMethod($hasShippingRate)
{
$data['id'] = $hasShippingRate->shipping_rate_id ;
$data['dynamic_first_unit'] = $hasShippingRate->dynamic_first_unit;
$data['dynamic_first_rate'] = $hasShippingRate->dynamic_first_rate;
$data['dynamic_addon_unit'] = $hasShippingRate->dynamic_addon_unit;
$data['dynamic_addon_rate'] = $hasShippingRate->dynamic_addon_rate;
$data['flat_rate'] = $hasShippingRate->flat_rate;
$data['range_fallback_rate'] = $hasShippingRate->range_fallback_rate;
$data['courier_code'] = $hasShippingRate->courier_code;
$data['shipping_id'] = $hasShippingRate->shipping_id;
$data['country_id'] = $hasShippingRate->country_id;
$data['total_states_selected'] = $hasShippingRate->total_states_selected;
return $data;
}
public function createShippingZone($request, $shopId)
{
$validator = new Validator;
$validation = $validator->make($request->all(), [
'type' => 'required',
'shipping_id' => 'required',
'freeshipping_enabled' => 'required',
'freeshipping_min_price' => 'required',
'handling_fee' => 'required',
'handling_fee_type' => 'required'
]);
$validation->validate();
if ($validation->fails()){
$errors = $validation->errors();
throw new InputException($errors->firstOfAll());
}
$createShippingZone = ShippingRate::create([
'type' => $request->type,
'unit' => 'weight',
'shipping_id' => $request->shipping_id,
'freeshipping_enabled' => $request->freeshipping_enabled,
'freeshipping_min_price' => $request->freeshipping_min_price,
'handling_fee' => $request->handling_fee,
'handling_fee_type' => $request->handling_fee_type
]);
return $this->formattedDataForIndex($createShippingZone);
}
public function updateShippingZone($request, $shopId, $zoneId)
{
$validator = new Validator;
$validation = $validator->make($request->all(), [
'type' => 'required',
'shipping_id' => 'required|integer',
'freeshipping_enabled' => 'required|integer|max:1',
'freeshipping_min_price' => 'required|regex:/^\d*(\.\d{4})?$/',
'handling_fee' => 'required|regex:/^\d*(\.\d{4})?$/',
'handling_fee_type' => 'required'
]);
$validation->validate();
if ($validation->fails()){
$errors = $validation->errors();
throw new InputException($errors->firstOfAll());
}
$hasShippingZone = ShippingRate::where('shipping_rate_id', $zoneId)->first();
if (! $hasShippingZone) {
return false;
}
$hasShippingZone->update([
'type' => $request->type,
'unit' => 'weight',
'shipping_id' => $request->shipping_id,
'freeshipping_enabled' => $request->freeshipping_enabled,
'freeshipping_min_price' => $request->freeshipping_min_price,
'handling_fee' => $request->handling_fee,
'handling_fee_type' => $request->handling_fee_type
]);
return $this->formattedDataForIndex($hasShippingZone);
}
public function deleteShippingZone($shopId, $zoneId)
{
$hasShipping = ShippingRate::where('shipping_rate_id', $zoneId)->firstOrFail();
if (! $hasShipping) {
return false;
}
$hasShipping->delete();
return true;
}
}
@tisuchi
Copy link
Author

tisuchi commented Sep 12, 2019

Plan

We are planning to use InputException on the package side. So, the package can act as standalone.

Problem

Once we use InputException from the package, the namespace will be based on the package. For example-

Structure:

Avana**{PackageName}**\Exceptions\InputException

Implementation:

Avana\Shipping\Exceptions\InputException'.

Now when we check in the action of generateErrorResponse() in the controller whether the exception is the instance of InputException (here the namespace of is App\Exceptions\InputException ) or not, it will always return false. In this way, we cannot implement.

Solution.

Passing an extra parameter in generateErrorResponse() action in the controller where it's false by default.

protected function generateErrorResponse(\Throwable $exception, $userMessage = false, $inputNamespace = false)
{
....

Inside the action, it has been checked by if-else like this way-

if ($inputNamespace === false){
    if ($exception instanceof InputException){
        $response['message'] = $exception->getUserMessage();
        $response['fields'] = $exception->getValidatorError();
    }
} else {
    if ($exception instanceof $inputNamespace.'\InputException' ) {
        $response['message'] = $exception->getUserMessage();
        $response['fields'] = $exception->getValidatorError();
    }
}

Implementation

By default, using generateErrorResponse() will be fine. None of the implemented parts will be affected by that.

try{
    // code
} catch(\Throwable $e){
    return $this->generateErrorResponse($e);
}

However, when we need to implement it from the different namespace or from the package, need to pass extra params.

try{
    // code
} catch(\Throwable $e){
    return $this->generateErrorResponse($e, false, 'Avana\Shipping\Exceptions\InputException');
}

Changed code

Controller:

77 line: Added extra parameter called $inputNamespace.

protected function generateErrorResponse(\Throwable $exception, $userMessage = false, $inputNamespace = false)

97 line: Rewrite the if statement

if ($inputNamespace === false){
    if ($exception instanceof InputException){
        $response['message'] = $exception->getUserMessage();
        $response['fields'] = $exception->getValidatorError();
    }
} else {
    if ($exception instanceof $inputNamespace.'\InputException' ) {
        $response['message'] = $exception->getUserMessage();
        $response['fields'] = $exception->getValidatorError();
    }
}
Added InputException.php in the package side.
ShippingZoneController.php:

Line 33 & 44: Added parameters like this way-

return $this->generateErrorResponse($e, false, 'Avana\Shipping\Exceptions\InputException');
ShippingZoneRepository.php:

Line 152: The InputException has been used from the package side.

 throw new InputException($errors->firstOfAll());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment