├── VERSION ├── Tests ├── Fixtures │ ├── config │ │ ├── config.yml │ │ └── config_custom.yml │ └── Exception │ │ └── TypeNotFoundHttpException.php └── Units │ ├── Exception │ └── Exception.php │ ├── Manager │ └── ExceptionManager.php │ ├── DependencyInjection │ └── M6WebApiExceptionExtension.php │ └── EventListener │ └── ExceptionListener.php ├── .gitignore ├── Resources └── doc │ ├── log.md │ ├── about.md │ ├── installation.md │ ├── variables.md │ ├── match.md │ ├── usage.md │ ├── unit_tests.md │ ├── stack_trace.md │ ├── errors.md │ ├── configuration.md │ └── form.md ├── Exception ├── BadRequestException.php ├── BadRequestHttpException.php ├── Interfaces │ ├── FlattenErrorExceptionInterface.php │ ├── HttpExceptionInterface.php │ └── ExceptionInterface.php ├── HttpException.php ├── Exception.php └── ValidationFormException.php ├── .coke ├── .travis.yml ├── .atoum.php ├── .scrutinizer.yml ├── M6WebApiExceptionBundle.php ├── LICENSE ├── CONTRIBUTING.md ├── composer.json ├── DependencyInjection ├── Configuration.php └── M6WebApiExceptionExtension.php ├── Manager └── ExceptionManager.php ├── README.md └── EventListener └── ExceptionListener.php /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 -------------------------------------------------------------------------------- /Tests/Fixtures/config/config.yml: -------------------------------------------------------------------------------- 1 | m6web_api_exception: ~ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | vendor/ 3 | composer.lock 4 | clover.xml 5 | -------------------------------------------------------------------------------- /Resources/doc/log.md: -------------------------------------------------------------------------------- 1 | # Logs 2 | 3 | To log use [M6WebLogBridgeBundle](https://github.com/M6Web/LogBridgeBundle) 4 | 5 | --- 6 | 7 | [Prev : Match](match.md) 8 | 9 | [Next : Configuration](configuration.md) -------------------------------------------------------------------------------- /Exception/BadRequestException.php: -------------------------------------------------------------------------------- 1 | addTestsFromDirectory(__DIR__.'/Tests'); 4 | 5 | if (getenv('TRAVIS') !== false) { 6 | $script->addDefaultReport(); 7 | 8 | $cloverWriter = new atoum\writers\file(__DIR__.'/clover.xml'); 9 | $cloverReport = new atoum\reports\asynchronous\clover(); 10 | $cloverReport->addWriter($cloverWriter); 11 | 12 | $runner->addReport($cloverReport); 13 | } 14 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: ['vendor/*', 'Tests/*'] 3 | 4 | before_commands: 5 | - "composer self-update" 6 | - "composer install --prefer-source --dev" 7 | 8 | tools: 9 | external_code_coverage: 10 | timeout: 600 11 | php_mess_detector: true 12 | php_code_sniffer: true 13 | sensiolabs_security_checker: true 14 | php_pdepend: true 15 | php_loc: true 16 | php_cpd: true 17 | -------------------------------------------------------------------------------- /M6WebApiExceptionBundle.php: -------------------------------------------------------------------------------- 1 | Type_1 = $Type_1; 23 | } 24 | } -------------------------------------------------------------------------------- /Tests/Fixtures/config/config_custom.yml: -------------------------------------------------------------------------------- 1 | m6web_api_exception: 2 | match_all: false 3 | stack_trace: true 4 | default: 5 | status: 400 6 | code: 1000 7 | exceptions: 8 | M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException: 9 | code: 1001 10 | message: "form validation failed" 11 | headers: 12 | Exception: "form validation failed" 13 | M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException: 14 | status: 404 15 | code: 1002 16 | message: "type {type} not found" 17 | -------------------------------------------------------------------------------- /Resources/doc/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Require the bundle in your composer.json file : 4 | 5 | ```json 6 | { 7 | "require": { 8 | "m6web/api-exception-bundle": "~1.0.0", 9 | } 10 | } 11 | ``` 12 | 13 | Register the bundle in your kernel : 14 | 15 | ```php 16 | // app/AppKernel.php 17 | 18 | public function registerBundles() 19 | { 20 | $bundles = array( 21 | new M6Web\Bundle\ApiExceptionBundle\M6WebApiExceptionBundle(), 22 | ); 23 | } 24 | ``` 25 | 26 | Then install the bundle : 27 | 28 | ```shell 29 | $ composer update m6web/api-exception-bundle 30 | ``` 31 | 32 | --- 33 | 34 | [Next : Usage](usage.md) -------------------------------------------------------------------------------- /Exception/Interfaces/HttpExceptionInterface.php: -------------------------------------------------------------------------------- 1 | =5.6", 16 | "symfony/http-kernel" : "^2.7||^3.0||^4.0", 17 | "symfony/dependency-injection" : "^2.7||^3.0||^4.0", 18 | "symfony/monolog-bridge" : "^2.7||^3.0||^4.0", 19 | "symfony/form" : "^2.7||^3.0||^4.0", 20 | "symfony/yaml" : "^2.7||^3.0||^4.0", 21 | "symfony/config" : "^2.7||^3.0||^4.0" 22 | }, 23 | "require-dev": { 24 | "atoum/atoum" : "^2.8||^3.0||^4.0", 25 | "m6web/coke" : "^2.1", 26 | "m6web/symfony2-coding-standard" : "^3.1" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "M6Web\\Bundle\\ApiExceptionBundle\\": "" 31 | } 32 | }, 33 | "config": { 34 | "bin-dir": "bin/" 35 | }, 36 | "minimum-stability" : "dev", 37 | "prefer-stable": true 38 | } 39 | -------------------------------------------------------------------------------- /Tests/Units/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | given( 14 | $exception = new TestedClass(500, 'exception {exceptionName} to test') 15 | ) 16 | ->then 17 | ->exception(function() use ($exception){ 18 | $exception->getMessageWithVariables(); 19 | }) 20 | ->isInstanceOf('\Exception') 21 | ->hasCode(500) 22 | ->hasMessage('Variable "exceptionName" for exception "M6Web\Bundle\ApiExceptionBundle\Exception\Exception" not found') 23 | ; 24 | } 25 | 26 | public function testGetMessageWithVariablesNotString() 27 | { 28 | $this 29 | ->given( 30 | $exception = new TestedClass(500, 'exception {code} to test') 31 | ) 32 | ->then 33 | ->exception(function() use ($exception){ 34 | $exception->getMessageWithVariables(); 35 | }) 36 | ->isInstanceOf('\Exception') 37 | ->hasCode(500) 38 | ->hasMessage('Variable "code" for exception "M6Web\Bundle\ApiExceptionBundle\Exception\Exception" must be a string, integer found') 39 | ; 40 | } 41 | } -------------------------------------------------------------------------------- /Resources/doc/variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | Add variables in message for your exception 4 | 5 | ```php 6 | id = $id; 28 | } 29 | } 30 | ``` 31 | 32 | Initialize variables when you use your exception 33 | 34 | ```php 35 | statusCode = $statusCode; 37 | $this->headers = $headers; 38 | parent::__construct($code, $message); 39 | } 40 | 41 | /** 42 | * Set status code 43 | * 44 | * @param integer $statusCode 45 | * 46 | * @return self 47 | */ 48 | public function setStatusCode($statusCode) 49 | { 50 | $this->statusCode = $statusCode; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Get status code 57 | * 58 | * @return string 59 | */ 60 | public function getStatusCode() 61 | { 62 | return $this->statusCode; 63 | } 64 | 65 | /** 66 | * Set headers 67 | * 68 | * @param array $headers 69 | * 70 | * @return self 71 | */ 72 | public function setHeaders(array $headers) 73 | { 74 | $this->headers = $headers; 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * Get headers 81 | * 82 | * @return array 83 | */ 84 | public function getHeaders() 85 | { 86 | return $this->headers; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Resources/doc/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Create new exception in your API 4 | 5 | Create a new class exception extends an exception from this bundle 6 | 7 | ```php 8 | given( 71 | $idUserUnknown = 0; 72 | $userManager = new TestedClass(...) 73 | ) 74 | ->then 75 | ->exception(function() use ($userManager, $idUserUnknown) { 76 | $userManager->getUser($idUserUnknown); 77 | }) 78 | ->isInstanceOf('Acme\DemoBundle\Exception\UserNotFoundException') 79 | ->hasCode(?) // No sens, because configuration not initialized 80 | ->hasMessage(?) // No sens, because configuration not initialized 81 | ; 82 | } 83 | } 84 | ``` 85 | 86 | --- 87 | 88 | [Prev : Configuration](configuration.md) 89 | 90 | [Next : About](about.md) 91 | -------------------------------------------------------------------------------- /Resources/doc/stack_trace.md: -------------------------------------------------------------------------------- 1 | # Stack Trace 2 | 3 | Additional stack trace exception to json response 4 | 5 | ```yaml 6 | # ./app/config/config.yml 7 | 8 | m6web_api_exception: 9 | stack_trace: true 10 | ``` 11 | 12 | result request with bad user and stack trace enabled 13 | 14 | ```json 15 | { 16 | "error": { 17 | "status": 404, 18 | "code": 5286, 19 | "message": "user not found", 20 | "stack_trace": [ 21 | { 22 | "file": "/src/acme-project/src/Acme/DemoBundle/Controller/DemoController.php", 23 | "line": 24, 24 | "function": "getUser", 25 | "class": "Acme\DemoBundle\Manager\UserManager", 26 | "type": "->", 27 | "args": [ 28 | 4 29 | ] 30 | }, 31 | { 32 | "function": "indexAction", 33 | "class": "Acme\DemoBundle\Controller\DemoController", 34 | "type": "->", 35 | "args": [ 36 | { 37 | "attributes": {}, 38 | "request": {}, 39 | "query": {}, 40 | "server": {}, 41 | "files": {}, 42 | "cookies": {}, 43 | "headers": {} 44 | } 45 | ] 46 | }, 47 | ... 48 | { 49 | "file": "/src/acme-project/web/app_dev.php", 50 | "line": 12, 51 | "function": "handle", 52 | "class": "Symfony\Component\HttpKernel\Kernel", 53 | "type": "->", 54 | "args": [ 55 | { 56 | "attributes": {}, 57 | "request": {}, 58 | "query": {}, 59 | "server": {}, 60 | "files": {}, 61 | "cookies": {}, 62 | "headers": {} 63 | } 64 | ] 65 | } 66 | ] 67 | } 68 | } 69 | ``` 70 | 71 | --- 72 | 73 | [Prev : Variables](variables.md) 74 | 75 | [Next : Errors](errors.md) 76 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('m6web_api_exception'); 20 | 21 | $rootNode 22 | ->children() 23 | ->booleanNode('stack_trace')->defaultValue(false)->end() 24 | ->booleanNode('match_all')->defaultValue(true)->end() 25 | ->arrayNode('default') 26 | ->addDefaultsIfNotSet() 27 | ->children() 28 | ->integerNode('code')->defaultValue(0)->end() 29 | ->integerNode('status')->defaultValue(500)->end() 30 | ->scalarNode('message')->defaultValue('Internal server error')->end() 31 | ->arrayNode('headers') 32 | ->useAttributeAsKey('name') 33 | ->prototype('scalar')->end() 34 | ->defaultValue([]) 35 | ->end() 36 | ->end() 37 | ->end() 38 | ->arrayNode('exceptions') 39 | ->useAttributeAsKey('name') 40 | ->prototype('array') 41 | ->children() 42 | ->integerNode('code')->end() 43 | ->integerNode('status')->end() 44 | ->scalarNode('message')->end() 45 | ->arrayNode('headers') 46 | ->useAttributeAsKey('name') 47 | ->prototype('scalar')->end() 48 | ->end() 49 | ->end() 50 | ->end() 51 | ->end() 52 | ->end(); 53 | 54 | return $treeBuilder; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Exception/Exception.php: -------------------------------------------------------------------------------- 1 | code = $code; 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * Set message 43 | * 44 | * @param string $message 45 | * 46 | * @return self 47 | */ 48 | public function setMessage($message) 49 | { 50 | $this->message = $message; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Get message with variables 57 | * 58 | * @throws \Exception 59 | * 60 | * @return string 61 | */ 62 | public function getMessageWithVariables() 63 | { 64 | $message = $this->message; 65 | 66 | preg_match(self::VARIABLE_REGEX, $message, $variables); 67 | 68 | foreach ($variables as $variable) { 69 | $variableName = substr($variable, 1, -1); 70 | 71 | if (!isset($this->$variableName)) { 72 | throw new \Exception(sprintf( 73 | 'Variable "%s" for exception "%s" not found', 74 | $variableName, 75 | get_class($this) 76 | ), 500); 77 | } 78 | 79 | if (!is_string($this->$variableName)) { 80 | throw new \Exception(sprintf( 81 | 'Variable "%s" for exception "%s" must be a string, %s found', 82 | $variableName, 83 | get_class($this), 84 | gettype($this->$variableName) 85 | ), 500); 86 | } 87 | 88 | $message = str_replace($variable, $this->$variableName, $message); 89 | } 90 | 91 | return $message; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Exception/ValidationFormException.php: -------------------------------------------------------------------------------- 1 | form = $form; 35 | parent::__construct($statusCode, $code, $message, $headers); 36 | } 37 | 38 | /** 39 | * Get form 40 | * 41 | * @return FormInterface 42 | */ 43 | public function getForm() 44 | { 45 | return $this->form; 46 | } 47 | 48 | /** 49 | * Flatten form errors 50 | * 51 | * @param FormInterface $form 52 | * @param boolean $subForm 53 | * 54 | * @return array 55 | */ 56 | public function getFlattenErrors(FormInterface $form = null, $subForm = false) 57 | { 58 | $form = $form ?: $this->form; 59 | $flatten = []; 60 | 61 | foreach ($form->getErrors() as $error) { 62 | if ($subForm) { 63 | $flatten[] = $error->getMessage(); 64 | } else { 65 | $path = $error->getCause()->getPropertyPath(); 66 | 67 | if (!array_key_exists($path, $flatten)) { 68 | $flatten[$path] = [$error->getMessage()]; 69 | continue; 70 | } 71 | 72 | $flatten[$path][] = $error->getMessage(); 73 | } 74 | } 75 | 76 | $subForm = true; 77 | foreach ($form->all() as $key => $child) { 78 | $childErrors = $this->getFlattenErrors($child, $subForm); 79 | 80 | if (!empty($childErrors)) { 81 | $flatten[$key] = $childErrors; 82 | } 83 | } 84 | 85 | return $flatten; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Manager/ExceptionManager.php: -------------------------------------------------------------------------------- 1 | defaultConfig = $defaultConfig; 32 | $this->exceptions = $exceptions; 33 | } 34 | 35 | /** 36 | * Configure Exception 37 | * 38 | * @param ExceptionInterface $exception 39 | * 40 | * @return ExceptionInterface 41 | */ 42 | public function configure(ExceptionInterface $exception) 43 | { 44 | $exceptionName = get_class($exception); 45 | 46 | $configException = $this->getConfigException($exceptionName); 47 | 48 | $exception->setCode($configException['code']); 49 | $exception->setMessage($configException['message']); 50 | 51 | if ($exception instanceof HttpExceptionInterface) { 52 | $exception->setStatusCode($configException['status']); 53 | $exception->setHeaders($configException['headers']); 54 | } 55 | 56 | return $exception; 57 | } 58 | 59 | /** 60 | * Get config to exception 61 | * 62 | * @param string $exceptionName 63 | * 64 | * @return array 65 | */ 66 | protected function getConfigException($exceptionName) 67 | { 68 | $exceptionParentName = get_parent_class($exceptionName); 69 | 70 | if (in_array( 71 | 'M6Web\Bundle\ApiExceptionBundle\Exception\Interfaces\ExceptionInterface', 72 | class_implements($exceptionParentName) 73 | )) { 74 | $parentConfig = $this->getConfigException($exceptionParentName); 75 | } else { 76 | $parentConfig = $this->defaultConfig; 77 | } 78 | 79 | if (isset($this->exceptions[$exceptionName])) { 80 | return array_merge($parentConfig, $this->exceptions[$exceptionName]); 81 | } 82 | 83 | return $parentConfig; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Resources/doc/errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | Json Responses displays an additional `errors` array if your exception implement `M6Web\Bundle\ApiExceptionBundle\Exception\Interfaces\FlattenErrorExceptionInterface` 4 | 5 | Just create an exception with `getFlattenErrors` method to transform errors to array 6 | 7 | ```php 8 | var1 = $var1; 35 | $this->var2 = $var2; 36 | } 37 | 38 | /** 39 | * Get errors 40 | * 41 | * @return array 42 | */ 43 | public function getFlattenErrors() 44 | { 45 | $errors = []; 46 | 47 | /* your algo with $var1 and $var2 to compose array */ 48 | 49 | return $errors 50 | } 51 | } 52 | ``` 53 | *Example: `M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException`* 54 | 55 | Use your exception with errors 56 | 57 | ```php 58 | processConfiguration($configuration, $configs); 22 | 23 | $this->loadExceptionManager($container, $config); 24 | $this->loadExceptionListener($container, $config); 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getAlias() 31 | { 32 | return 'm6web_api_exception'; 33 | } 34 | 35 | /** 36 | * load service exception manager 37 | * 38 | * @param ContainerBuilder $container 39 | * @param array $config 40 | */ 41 | protected function loadExceptionManager(ContainerBuilder $container, array $config) 42 | { 43 | $definition = new Definition( 44 | 'M6Web\Bundle\ApiExceptionBundle\Manager\ExceptionManager', 45 | [ 46 | $config['default'], 47 | $config['exceptions'], 48 | ] 49 | ); 50 | $definition->setPublic(true); 51 | 52 | $container->setDefinition($this->getAlias().'.manager.exception', $definition); 53 | } 54 | 55 | /** 56 | * load service exception listener 57 | * 58 | * @param ContainerBuilder $container 59 | * @param array $config 60 | */ 61 | protected function loadExceptionListener(ContainerBuilder $container, array $config) 62 | { 63 | $definition = new Definition( 64 | 'M6Web\Bundle\ApiExceptionBundle\EventListener\ExceptionListener', 65 | [ 66 | new Reference('kernel'), 67 | new Reference($this->getAlias().'.manager.exception'), 68 | $config['match_all'], 69 | $config['default'], 70 | $config['stack_trace'], 71 | ] 72 | ); 73 | 74 | $definition->setPublic(true); 75 | 76 | $definition->addTag( 77 | 'kernel.event_listener', 78 | [ 79 | 'event' => 'kernel.exception', 80 | 'method' => 'onKernelException', 81 | 'priority' => '-100' // as the setResponse stop the exception propagation, this listener has to pass in last position 82 | ] 83 | ); 84 | 85 | $container->setDefinition($this->getAlias().'.listener.exception', $definition); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Resources/doc/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ### Description 4 | 5 | Node to configure bundle `m6web_api_exception` 6 | 7 | - `match_all` *(___boolean___, default `true`)* : Match all exception, M6WebApiException and others 8 | - `stack_trace` *(___boolean___, default `false`)* : Add stack trace exception to JsonResponse and log 9 | - `default` *(___array___)* : Default configuration to exceptions 10 | - `status` *(___integer___, default `500`)* : Http status 11 | - `code` *(___integer___, default `0`)* : Code error specific to project 12 | - `message` *(___string___, default `internal server error`)* : Error message 13 | - `headers` *(___array___, default `[]`)* : Headers to JsonResponse 14 | - `exceptions` *(___array___)* : Extends default configuration by exceptions 15 | - `Acme\DemoBundle\Exception\BadRequestException` *(___namespace exception___)* 16 | - `status` *(___integer___)* : Http status 17 | - `code` *(___integer___)* : Code error specific to project 18 | - `message` *(___string___)* : Error message 19 | - `headers` *(___array___)* : Headers to JsonResponse 20 | - `Acme\DemoBundle\Exception\MyOtherException` 21 | - `...` 22 | - `...` 23 | 24 | *NB : `code` is displayed only if greater than `0`* 25 | *NB 2 : `status` and `headers` are enabled only for exceptions that implements `M6Web\Bundle\ApiExceptionBundle\Exception\Interfaces\HttpExceptionInterface`* 26 | 27 | ### Configuration example 28 | 29 | ```yaml 30 | # ./app/config/config.yml 31 | 32 | m6web_api_exception: 33 | stack_trace: true 34 | default: 35 | status: 400 36 | code: 1000 37 | exceptions: 38 | Acme\DemoBundle\Exception\ValidationFormException: 39 | code: 1001 40 | message: "form validation failed" 41 | headers: 42 | Exception: "form validation failed" 43 | Acme\DemoBundle\Exception\TypeNotFoundException: 44 | status: 404 45 | code: 1002 46 | message: "type not found" 47 | ``` 48 | 49 | ### Configuration extends system 50 | 51 | Parameters exception are determined by this configuration, configuration of these parents, and finally the default configuration. 52 | 53 | example : 54 | 55 | ```yaml 56 | # ./app/config/config.yml 57 | 58 | m6web_api_exception: 59 | stack_trace: true 60 | default: 61 | status: 500 62 | code: 1000 63 | message: "internal server error" 64 | headers: 65 | Exception: "error" 66 | exceptions: 67 | Acme\DemoBundle\Exception\NotFoundException: 68 | status: 404 69 | code: 1001 70 | message: "resource not found" 71 | Acme\DemoBundle\Exception\TypeNotFoundException: # extends Acme\DemoBundle\Exception\NotFoundException 72 | code: 1002 73 | message: "type {type} not found" 74 | ``` 75 | 76 | final configuration to `Acme\DemoBundle\Exception\TypeNotFoundException` : 77 | 78 | ```yaml 79 | Acme\DemoBundle\Exception\TypeNotFoundException: 80 | status: 404 81 | code: 1002 82 | message: "type {type} not found" 83 | headers: 84 | Exception: "error" 85 | ``` 86 | 87 | --- 88 | 89 | [Prev : Log](log.md) 90 | 91 | [Next : Unit Tests](unit_tests.md) 92 | -------------------------------------------------------------------------------- /Resources/doc/form.md: -------------------------------------------------------------------------------- 1 | ### Form errors 2 | 3 | Create a form 4 | 5 | ```php 6 | add('username', 'string', [ 29 | 'constraints' => [ 30 | new Assert\NotBlank(), 31 | new Assert\Length([ 32 | 'min' => 10, 33 | ]), 34 | ], 35 | ]) 36 | ->add('email', 'string', [ 37 | 'constraints' => [ 38 | new Assert\NotBlank(), 39 | new Assert\Email(), 40 | ], 41 | ]) 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function setDefaultOptions(OptionsResolver $resolver) 48 | { 49 | $resolver->setDefaults([ 50 | 'data_class' => 'Acme\DemoBundle\Entity\User' 51 | ]); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getName() 58 | { 59 | return 'user_type'; 60 | } 61 | } 62 | ``` 63 | 64 | Create a new class exception extends `M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException` 65 | 66 | ```php 67 | createForm(new UserType()); 121 | $formUser->handleRequest($request); 122 | 123 | if (!$formUser->isValid()) { 124 | throw new UserTypeValidationException($formUser); 125 | } 126 | 127 | /*...*/ 128 | } 129 | 130 | /*...*/ 131 | } 132 | ``` 133 | 134 | Result with invalid post parameters 135 | 136 | ```json 137 | { 138 | "error": { 139 | "status": 400, 140 | "code": 4723, 141 | "message": "validation user form failed", 142 | "errors": { 143 | "username": [ 144 | "This value should not be blank.", 145 | "This value must minimum contain 10 characters" 146 | ], 147 | "email": [ 148 | "This value should not be blank.", 149 | "Email invalid" 150 | ], 151 | }, 152 | "stack_trace": [ 153 | ... 154 | ] 155 | } 156 | } 157 | ``` 158 | 159 | --- 160 | 161 | [Prev : Errors](errors.md) 162 | 163 | [Next : Match](match.md) 164 | -------------------------------------------------------------------------------- /Tests/Units/Manager/ExceptionManager.php: -------------------------------------------------------------------------------- 1 | mockGenerator->orphanize('__construct'); 19 | $form = new \mock\Symfony\Component\Form\Form; 20 | 21 | return $form; 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | protected function getDefaultConfig() 28 | { 29 | return [ 30 | 'status' => 400, 31 | 'code' => 1000, 32 | 'message' => 'bad request', 33 | 'headers' => [] 34 | ]; 35 | } 36 | 37 | /** 38 | * @return array 39 | */ 40 | protected function getExceptionsConfig() 41 | { 42 | return [ 43 | 'M6Web\\Bundle\\ApiExceptionBundle\\Exception\\ValidationFormException' => [ 44 | 'code' => 1001, 45 | 'message' => 'form validation failed', 46 | 'headers' => [ 47 | 'Exception' => 'form validation failed' 48 | ] 49 | ], 50 | 'M6Web\\Bundle\\ApiExceptionBundle\\Tests\\Fixtures\\Exception\\TypeNotFoundHttpException' => [ 51 | 'status' => 404, 52 | 'code' => 1003, 53 | 'message' => 'notify type not found', 54 | ], 55 | 'M6Web\\Bundle\\ApiExceptionBundle\\Exception\\BadRequestException' => [ 56 | ] 57 | ]; 58 | } 59 | 60 | public function testConfigure() 61 | { 62 | $this 63 | ->given( 64 | $defaultConfig = $this->getDefaultConfig(), 65 | $exceptionsConfig = $this->getExceptionsConfig(), 66 | $badRequestException = new BadRequestException(), 67 | $validationFormException = new ValidationFormException($this->getFormMock()), 68 | $typeNotFoundException = new TypeNotFoundHttpException(), 69 | $exceptionManager = new TestedClass($defaultConfig, $exceptionsConfig) 70 | ) 71 | ->then 72 | 73 | ->object($exception = $exceptionManager->configure($badRequestException)) 74 | ->isInstanceOf('M6Web\Bundle\ApiExceptionBundle\Exception\BadRequestException') 75 | ->integer($exception->getCode()) 76 | ->isEqualTo($defaultConfig['code']) 77 | ->string($exception->getMessage()) 78 | ->isEqualTo($defaultConfig['message']) 79 | 80 | ->object($exception = $exceptionManager->configure($validationFormException)) 81 | ->isInstanceOf('M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException') 82 | ->integer($exception->getCode()) 83 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['code']) 84 | ->string($exception->getMessage()) 85 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['message']) 86 | ->integer($exception->getStatusCode()) 87 | ->isEqualTo($defaultConfig['status']) 88 | ->array($headers = $exception->getHeaders()) 89 | ->hasSize(1) 90 | ->string($headers['Exception']) 91 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['headers']['Exception']) 92 | 93 | ->object($exception = $exceptionManager->configure($typeNotFoundException)) 94 | ->isInstanceOf('M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundHttpException') 95 | ->integer($exception->getCode()) 96 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundHttpException']['code']) 97 | ->string($exception->getMessage()) 98 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundHttpException']['message']) 99 | ->integer($exception->getStatusCode()) 100 | ->isEqualTo($exceptionsConfig['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundHttpException']['status']) 101 | ->array($exception->getHeaders()) 102 | ->hasSize(0) 103 | ->isEqualTo($defaultConfig['headers']) 104 | ; 105 | } 106 | } -------------------------------------------------------------------------------- /EventListener/ExceptionListener.php: -------------------------------------------------------------------------------- 1 | kernel = $kernel; 61 | $this->exceptionManager = $exceptionManager; 62 | $this->matchAll = $matchAll; 63 | $this->default = $default; 64 | $this->stackTrace = $stackTrace; 65 | } 66 | 67 | /** 68 | * Format response exception 69 | * 70 | * @param GetResponseForExceptionEvent $event 71 | */ 72 | public function onKernelException(GetResponseForExceptionEvent $event) 73 | { 74 | $exception = $event->getException(); 75 | 76 | if (!($event->getRequest()) 77 | || ($this->matchAll === false && !$this->isApiException($exception)) 78 | ) { 79 | return; 80 | } 81 | 82 | $data = []; 83 | 84 | if ($this->isApiException($exception)) { 85 | $exception = $this->exceptionManager->configure($exception); 86 | } 87 | 88 | $statusCode = $this->getStatusCode($exception); 89 | 90 | $data['error']['status'] = $statusCode; 91 | 92 | if ($code = $exception->getCode()) { 93 | $data['error']['code'] = $code; 94 | } 95 | 96 | $data['error']['message'] = $this->getMessage($exception); 97 | 98 | if ($this->isFlattenErrorException($exception)) { 99 | $data['error']['errors'] = $exception->getFlattenErrors(); 100 | } 101 | 102 | if ($this->stackTrace) { 103 | $data['error']['stack_trace'] = $exception->getTrace(); 104 | 105 | // Clean stacktrace to avoid circular reference or invalid type 106 | array_walk_recursive( 107 | $data['error']['stack_trace'], 108 | function(&$item) { 109 | if (is_object($item)) { 110 | $item = get_class($item); 111 | } elseif (is_resource($item)) { 112 | $item = get_resource_type($item); 113 | } 114 | } 115 | ); 116 | } 117 | 118 | $response = new JsonResponse($data, $statusCode, $this->getHeaders($exception)); 119 | $event->setResponse($response); 120 | } 121 | 122 | /** 123 | * Get exception status code 124 | * 125 | * @param \Exception $exception 126 | * 127 | * @return integer 128 | */ 129 | private function getStatusCode(\Exception $exception) 130 | { 131 | $statusCode = $this->default['status']; 132 | 133 | if ($exception instanceof SymfonyHttpExceptionInterface 134 | || $exception instanceof HttpExceptionInterface 135 | ) { 136 | $statusCode = $exception->getStatusCode(); 137 | } 138 | 139 | return $statusCode; 140 | } 141 | 142 | /** 143 | * Get exception message 144 | * 145 | * @param \Exception $exception 146 | * 147 | * @return integer 148 | */ 149 | private function getMessage(\Exception $exception) 150 | { 151 | $message = $exception->getMessage(); 152 | 153 | if ($this->isApiException($exception)) { 154 | $message = $exception->getMessageWithVariables(); 155 | } 156 | 157 | return $message; 158 | } 159 | 160 | /** 161 | * Get exception headers 162 | * 163 | * @param \Exception $exception 164 | * 165 | * @return array 166 | */ 167 | private function getHeaders(\Exception $exception) 168 | { 169 | $headers = $this->default['headers']; 170 | 171 | if ($exception instanceof SymfonyHttpExceptionInterface 172 | || $exception instanceof HttpExceptionInterface 173 | ) { 174 | $headers = $exception->getHeaders(); 175 | } 176 | 177 | return $headers; 178 | } 179 | 180 | /** 181 | * Is api exception 182 | * 183 | * @param \Exception $exception 184 | * 185 | * @return boolean 186 | */ 187 | private function isApiException(\Exception $exception) 188 | { 189 | if ($exception instanceof ExceptionInterface) { 190 | return true; 191 | } 192 | 193 | return false; 194 | } 195 | 196 | /** 197 | * Is flatten error exception 198 | * 199 | * @param \Exception $exception 200 | * 201 | * @return boolean 202 | */ 203 | private function isFlattenErrorException(\Exception $exception) 204 | { 205 | if ($exception instanceof FlattenErrorExceptionInterface) { 206 | return true; 207 | } 208 | 209 | return false; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Tests/Units/DependencyInjection/M6WebApiExceptionExtension.php: -------------------------------------------------------------------------------- 1 | setDefinition('kernel', $definition); 24 | 25 | return $container; 26 | } 27 | 28 | protected function getContainerForConfiguration($fixtureName) 29 | { 30 | $parameterBag = new ParameterBag(array('kernel.debug' => true)); 31 | $container = new ContainerBuilder($parameterBag); 32 | 33 | $container = $this->registerServiceKernel($container); 34 | 35 | $extension = new TestedClass(); 36 | $container->registerExtension($extension); 37 | 38 | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../Fixtures/config')); 39 | $loader->load($fixtureName.'.yml'); 40 | 41 | return $container; 42 | } 43 | 44 | /** 45 | * @php < 7.1 46 | */ 47 | public function testDefaultConfiguration() 48 | { 49 | $container = $this->getContainerForConfiguration('config'); 50 | $container->compile(); 51 | 52 | $this 53 | ->boolean($container->has('m6web_api_exception.manager.exception')) 54 | ->isTrue() 55 | ->array($managerArguments = $container->getDefinition('m6web_api_exception.manager.exception')->getArguments()) 56 | ->hasSize(2) 57 | ->array($managerArguments[0]) 58 | ->hasSize(4) 59 | ->integer($managerArguments[0]['status']) 60 | ->isEqualTo(500) 61 | ->integer($managerArguments[0]['code']) 62 | ->isEqualTo(0) 63 | ->string($managerArguments[0]['message']) 64 | ->isEqualTo('Internal server error') 65 | ->array($managerArguments[0]['headers']) 66 | ->isEqualTo([]) 67 | ->array($managerArguments[1]) 68 | ->hasSize(0) 69 | ->array($listenerArguments = $container->getDefinition('m6web_api_exception.listener.exception')->getArguments()) 70 | ->hasSize(5) 71 | ->object($listenerArguments[0]) 72 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 73 | ->string((string) $listenerArguments[0]) 74 | ->isEqualTo('kernel') 75 | ->object($listenerArguments[1]) 76 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 77 | ->string((string) $listenerArguments[1]) 78 | ->isEqualTo('m6web_api_exception.manager.exception') 79 | ->boolean($listenerArguments[2]) 80 | ->isEqualTo(true) 81 | ->array($listenerArguments[3]) 82 | ->hasSize(4) 83 | ->integer($managerArguments[0]['status']) 84 | ->isEqualTo(500) 85 | ->integer($managerArguments[0]['code']) 86 | ->isEqualTo(0) 87 | ->string($managerArguments[0]['message']) 88 | ->isEqualTo('Internal server error') 89 | ->array($managerArguments[0]['headers']) 90 | ->isEqualTo([]) 91 | ->boolean($listenerArguments[4]) 92 | ->isEqualTo(false) 93 | ; 94 | } 95 | 96 | /** 97 | * @php < 7.1 98 | */ 99 | public function testCustomConfiguration() 100 | { 101 | $container = $this->getContainerForConfiguration('config_custom'); 102 | $container->compile(); 103 | 104 | $this 105 | ->boolean($container->has('m6web_api_exception.manager.exception')) 106 | ->isTrue() 107 | ->array($managerArguments = $container->getDefinition('m6web_api_exception.manager.exception')->getArguments()) 108 | ->hasSize(2) 109 | ->array($managerArguments[0]) 110 | ->hasSize(4) 111 | ->integer($managerArguments[0]['status']) 112 | ->isEqualTo(400) 113 | ->integer($managerArguments[0]['code']) 114 | ->isEqualTo(1000) 115 | ->string($managerArguments[0]['message']) 116 | ->isEqualTo('Internal server error') 117 | ->array($managerArguments[0]['headers']) 118 | ->isEqualTo([]) 119 | ->array($managerArguments[1]) 120 | ->hasSize(2) 121 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']) 122 | ->hasSize(3) 123 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['code']) 124 | ->isEqualTo(1001) 125 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['message']) 126 | ->isEqualTo('form validation failed') 127 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['headers']) 128 | ->hasSize(1) 129 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['headers']['Exception']) 130 | ->isEqualTo('form validation failed') 131 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']) 132 | ->hasSize(4) 133 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['status']) 134 | ->isEqualTo(404) 135 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['code']) 136 | ->isEqualTo(1002) 137 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['message']) 138 | ->isEqualTo('type {type} not found') 139 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['headers']) 140 | ->isEqualTo([]) 141 | ->array($listenerArguments = $container->getDefinition('m6web_api_exception.listener.exception')->getArguments()) 142 | ->hasSize(5) 143 | ->object($listenerArguments[0]) 144 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 145 | ->string((string) $listenerArguments[0]) 146 | ->isEqualTo('kernel') 147 | ->object($listenerArguments[1]) 148 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 149 | ->string((string) $listenerArguments[1]) 150 | ->isEqualTo('m6web_api_exception.manager.exception') 151 | ->boolean($listenerArguments[2]) 152 | ->isEqualTo(false) 153 | ->array($listenerArguments[3]) 154 | ->hasSize(4) 155 | ->integer($managerArguments[0]['status']) 156 | ->isEqualTo(400) 157 | ->integer($managerArguments[0]['code']) 158 | ->isEqualTo(1000) 159 | ->string($managerArguments[0]['message']) 160 | ->isEqualTo('Internal server error') 161 | ->array($managerArguments[0]['headers']) 162 | ->isEqualTo([]) 163 | ->boolean($listenerArguments[4]) 164 | ->isEqualTo(true) 165 | ; 166 | } 167 | 168 | /** 169 | * @php >= 7.1 170 | */ 171 | public function testDefaultConfigurationPhp71Min() 172 | { 173 | $container = $this->getContainerForConfiguration('config'); 174 | $container->compile(); 175 | 176 | $this 177 | ->boolean($container->has('m6web_api_exception.manager.exception')) 178 | ->isTrue() 179 | ->array($managerArguments = $container->getDefinition('m6web_api_exception.manager.exception')->getArguments()) 180 | ->hasSize(2) 181 | ->array($managerArguments[0]) 182 | ->hasSize(4) 183 | ->integer($managerArguments[0]['status']) 184 | ->isEqualTo(500) 185 | ->integer($managerArguments[0]['code']) 186 | ->isEqualTo(0) 187 | ->string($managerArguments[0]['message']) 188 | ->isEqualTo('Internal server error') 189 | ->array($managerArguments[0]['headers']) 190 | ->isEqualTo([]) 191 | ->array($managerArguments[1]) 192 | ->hasSize(0) 193 | ->array($listenerArguments = $container->getDefinition('m6web_api_exception.listener.exception')->getArguments()) 194 | ->hasSize(5) 195 | ->object($listenerArguments[0]) 196 | ->isInstanceOf('Symfony\Component\DependencyInjection\Definition') 197 | ->string($listenerArguments[0]->getClass()) 198 | ->isEqualTo('Symfony\Component\HttpKernel\Kernel') 199 | ->object($listenerArguments[1]) 200 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 201 | ->string((string) $listenerArguments[1]) 202 | ->isEqualTo('m6web_api_exception.manager.exception') 203 | ->boolean($listenerArguments[2]) 204 | ->isEqualTo(true) 205 | ->array($listenerArguments[3]) 206 | ->hasSize(4) 207 | ->integer($managerArguments[0]['status']) 208 | ->isEqualTo(500) 209 | ->integer($managerArguments[0]['code']) 210 | ->isEqualTo(0) 211 | ->string($managerArguments[0]['message']) 212 | ->isEqualTo('Internal server error') 213 | ->array($managerArguments[0]['headers']) 214 | ->isEqualTo([]) 215 | ->boolean($listenerArguments[4]) 216 | ->isEqualTo(false) 217 | ; 218 | } 219 | 220 | /** 221 | * @php >= 7.1 222 | */ 223 | public function testCustomConfigurationPhp71Min() 224 | { 225 | $container = $this->getContainerForConfiguration('config_custom'); 226 | $container->compile(); 227 | 228 | $this 229 | ->boolean($container->has('m6web_api_exception.manager.exception')) 230 | ->isTrue() 231 | ->array($managerArguments = $container->getDefinition('m6web_api_exception.manager.exception')->getArguments()) 232 | ->hasSize(2) 233 | ->array($managerArguments[0]) 234 | ->hasSize(4) 235 | ->integer($managerArguments[0]['status']) 236 | ->isEqualTo(400) 237 | ->integer($managerArguments[0]['code']) 238 | ->isEqualTo(1000) 239 | ->string($managerArguments[0]['message']) 240 | ->isEqualTo('Internal server error') 241 | ->array($managerArguments[0]['headers']) 242 | ->isEqualTo([]) 243 | ->array($managerArguments[1]) 244 | ->hasSize(2) 245 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']) 246 | ->hasSize(3) 247 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['code']) 248 | ->isEqualTo(1001) 249 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['message']) 250 | ->isEqualTo('form validation failed') 251 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['headers']) 252 | ->hasSize(1) 253 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException']['headers']['Exception']) 254 | ->isEqualTo('form validation failed') 255 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']) 256 | ->hasSize(4) 257 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['status']) 258 | ->isEqualTo(404) 259 | ->integer($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['code']) 260 | ->isEqualTo(1002) 261 | ->string($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['message']) 262 | ->isEqualTo('type {type} not found') 263 | ->array($managerArguments[1]['M6Web\Bundle\ApiExceptionBundle\Tests\Fixtures\Exception\TypeNotFoundException']['headers']) 264 | ->isEqualTo([]) 265 | ->array($listenerArguments = $container->getDefinition('m6web_api_exception.listener.exception')->getArguments()) 266 | ->hasSize(5) 267 | ->object($listenerArguments[0]) 268 | ->isInstanceOf('Symfony\Component\DependencyInjection\Definition') 269 | ->string($listenerArguments[0]->getClass()) 270 | ->isEqualTo('Symfony\Component\HttpKernel\Kernel') 271 | ->object($listenerArguments[1]) 272 | ->isInstanceOf('Symfony\Component\DependencyInjection\Reference') 273 | ->string((string) $listenerArguments[1]) 274 | ->isEqualTo('m6web_api_exception.manager.exception') 275 | ->boolean($listenerArguments[2]) 276 | ->isEqualTo(false) 277 | ->array($listenerArguments[3]) 278 | ->hasSize(4) 279 | ->integer($managerArguments[0]['status']) 280 | ->isEqualTo(400) 281 | ->integer($managerArguments[0]['code']) 282 | ->isEqualTo(1000) 283 | ->string($managerArguments[0]['message']) 284 | ->isEqualTo('Internal server error') 285 | ->array($managerArguments[0]['headers']) 286 | ->isEqualTo([]) 287 | ->boolean($listenerArguments[4]) 288 | ->isEqualTo(true) 289 | ; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Tests/Units/EventListener/ExceptionListener.php: -------------------------------------------------------------------------------- 1 | mockGenerator->orphanize('__construct'); 34 | $request = new \mock\Symfony\Component\HttpFoundation\Request(); 35 | 36 | $request->query = new ParameterBag($get); 37 | $request->request = new ParameterBag($post); 38 | $request->headers = new HeaderBag($headers); 39 | $request->attributes = new ParameterBag(['debug' => true]); 40 | 41 | $request->getMockController()->getMethod = strtoupper($method); 42 | $request->getMockController()->getSchemeAndHttpHost = 'http://test.tld'; 43 | $request->getMockController()->getBaseUrl = ''; 44 | $request->getMockController()->getPathInfo = $path; 45 | 46 | return $request; 47 | } 48 | 49 | /** 50 | * @param bool|true $isSuccessful 51 | * 52 | * @return \mock\Symfony\Component\HttpFoundation\Response 53 | */ 54 | protected function getResponseMock($isSuccessful = true) 55 | { 56 | $this->mockGenerator->orphanize('__construct'); 57 | 58 | $response = new \mock\Symfony\Component\HttpFoundation\Response(); 59 | $response->getMockController()->isSuccessful = $isSuccessful; 60 | 61 | return $response; 62 | } 63 | 64 | /** 65 | * @param $request 66 | * @param mixed $exception 67 | * 68 | * @return \mock\Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent 69 | */ 70 | protected function getGetResponseForExceptionEventMock($request, $exception) 71 | { 72 | $this->mockGenerator->orphanize('__construct'); 73 | $this->mockGenerator->shuntParentClassCalls(); 74 | 75 | $event = new \mock\Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent(); 76 | $event->getMockController()->getRequest = $request; 77 | $event->getMockController()->getException = $exception; 78 | $event->getMockController()->isMasterRequest = true; 79 | 80 | return $event; 81 | 82 | } 83 | 84 | /** 85 | * @return \mock\Symfony\Component\HttpKernel\Kernel 86 | */ 87 | protected function getKernelMock() 88 | { 89 | $this->mockGenerator->orphanize('__construct'); 90 | $kernel = new \mock\Symfony\Component\HttpKernel\Kernel; 91 | 92 | $kernel->getMockController()->getEnvironment = 'test'; 93 | 94 | return $kernel; 95 | } 96 | 97 | /** 98 | * @param array $configException 99 | * 100 | * @return \mock\M6Web\Bundle\ApiExceptionBundle\Manager\Exception 101 | */ 102 | protected function getManagerException(array $configException) 103 | { 104 | $managerException = new ExceptionManager( 105 | $configException, 106 | [] 107 | ); 108 | 109 | return $managerException; 110 | } 111 | 112 | /** 113 | * @return \mock\Symfony\Component\Form\Form 114 | */ 115 | protected function getFormMock() 116 | { 117 | $this->mockGenerator->orphanize('__construct'); 118 | $form = new \mock\Symfony\Component\Form\Form; 119 | 120 | return $form; 121 | } 122 | 123 | /** 124 | * @param array $errors 125 | * 126 | * @return \mock\M6\Heartbeat\Exception\ValidationFormException 127 | */ 128 | protected function getFormValidationExceptionMock(array $errors) 129 | { 130 | $form = $this->getFormMock(); 131 | $exception = new \mock\M6Web\Bundle\ApiExceptionBundle\Exception\ValidationFormException($form); 132 | 133 | $exception->getMockController()->getFlattenErrors = $errors; 134 | 135 | return $exception; 136 | } 137 | 138 | public function testOnKernelExceptionWithMatchAllAndConfigExceptionSameDefault() 139 | { 140 | $matchAll = true; 141 | $statusCode = 400; 142 | $code = 1000; 143 | $message = 'This is an exception for test listener'; 144 | $headers = [ 145 | 'Exception' => 'bad request' 146 | ]; 147 | $configExceptionDefault = [ 148 | 'status' => $statusCode, 149 | 'code' => $code, 150 | 'message' => $message, 151 | 'headers' => $headers, 152 | ]; 153 | $data = [ 154 | 'error' => [ 155 | 'status' => $statusCode, 156 | 'code' => $code, 157 | 'message' => $message, 158 | ] 159 | ]; 160 | 161 | $this 162 | ->given( 163 | $exception = new BadRequestException, 164 | $kernel = $this->getKernelMock(), 165 | $exceptionManager = $this->getManagerException($configExceptionDefault), 166 | $request = $this->getRequestMock('/test-uri/', 'GET'), 167 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 168 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault, false), 169 | $exceptionListener->onKernelException($event), 170 | $validResponse = new JsonResponse($data, $statusCode, $headers) 171 | ) 172 | ->then 173 | ->mock($event) 174 | ->call('setResponse') 175 | ->withArguments($validResponse) 176 | ->once() 177 | ; 178 | } 179 | 180 | public function testOnKernelExceptionWithMatchAllAndConfigExceptionSameDefaultWithoutCode() 181 | { 182 | $matchAll = true; 183 | $statusCode = 400; 184 | $code = 0; 185 | $message = 'This is an exception for test listener'; 186 | $headers = [ 187 | 'Exception' => 'bad request' 188 | ]; 189 | $configExceptionDefault = [ 190 | 'status' => $statusCode, 191 | 'code' => $code, 192 | 'message' => $message, 193 | 'headers' => $headers, 194 | ]; 195 | $data = [ 196 | 'error' => [ 197 | 'status' => $statusCode, 198 | 'message' => $message, 199 | ] 200 | ]; 201 | 202 | $this 203 | ->given( 204 | $exception = new BadRequestException, 205 | $kernel = $this->getKernelMock(), 206 | $exceptionManager = $this->getManagerException($configExceptionDefault), 207 | $request = $this->getRequestMock('/test-uri/', 'GET'), 208 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 209 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault, false), 210 | $exceptionListener->onKernelException($event), 211 | $validResponse = new JsonResponse($data, $statusCode, $headers) 212 | ) 213 | ->then 214 | ->mock($event) 215 | ->call('setResponse') 216 | ->withArguments($validResponse) 217 | ->once() 218 | ; 219 | } 220 | 221 | public function testOnKernelExceptionWithMatchAllAndConfigExceptionDifferentDefaultWithoutHttpException() 222 | { 223 | $statusCodeDefault = 500; 224 | $matchAll = true; 225 | $statusCode = 400; 226 | $code = 1000; 227 | $message = 'This is an exception for test listener'; 228 | $headers = [ 229 | 'Exception' => 'bad request' 230 | ]; 231 | $configExceptionDefault = [ 232 | 'status' => $statusCodeDefault, 233 | 'code' => 0, 234 | 'message' => $message, 235 | 'headers' => $headers, 236 | ]; 237 | $configException = $configExceptionDefault; 238 | $configException['status'] = $statusCode; 239 | $configException['code'] = $code; 240 | $configException['headers'] = []; 241 | $data = [ 242 | 'error' => [ 243 | 'status' => $statusCodeDefault, 244 | 'code' => $code, 245 | 'message' => $message, 246 | ] 247 | ]; 248 | 249 | $this 250 | ->given( 251 | $exception = new BadRequestException, 252 | $kernel = $this->getKernelMock(), 253 | $exceptionManager = $this->getManagerException($configException), 254 | $request = $this->getRequestMock('/test-uri/', 'GET'), 255 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 256 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault), 257 | $exceptionListener->onKernelException($event), 258 | $validResponse = new JsonResponse($data, $statusCodeDefault, $headers) 259 | ) 260 | ->then 261 | ->mock($event) 262 | ->call('setResponse') 263 | ->withArguments($validResponse) 264 | ->once() 265 | ; 266 | } 267 | 268 | public function testOnKernelExceptionWithMatchAllAndConfigExceptionDifferentDefaultWithHttpException() 269 | { 270 | $matchAll = true; 271 | $statusCode = 400; 272 | $code = 1000; 273 | $message = 'This is an exception for test listener'; 274 | $headers = [ 275 | 'Exception' => 'bad request' 276 | ]; 277 | $configExceptionDefault = [ 278 | 'status' => '500', 279 | 'code' => 0, 280 | 'message' => $message, 281 | 'headers' => [], 282 | ]; 283 | $configException = $configExceptionDefault; 284 | $configException['status'] = $statusCode; 285 | $configException['code'] = $code; 286 | $configException['headers'] = $headers; 287 | $data = [ 288 | 'error' => [ 289 | 'status' => $statusCode, 290 | 'code' => $code, 291 | 'message' => $message, 292 | ] 293 | ]; 294 | 295 | $this 296 | ->given( 297 | $exception = new BadRequestHttpException, 298 | $kernel = $this->getKernelMock(), 299 | $exceptionManager = $this->getManagerException($configException), 300 | $request = $this->getRequestMock('/test-uri/', 'GET'), 301 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 302 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault), 303 | $exceptionListener->onKernelException($event), 304 | $validResponse = new JsonResponse($data, $statusCode, $headers) 305 | ) 306 | ->then 307 | ->mock($event) 308 | ->call('setResponse') 309 | ->withArguments($validResponse) 310 | ->once() 311 | ; 312 | } 313 | 314 | public function testOnKernelExceptionWithMatchAllWithStackTrace() 315 | { 316 | $stackTrace = true; 317 | $matchAll = true; 318 | $statusCode = 400; 319 | $code = 1000; 320 | $message = 'This is an exception for test listener'; 321 | $headers = [ 322 | 'Exception' => 'bad request' 323 | ]; 324 | $configExceptionDefault = [ 325 | 'status' => $statusCode, 326 | 'code' => $code, 327 | 'message' => $message, 328 | 'headers' => $headers, 329 | ]; 330 | $data = [ 331 | 'error' => [ 332 | 'status' => $statusCode, 333 | 'code' => $code, 334 | 'message' => $message, 335 | ] 336 | ]; 337 | 338 | $this 339 | ->given( 340 | $exception = new BadRequestException, 341 | $data['error']['stack_trace'] = $exception->getTrace(), 342 | $kernel = $this->getKernelMock(), 343 | $exceptionManager = $this->getManagerException($configExceptionDefault), 344 | $request = $this->getRequestMock('/test-uri/', 'GET'), 345 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 346 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault, $stackTrace), 347 | $exceptionListener->onKernelException($event), 348 | $validResponse = new JsonResponse($data, $statusCode, $headers) 349 | ) 350 | ->then 351 | ->mock($event) 352 | ->call('setResponse') 353 | ->withArguments($validResponse) 354 | ->once() 355 | ; 356 | } 357 | 358 | public function testOnKernelExceptionWithMatchAllWithFlattenError() 359 | { 360 | $matchAll = true; 361 | $statusCode = 400; 362 | $code = 1000; 363 | $message = 'This is an exception for test listener'; 364 | $headers = [ 365 | 'Exception' => 'bad request' 366 | ]; 367 | $configExceptionDefault = [ 368 | 'status' => $statusCode, 369 | 'code' => $code, 370 | 'message' => $message, 371 | 'headers' => $headers, 372 | ]; 373 | $errors = [ 374 | 'name' => [ 375 | 'This field should be not blank.', 376 | 'This value is too long. It should have 30 character or less.' 377 | ] 378 | ]; 379 | $data = [ 380 | 'error' => [ 381 | 'status' => $statusCode, 382 | 'code' => $code, 383 | 'message' => $message, 384 | 'errors' => $errors 385 | ] 386 | ]; 387 | 388 | $this 389 | ->given( 390 | $exception = $this->getFormValidationExceptionMock($errors), 391 | $kernel = $this->getKernelMock(), 392 | $exceptionManager = $this->getManagerException($configExceptionDefault), 393 | $request = $this->getRequestMock('/test-uri/', 'GET'), 394 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 395 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault), 396 | $exceptionListener->onKernelException($event), 397 | $validResponse = new JsonResponse($data, $statusCode, $headers) 398 | ) 399 | ->then 400 | ->mock($event) 401 | ->call('setResponse') 402 | ->withArguments($validResponse) 403 | ->once() 404 | ; 405 | } 406 | 407 | public function testOnKernelExceptionWithoutMatchAllAndExceptionNotMatch() 408 | { 409 | $matchAll = false; 410 | $code = 1000; 411 | $message = 'This is an exception for test listener'; 412 | $configExceptionDefault = [ 413 | 'code' => $code, 414 | 'message' => $message, 415 | ]; 416 | 417 | $this 418 | ->given( 419 | $exception = new \Exception($message, $code), 420 | $kernel = $this->getKernelMock(), 421 | $exceptionManager = $this->getManagerException($configExceptionDefault), 422 | $request = $this->getRequestMock('/test-uri/', 'GET'), 423 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 424 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault), 425 | $exceptionListener->onKernelException($event) 426 | ) 427 | ->then 428 | ->mock($event) 429 | ->call('setResponse') 430 | ->never() 431 | ; 432 | } 433 | 434 | public function testOnKernelExceptionWithMatchAllAndConfigExceptionSameDefaultAndVariablesInMessage() 435 | { 436 | $type = 'notification'; 437 | $matchAll = true; 438 | $statusCode = 400; 439 | $code = 1000; 440 | $message = 'Type {Type_1} not found'; 441 | $headers = []; 442 | $configExceptionDefault = [ 443 | 'status' => $statusCode, 444 | 'code' => $code, 445 | 'message' => $message, 446 | 'headers' => $headers, 447 | ]; 448 | $data = [ 449 | 'error' => [ 450 | 'status' => $statusCode, 451 | 'code' => $code, 452 | 'message' => 'Type '.$type.' not found', 453 | ] 454 | ]; 455 | 456 | $this 457 | ->given( 458 | $exception = new TypeNotFoundHttpException($type), 459 | $kernel = $this->getKernelMock(), 460 | $exceptionManager = $this->getManagerException($configExceptionDefault), 461 | $request = $this->getRequestMock('/test-uri/', 'GET'), 462 | $event = $this->getGetResponseForExceptionEventMock($request, $exception), 463 | $exceptionListener = new TestedClass($kernel, $exceptionManager, $matchAll, $configExceptionDefault, false), 464 | $exceptionListener->onKernelException($event), 465 | $validResponse = new JsonResponse($data, $statusCode, $headers) 466 | ) 467 | ->then 468 | ->mock($event) 469 | ->call('setResponse') 470 | ->withArguments($validResponse) 471 | ->once() 472 | ; 473 | } 474 | } --------------------------------------------------------------------------------