Am I configuring my error handler and exception manager for Zend-Log correctly? - zend-framework3

I have a ZF 3 application. Messages I explicitly log with e.g $log->debug() Show up just fine. Exceptions don't. Errors seem to show up because that's the default php config to go to stderr. Here is the relevant lines from modules.config.php:
'service_manager' => [
'factories' => [
. . . .
'log' => \Zend\Log\LoggerServiceFactory::class,
],
],
'log' => [
'writers' => [
[
'name' => 'stream',
'options' => [ 'stream' => 'php://stderr' ]
],
],
'errorHandler' => true,
'exceptionhandler' => true,
],
The lines in the source that lead me to believe this is the correct config.
if (isset($options['exceptionhandler']) && $options['exceptionhandler'] === true) {
static::registerExceptionHandler($this);
}
if (isset($options['errorhandler']) && $options['errorhandler'] === true) {
static::registerErrorHandler($this);
}
To test it I made the following endpouints:
public function errorAction()
{
$msg = $this->params()->fromQuery('msg', 'Default Error message');
trigger_error('Index Error Action' . $msg, E_USER_ERROR);
$model = new JsonErrorModel(['msg' => $msg]);
return $model;
}
public function exceptionAction()
{
$msg = $this->params()->fromQuery('msg', 'Default Error message');
throw new \RuntimeException('Index Exception Action' . $msg);
$model = new JsonErrorModel(['msg' => $msg]);
return $model;
}

You have typo in your configuration array
'log' => [
....
'errorHandler' => true,
....
],
This index shouldn't be camelCase it should be errorhandler (all letters are lowercase). I would also add fatal_error_shutdownfunction => true to configutation so you will log fatal errors.
Zend uses set_exception_handler to handle exceptions, so keep in mind that logging exceptions will work only if they are not in try/catch block.
Sets the default exception handler if an exception is not caught within a try/catch block
Source: http://php.net/manual/en/function.set-exception-handler.php
All these functions may be set manually:
\Zend\Log\Logger::registerErrorHandler($logger);
\Zend\Log\Logger::registerFatalErrorShutdownFunction($logger);
\Zend\Log\Logger::registerExceptionHandler($logger);
If you want test it, you can do following things:
Error
public function errorAction()
{
$log = $this->getServiceLocator()->get('log'); // init logger. You shouldn't use getServiceLocator() in controller. Recommended way is injecting through factory
array_merge([], 111);
}
It should write in log:
2017-03-09T15:33:47+01:00 WARN (4): array_merge(): Argument #2 is not an array {"errno":2,"file":"[...]\\module\\Application\\src\\Application\\Controller\\IndexController.php","line":80}
Fatal error
public function fatalErrorAction()
{
$log = $this->getServiceLocator()->get('log'); // init logger. You shouldn't use getServiceLocator() in controller. Recommended way is injecting through factory
$class = new ClassWhichDoesNotExist();
}
Log:
2017-03-09T15:43:06+01:00 ERR (3): Class 'Application\Controller\ClassWhichDoesNotExist' not found {"file":"[...]\\module\\Application\\src\\Application\\Controller\\IndexController.php","line":85}
Or you could initialize logger in Module.php file if you need logger globally.
I don't think it is possible to log exception in controller's action. I'm not sure, but action is dispatched in try/catch block.

Related

Processor data value error - ApiPlatform 3.0

I recently migrate a huge project from Apiplatform 2.6 to 3.0. I'm converting my controllers into providers and processors but i encounter some problems.
I try to block an action to my users on a showcase website. My endpoint look like that (with id = showcase website id and blockOrder = boolean) :
#[ApiResource(
operations: [
new Post(
uriTemplate: '/shop/{id}/all/block-order/{blockOrder}',
status: 200,
processor: UpdateBlockOrderToAllParticipantsProcessor::class,
read: false,
deserialize: false,
serialize: false
),
],
normalizationContext: ['groups' => ['shop:read:bo', 'shop:dashboard:bo', 'shop:read:bo', 'lifecycle']],
denormalizationContext: ['groups' => ['shop:write:bo']],
provider: ShopProvider::class
)]
My problem comes from my value "$data" in my processor. I think that I get the wrong info in my endpoint and then when I call it in postman it throw me this error :
public function process(mixed $data, ApiOperation $operation, array $uriVariables = [], array $context = [])
{
assert($data instanceof Shop);
return $response;
}
Error :
{
"#context": "/bo/contexts/Error",
"#type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "Could not resolve argument $data of \"api_platform.action.placeholder::__invoke()\", maybe you forgot to register the controller as a service or missed tagging it with the \"controller.service_arguments\"?",
...
}
How can I get my shop data without this error ?

laravel phpunit withexceptionhandling

I'm in the process of writing a web app using Laravel 5.5 and Vue.js. PHPUnit version is 6.3.1.
I'm testing for validation errors when a user registers using Form Requests.
Route:
// web.php
Route::post('/register', 'Auth\RegisterController#store')->name('register.store');
This is my passing test:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->withExceptionHandling();
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
However, it fails when I remove exception handling:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
I do not understand why this test fails without exception handling as it's stated in the Laravel documentation that
If the request was an AJAX request, a HTTP response with a 422 status
code will be returned
I already tried to declare this particular route in the api middleware group, but that didn't change anything.
Can someone with more experience than I do explain to me why that is? Thanks in advance.
EDIT: This is the content of my Handler.php class file. I don't think anything was edited.
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
public function report(Exception $exception)
{
parent::report($exception);
}
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}

Laravel Testing login Credential using phpunit + multiprocess

I'm pretty new on unit testing and I want to try to test my login page
my Goal for this unit are :
-> if it match in database -> redirect to route '/'
-> if not -> redirect to route '/login'
<?php
namespace Tests\Feature;
use App\Domain\Core\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class userTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic test example.
*
* #return void
*/
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)->assertRedirect('/');
}
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'usera'
];
$this->post('login',$credential)->assertRedirect('/login');
}
}
when I test on TestLoginTrue , its successfully return to '/' But when i try the TestLoginFalse ... it return same like TestLoginTrue, it should be stayed on '/login' route,
Any Idea?
Plus I want to try to check if when I already login I couldn't access the login page so my initial idea is :
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)
->assertRedirect('/')
->get('/login')
->assertRedirect('/');
}
but... it returns
1) Tests\Feature\userTest::testLoginTrue BadMethodCallException:
Method [get] does not exist on Redirect.
So how to do it correctly?
Thanks in advance
I am also a bit stuck with Laravel 5.4 testing follow redirects case.
As a workaround, you may check $response->assertSessionHasErrors(). This way it should work:
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'incorrectpass'
];
$response = $this->post('login',$credential);
$response->assertSessionHasErrors();
}
Also, in testLoginTrue() you may check, that session missing errors:
$response = $this->post('login',$credential);
$response->assertSessionMissing('errors');
Hope this helps!

Batch requests on Symfony

I am trying to reproduce the behaviour of the facebook batch requests function on their graph api.
So I think that the easiest solution is to make several requests on a controller to my application like:
public function batchAction (Request $request)
{
$requests = $request->all();
$responses = [];
foreach ($requests as $req) {
$response = $this->get('some_http_client')
->request($req['method'],$req['relative_url'],$req['options']);
$responses[] = [
'method' => $req['method'],
'url' => $req['url'],
'code' => $response->getCode(),
'headers' => $response->getHeaders(),
'body' => $response->getContent()
]
}
return new JsonResponse($responses)
}
So with this solution, I think that my functional tests would be green.
However, I fill like initializing the service container X times might make the application much slower. Because for each request, every bundle is built, the service container is rebuilt each time etc...
Do you see any other solution for my problem?
In other words, do I need to make complete new HTTP requests to my server to get responses from other controllers in my application?
Thank you in advance for your advices!
Internally Symfony handle a Request with the http_kernel component. So you can simulate a Request for every batch action you want to execute and then pass it to the http_kernel component and then elaborate the result.
Consider this Example controller:
/**
* #Route("/batchAction", name="batchAction")
*/
public function batchAction()
{
// Simulate a batch request of existing route
$requests = [
[
'method' => 'GET',
'relative_url' => '/b',
'options' => 'a=b&cd',
],
[
'method' => 'GET',
'relative_url' => '/c',
'options' => 'a=b&cd',
],
];
$kernel = $this->get('http_kernel');
$responses = [];
foreach($requests as $aRequest){
// Construct a query params. Is only an example i don't know your input
$options=[];
parse_str($aRequest['options'], $options);
// Construct a new request object for each batch request
$req = Request::create(
$aRequest['relative_url'],
$aRequest['method'],
$options
);
// process the request
// TODO handle exception
$response = $kernel->handle($req);
$responses[] = [
'method' => $aRequest['method'],
'url' => $aRequest['relative_url'],
'code' => $response->getStatusCode(),
'headers' => $response->headers,
'body' => $response->getContent()
];
}
return new JsonResponse($responses);
}
With the following controller method:
/**
* #Route("/a", name="route_a_")
*/
public function aAction(Request $request)
{
return new Response('A');
}
/**
* #Route("/b", name="route_b_")
*/
public function bAction(Request $request)
{
return new Response('B');
}
/**
* #Route("/c", name="route_c_")
*/
public function cAction(Request $request)
{
return new Response('C');
}
The output of the request will be:
[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]
PS: I hope that I have correctly understand what you need.
There are ways to optimise test-speed, both with PHPunit configuration (for example, xdebug config, or running the tests with the phpdbg SAPI instead of including the Xdebug module into the usual PHP instance).
Because the code will always be running the AppKernel class, you can also put some optimisations in there for specific environments - including initiali[zs]ing the container less often during a test.
I'm using one such example by Kris Wallsmith. Here is his sample code.
class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox
protected function initializeContainer()
{
static $first = true;
if ('test' !== $this->getEnvironment()) {
parent::initializeContainer();
return;
}
$debug = $this->debug;
if (!$first) {
// disable debug mode on all but the first initialization
$this->debug = false;
}
// will not work with --process-isolation
$first = false;
try {
parent::initializeContainer();
} catch (\Exception $e) {
$this->debug = $debug;
throw $e;
}
$this->debug = $debug;
}

ZF2 - ServiceManager & getServiceConfig problems - unable to fetch or create instance

I am coming across some problems when trying to use ZF2's authentication services. I have to following Module.php getServiceConfig function:
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'Auth\Model\CustomerTable' => function($sm) {
$tableGateway = $sm->get('CustomerTableGateway');
$table = new CustomerTable($tableGateway);
return $table;
},
'CustomerTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Customer()); // prototype pattern implemented.
return new TableGateway('customer', $dbAdapter, null, $resultSetPrototype);
},
'Auth\Model\AuthStorage' => function($sm){
return new \Auth\Model\AuthStorage('jamietech');
},
'AuthService' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter,
'customer','username','password');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$authService->setStorage($sm->get('Auth\Model\AuthStorage'));
return $authService;
},
),
);
}
The AuthStorage factory simply creates a new AuthStorage for us to keep track of the rememberMe function I have, and the AuthService factory creates a new Authentication Service for us. I can't see anything that I have done wrong but when running the following code in the AuthController.php:
<?php
public function loginAction()
{
//if already login, redirect to success page
if ($this->getAuthService()->hasIdentity()){
return $this->redirect()->toRoute('success');
}
$form = new LoginForm();
return array(
'form' => $form,
'messages' => $this->flashmessenger()->getMessages()
);
}
public function logoutAction()
{
$this->getSessionStorage()->forgetMe();
$this->getAuthService()->clearIdentity();
$this->flashmessenger()->addMessage("You have logged out successfully.");
return $this->redirect()->toRoute('auth', array('action'=>'login'));
}
PHPUnit encounters the following errors when running the PHPUnit command:
1: "testLoginActionCanBeAccessed - Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance of Zend\Db\Adapter\Adapter
1: "testLogoutActionCanBeAccessed - session_regenerate_id(): cannot regenerate session id - headers already sent.
And this error for both login and logout when the -process-isolation command is run:
"Serialization of closure is not allowed in: C;\Users\-----\AppData\Local\Temp\-----
If somebody could help that would be great. I am a ZF noob so try not to be too harsh.
EDIT: BTW THe global.php file includes the service_manager adapter factory illustrated in the ZF2 tutorial application.
Thank you!
Jamie Mclaughlan
did you check these:
autoload_classmap.php (for your module)
in your module.config.php
like this
service_manager' => array(
'aliases' => array(
'mymodule-ZendDbAdapter' => 'Zend\Db\Adapter\Adapter',
),
);
I hope it helps you to find the answer

Resources