0

Current Functionality

Hello,

We are building a REST API using Symfony 3.3 and want to be able to output any type of error information (both exceptions and API-specific errors) back to the requester as a JSON response.

We have already installed a bundle to take care of API Exception handling. (It will catch normal built-in exceptions such as 404, and return their status codes with a brief description in JSON format).

What this bundle does is that, for example, if we go to a route that doesn't exist, it will pass back a JSON object similar to the following:

{ "errorCode": 404, "errorMessage": "No route found for /testing" }

The New Functionality Which Is Needed

Now that all normal exceptions are caught and displayed in JSON format, we want to be able to catch when the user uses our API incorrectly and then alert them to how they can fix their request via the JSON response we send them.

We want to catch API-usage errors, such as if the user is:

  1. Missing required parameters.
  2. Passing in the incorrect type of parameter.

We have a lot of different actions in the controllers which each accept a different parameter list, and some may not accept parameters at all.

We want to be able to catch both incorrect parameters in the route itself (for example, the first part of our route starts with the language code - i.e. something like http://localhost/en/someAction) and we also want to catch incorrect parameters added as a query - i.e. if they do something like http://localhost/en/someAction?firstName=123. Obviously in this case, we would want a string passed into the firstName parameter.

Current Proposed Solution

A. We imagine that we'll need some sort of global mapping array which will hold the data of what the correct parameter names should be for each controller action (storing this information in the database is likely not an option at this point). For example, I was thinking we could have an associative array something like the following:

[ "addUserAction" => ["firstName|str|req", "lastName|str|opt"],
"deleteUserAction" => ["userId|int|req"] ]

B. The only thing is, we not only want to perform these checks at the controller action level, but we also want to perform these checks in each of the repository functions in order to provide an extra layer of security. So, we'd have to actually add additional data to the array above to differentiate between whether a controller action is being executed or a repository's function. Perhaps something like:

[ "ctl.user.addUserAction" => ["firstName|str|req", "lastName|str|opt"],
"repo.user.deleteUser" => ["userId|int|req"] ]

C. This mapping array would then need to be stored somewhere globally where both controllers and repositories can access it.

D. Once we're able to access the mapping array from both controllers and repositories, we would then lookup the current function we're in within the array and then we'd know what the correct parameters to accept are and could decide whether to throw a JSON exception or not.

Questions

Q1. A Better Solution?

Looking at my proposed plan above, even if I'm able to implement it, it seems like an awful lot of overhead work (and a very coupled design which will involve that array having to be added to constantly whenever we add new functionality). I imagine there is a smarter way to not only perform the mapping in Symfony but also to check the mapping at the appropriate time without having to edit each and every function to check against the associate array (which is why I'm reaching out for help here before I begin coding this). I would appreciate any feedback on how this proposed solution could be made better and more maintainable.

Q2. Global Associative Arrays

Assuming that for some reason that we did want to go with the global associative array idea proposed above, out of curiosity, where would the proper place be in Symfony to place such an array so that it can be accessed globally (i.e. by both a controller's action or repository's function)? (As a PHP Developer coming from a CodeIgniter-background, I know that in CodeIgniter, we would have placed things we wanted to use across the application in the constants.php file. No idea what the equivalent functionality would be in Symfony).

khgm13
  • 63
  • 7
  • 2
    Did you look into the [Form](https://symfony.com/doc/current/forms.html) component (and/or the [Validation](https://symfony.com/doc/current/validation.html) component) of Symfony? Also you should probably use Symfony 3.4 instead of 3.3 (or directly go to Symfony 4.0). – Tobias Xy Feb 05 '18 at 19:37
  • Considering how broadly Symfony can be utilized to handle your specific use-case you will get a lot of different approaches. From listeners, services, annotation readers, and pure php configurations. My suggestion would be to utilize the [kernel event dispatcher](https://symfony.com/doc/3.4/event_dispatcher/before_after_filters.html) to validate the user request against your controller definitions and what the controllers implement. Thus making the error controlling easier to track and expand upon. – Will B. Feb 05 '18 at 20:42
  • Thanks Tobias. I'm reading up on the Form and Validation components now. – khgm13 Feb 05 '18 at 20:58
  • Thanks fyrye. I will read up on the kernel event dispatcher. The only thing is, I think I still need some sort of associative array/object to keep track of the parameter mappings against since most of the parameters we accept are via query string rather than directly through the route. And from what I understand, the parameter type hinting we can provide in controller action function definitions only works with parameters passed as part of the route. – khgm13 Feb 05 '18 at 21:04
  • @khgm13 you can leverage the `AnnotationReader`, or define your rules within the event dispatchers based on the interface type(s) of the controller as shown in the example, or even define them within the routing parameters as `defaults` and `requirements`. However, it is strongly encourage when developing a REST API to create hardened routes, not abstract routes that can accept a random amount or type of parameters. Something that you can provide coverage on. – Will B. Feb 05 '18 at 23:41

1 Answers1

0

Well, regarding controllers actions params, seems like you're trying to recreate the part of routing. There is already a mechanism to restrict params and their types (for example, via regex).

As for method params, well PHP is dynamically typed language) If you're using PHP 7 and above, you can simply type hint scalar types (integer, string, etc.). As for optional params, you can simply default them to null.

But what you're trying to achieve with an additional security layer, usually have no use in real world. If you need such bulletproof code, maybe consider using Java or C#. It is one thing to sanitize user input (which is always should be done), but to sanitize method params is a little bit paranoid. No offense meant, but you should trust your developers.

sevavietl
  • 3,762
  • 1
  • 14
  • 21
  • Thanks for your reply sevavietl. I would definitely like to take advantage of type hinting, if possible. I did a bit of research, but from what I have found, it looks like type hinting can only be used with the parameters that are passed in as part of the route (e.g. http://localhost/languageParameter/testing). If the parameters are passed as a query string after the question mark (e.g. ?parameterName=parameterValue), then I believe the only way to get them is to perform a $request->query->all(), assign it to a variable and manually check the types. Would you know of a workaround? – khgm13 Feb 05 '18 at 20:54
  • 1
    @khgm13, yes, about query params -- my bad, sorry. If I was creating API myself, I would ask myself a question why I need those params in the first place. You can take a look at [these REST courses](https://knpuniversity.com/tracks/rest). Maybe you will find some answers and ideas there, although they are not free( As I understand for the API, better to use as fewer query params as possible. – sevavietl Feb 05 '18 at 21:44