I can't find an explanation of why a return response() inside a catch is not stopping the execution, i guess im missing something.
In this example the request have an error so the try-catch on the service receive ValidationException. This goes to ValidationErrorResponder and here is where the execution should finish and return a json with the errors. But it continues and return the json error response through $this->updateUserResponder->respond()
I have a defined route which execute the __invoke() method on UpdateUserAction
class UpdateUserAction
{
protected $updateUserService;
protected $updateUserResponder;
public function __construct(UpdateUserService $updateUserService, UpdateUserResponder $updateUserResponder)
{
$this->updateUserService = $updateUserService;
$this->updateUserResponder = $updateUserResponder;
}
public function __invoke(Request $request, $userId)
{
$serviceData = [
'id' => $userId,
'commandPayload' => $request->only('name')
];
return $this->updateUserResponder->respond($this->updateUserService->execute($serviceData));
}
}
class UpdateUserService extends BaseService
{
public function execute(array $data = [])
{
try {
$this->bus->addHandler(UpdateUserCommand::class, UpdateUserHandler::class);
return $this->bus->dispatch(UpdateUserCommand::class, $data, [UpdateUserValidator::class]);
} catch (ValidationException $e) {
return $this->validationErrorResponder->respond($e);
}
}
}
class UpdateUserValidator implements Middleware
{
protected $rules = [
'id' => 'uuid',
'commandPayload.name' => 'max:256'
];
protected $messages = [];
public function execute($command, callable $next)
{
$validator = Validator::make((array) $command, $this->rules, $this->messages);
if ($validator->fails()) {
throw new ValidationException($validator);
}
return $next($command);
}
}
This shoudl return the final response wiht the errors in a JSON but
class ValidationErrorResponder
{
public function respond($validator)
{
$messages = $validator->getValidator()->getMessageBag()->messages();
return response()->json(['errors' => $messages], 422);
}
}
Maybe the error it's another and the catch does not working because only are catching ValidationException.
So try catching all exception to see what happens:
class UpdateUserService extends BaseService
{
public function execute(array $data = [])
{
try {
$this->bus->addHandler(UpdateUserCommand::class, UpdateUserHandler::class);
return $this->bus->dispatch(UpdateUserCommand::class, $data, [UpdateUserValidator::class]);
} catch (\Exception $e) {
return $this->validationErrorResponder->respond($e);
}
}
}
Related
I have a class social which has:
protected $id;
public function __construct($request, $id)
{
Log::info('Processing...', ['request' => $request, 'id' => $id]);
try {
$client = new Client();
$url = sprintf($request);
$response = $client->get($url);
$json = json_decode((string) $response->getBody(), true);
return $json;
} catch (ClientException $exception) {
$responseBody = $exception->getResponse()->getBody(true);
Log::error($responseBody, ['entity_id' => $id]);
}
}
public function wikipedia($wikipedia_url, $id)
{
dd($json);
try {
$wikipedia_array = $json['parse']['text'];
$wikipedia_array = array_slice($wikipedia_array, 0, 9);
$wikipedia_array = implode($wikipedia_array, ',');
Log::info('Processed Wikipedia for', ['entity_id' => $id]);
return $wikipedia_array;
} catch (Exception $e) {
Log::error('Wikipedia:', ['message' => $e->getMessage(), 'entity_id' => $id]);
}
}
In another function I am calling a facade like this:
$id = $entity->id;
$wikipedia_id = $entity->wikipedia;
if (!empty($wikipedia_id)) {
$wikipedia_url = 'http://en.wikipedia.org/w/api.php?action=parse&prop=text§ion=0&disablelimitreport=1&format=json&page='.$wikipedia_id;
$wikipedia_html = Social::wikipedia($wikipedia_url, $id);
Log::info('Wikipedia ok for', ['entity_id' => $id]);
}
However I get this:
Type error: Too few arguments to function App\Helpers\Social::__construct(), 0 passed in /home/vagrant/liveandnow/app/Providers/SocialServiceProvider.php on line 35 and exactly 2 expected
Can anyone explain to me how to call a method, pass parameters to it but also pass them along to construct?
Here's my facade:
<?php
namespace App\Facade;
use Illuminate\Support\Facades\Facade;
class Social extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'social';
}
}
and service provider:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
the error lies in the service provider.
You define a constructor with 2 parameters,
public function __construct($request, $id)
but in your service provider you call it like this:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
You need to add both arguments when instantiating the Social class, for example like
return new Social("http://xyz.de", 1);
Hope this helps.
We have created an API that connects to the SOAP API system of the customer. We have a custom exception CustomerNotFound :
<?php
namespace App\Exceptions;
class CustomerNotFoundException extends \Exception
{
protected $code = 'customer_not_found';
protected $message = 'Customer not found!';
}
A Soap Class function for retrieving user information
public function getCustomerById($customerID)
{
$parameters = $this->appendToParameters([
'customerID' => $customerID])
->getParameters();
$result = $this->client->GetCustomerByID($parameters);
if ($result->SDKResult->ResultCode === 'Success') {
$data = $result->GetCustomerByIDResult;
return $data;
}
throw new CustomerNotFoundException();
}
And a controller that calls the soap class function:
public function getCustomerDetails(Request $request, $customerId)
{
try {
$customerDetails = $this->customerService->getCustomerById($customerId);
return $this->respond($customerDetails);
} catch (\SoapFault $e) {
$this->respondInternalError($e->getMessage());
} catch (\App\Exceptions\CustomerNotFoundException $c) {
$this->respondNotFound($c->getMessage());
}
}
I know the exception is working because when the soap function is also called on another controller, it works and then catches the exception properly when an invalid customer id is given. Also when I remove the try catch block and call the controller function with and invalid id, the api returns the CustomerNotFound exception. However, when it is inside a try catch block it just returns a STATUS 200 and an empty response (no jJSON, etc.). No errors are shown. Any ideas?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Response;
use JWTAuth;
class ApiController extends Controller
{
protected $statusCode = 200;
public function isDevMode(){
return request()->get('__devmode__', null) == 'ph';
}
public function err($msg, $code = 'error'){
return $this->respond([
'data'=> null,
'status'=> [
'code'=> $code,
'msg'=> $msg,
'success'=> false
]
]);
}
public function ok($data =[],$msg='Success',$code='success'){
return $this->respond([
'data'=> $data,
'status'=> [
'code'=> $code,
'msg'=> $msg,
'success'=> true
]
]);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
return $this;
}
public function respondNotFound($message = 'Not Found.',$code='not_found')
{
return $this->setStatusCode(404)->respondWithError($message,$code);
}
public function respondInternalError($message = 'Internal Server Error.', $code='internal_server_error')
{
return $this->setStatusCode(500)->respondWithError($message,$code);
}
public function respond($data, $headers = ['Content-type'=> 'application/json; charset=utf-8'])
{
return Response::json($data, $this->getStatusCode(), $headers);
}
public function respondWithError($message, $code = 'error')
{
return $this->respond([
'error' => [
'code' => $code,
'message' => $message
// 'status_code' => $this->getStatusCode()
]
]);
}
public function getUserTokenDetails()
{
$token = JWTAuth::getToken()->get();
try {
if (! $user = JWTAuth::parseToken())
{
return response()->json(['user_not_found'], 404);
}
}
catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token has expired.','token_expired');
}
catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token is invalid','token_invalid');
}
catch (Tymon\JWTAuth\Exceptions\JWTException $e)
{
$this->setStatusCode(401);
return $this->respondWithError('Token not found.','token_not_found');
}
$payload = JWTAuth::parseToken()->getPayload();
$user = $payload['customClaims'];
return $user['CUST_ID'];
}
}
I have an application, which for each method it does a field validation before proceeding with code execution.
public function authenticateSeller(Request $request)
{
$fields = $request->all();
$rules = config("validation-rules.loads.access");
$validator = Validator::make($fields, $rules);
if($validator->fails()) {
throw new CustomFieldValidation($validator);
}
}
I needed to pass the object to the CustomFieldValidation class to pass to the Laravel Handler class to handle the errors and return in the JSON form. How to do this?
class CustomFieldValidation extends \Exception
{
public function __construct($validator,$message= NULL, $code = NULL, Exception $previous = NULL)
{
parent::__construct($message, $code, $previous);
}
}
I was hoping to manipulate the message handler's rendering method.
public function render($request, Exception $exception)
{
if($exception instanceof CustomFieldValidation) {
foreach($validator->errors()->all() as $message) {
$errors[] = [
'message' => $message,
];
}
return response()->json($errors, 400, ['Content-type' => 'application/json; charset=utf-8'], JSON_UNESCAPED_UNICODE);
}
return parent::render($request, $exception);
}
Can someone help me?
To answer your question: just add a $validator attribute to your exception class.
class CustomFieldValidation extends \Exception
{
public $validator;
public function __construct($validator,$message= NULL, $code = NULL, Exception $previous = NULL)
{
parent::__construct($message, $code, $previous);
$this->validator = $validator;
}
}
public function render($request, Exception $exception)
{
if($exception instanceof CustomFieldValidation) {
foreach($exception->validator->errors()->all() as $message) {
$errors[] = [
'message' => $message,
];
}
return response()->json($errors, 400, ['Content-type' => 'application/json; charset=utf-8'], JSON_UNESCAPED_UNICODE);
}
return parent::render($request, $exception);
}
#Samsquanch is right, the use case example you're showing is simple and it'd be better to just return JSON from the controller. However if you're throwing the exception from some other class, this would make sense.
I'm a new user of Slim framework, I've a simple Slim 3 application, with sign in and sign up validation. But I'm not really sure if this is the right/best way to set errors and check if user is logged in -In order to redirect it to his account if session user.id exists.
I used a middleware: AuthMiddleware which includes:
class AuthMiddleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __invoke($request, $response, $next)
{
if (isset($_SESSION['user.id']) && !empty($_SESSION['user.id'])) {
return $response->withRedirect($this->container->router->pathFor('user.index'));
}
$twig = $this->container->view->getEnvironment();
if (isset($_SESSION['validation'])) {
$twig->addGlobal('errors', $_SESSION['validation']['errors']);
$twig->addGlobal('values', $_SESSION['validation']['values']);
unset($_SESSION['validation']);
}
if (isset($_SESSION['auth.signup.success'])) {
$twig->addGlobal('auth_signup_success', $_SESSION['auth.signup.success']);
unset($_SESSION['auth.signup.success']);
}
if (isset($_SESSION['auth.signin.failed'])) {
$twig->addGlobal('auth_signin_failed', $_SESSION['auth.signin.failed']);
unset($_SESSION['auth.signin.failed']);
}
$response = $next($request, $response);
return $response;
}
}
And I used Twig for my views.
Session validation assigned in the validator.php which includes:
class Validator
{
protected $errors = [];
protected $values = [];
public function validate($request, $rules)
{
foreach ($rules as $field => $rule) {
$this->values[$field] = $request->getParam($field);
try {
$rule->setName(ucfirst($field))->assert($request->getParam($field));
} catch (NestedValidationException $e) {
$this->errors[$field] = $e->getMessages()[0];
}
}
if ($this->failed()) {
$_SESSION['validation'] = [
'errors' => $this->errors,
'values' => $this->values,
];
}
return $this;
}
public function failed()
{
return !empty($this->errors);
}
}
Using Respect\Validation. Also, is this the right use of Middlewares?
Thanks in advance.
try creating a separate file for the methods, and calling it from the middleware:
<?php
class AuthMiddleware extends Middleware {
public function __invoke($request, $response, $next) {
if (!$this->container->auth->check()) {
$this->container->flash->addMessage('danger', 'Please sign in to continue.');
return $response->withRedirect($this->container->router->pathFor('auth.signin'));
}
$response = $next($request, $response);
return $response;
}
}
while the Auth class would have those methods to check:
<?php
public function check () {
return isset($_SESSION['user']);
}
public function user() {
if (isset($_SESSION['user'])) {
return User::find($_SESSION['user'])->first();
} else {
return false;
}
}
Don't forget to include the Auth Class within your $app:
<?php
$container['auth'] = function ($container) {
return new \App\Auth\Auth();
};
I'm trying to implement some unit tests for my REST controller. Everything works fine until I use the Validator facade. The index and show tests are working fine.
The error I'm getting is:
Fatal Error: Call to a member function setAttributeName() on a non-object in D:\....\controllers\AllergyController.
My code is:
//Unit test
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
public function testShow()
{
$this->allergy->shouldReceive('find')->once()->andReturn(array());
$this->call('GET', 'api/allergy/1');
$this->assertResponseOk();
}
public function testStore()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'foo');
$this->allergy->shouldReceive('create')->once();
$validator->shouldReceive('make')->once();
$validator->shouldReceive('setAttributeNames')->once();
$this->call('POST', 'api/allergy', $input);
$this->assertResponseOk();
}
}
My controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
public function index()
{
...
}
public function show($id)
{
...
}
public function store()
{
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.add.name')
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->create(Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
}
}
I can't seem to fgure out why it's throwing this error. When I call the controller with a normal api call it works fine.
Any help is much appreciated!
You are probably wanting to return the validator double from the stubbed make call.
$validator->shouldReceive('make')->once()->andReturn($validator);