{"id":1707,"date":"2015-11-03T09:28:18","date_gmt":"2015-11-03T09:28:18","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=1707"},"modified":"2024-11-27T12:46:42","modified_gmt":"2024-11-27T12:46:42","slug":"getting-started-with-building-apis-in-symfony2","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/getting-started-with-building-apis-in-symfony2\/","title":{"rendered":"Getting Started with Building APIs in Symfony2"},"content":{"rendered":"<p>Hello all you Interwebs friends!\u00a0While we&#8217;re passing through the shallow mists of time, REST\u00a0is becoming more and more of a universal standard when building web applications. That said, here&#8217;s a very brief tutorial on how to get started with building APIs in Symfony2.<\/p>\n<p><strong>Spoiler alert:<\/strong>\u00a0the bits of code written below use\u00a0FosUserBundle + Doctrine.<\/p>\n<h2>1.<strong> Generate a New Bundle<\/strong><\/h2>\n<p>It&#8217;s nice to keep your code neat and tidy, so ideally, you should create a separate bundle that will only store your API code, we can generically name <strong>ApiBundle<\/strong>.<\/p>\n<pre class=\"\">$ php app\/console generate:bundle --bundle-name=ApiBundle --format=annotation<\/pre>\n<p>&nbsp;<\/p>\n<h3>2. \u00a0<strong>Versioning<\/strong><\/h3>\n<p>This isn&#8217;t by any means mandatory, but if you believe that your API endpoints will suffer major changes along the way, that cannot be predicted from the get go, it would be nice to version your code. This means that you would, initially, have a prefix in your endpoints, like: `\/v1`\/endpoint.json, and you&#8217;d increase that value each time a new version comes along. I&#8217;ll describe how to actually\u00a0create the first version (`v1`) of your API a little further\u00a0down the line.<\/p>\n<h3>3. <strong>Install a Few Useful Bundles<\/strong><\/h3>\n<ul>\n<li><a href=\"http:\/\/symfony.com\/doc\/current\/bundles\/FOSRestBundle\/index.html\" target=\"_blank\" rel=\"noopener\">FosRestBundle<\/a> &#8211; this bundle will make our REST implementation a lot easier.<\/li>\n<\/ul>\n<pre class=\"lang:default decode:true \">$ composer require friendsofsymfony\/rest-bundle<\/pre>\n<p>and then include <strong>FOSRestBundle<\/strong> in your `AppKernel.php` file:<\/p>\n<pre class=\"lang:default decode:true\">$bundles = array(\r\n\r\n\/\/ ...\r\nnew FOS\\RestBundle\\FOSRestBundle(),\r\n);\r\n<\/pre>\n<ul>\n<li><a href=\"http:\/\/jmsyst.com\/bundles\/JMSSerializerBundle\" target=\"_blank\" rel=\"noopener\">JmsSerializerBundle<\/a> &#8211; this will take care of the representation of our resources, converting objects into JSON.<\/li>\n<\/ul>\n<pre class=\"lang:default decode:true\">composer require jms\/serializer-bundle<\/pre>\n<p><span style=\"line-height: 1.5;\">and then include <strong>JMSSerializerBundle<\/strong> in your `AppKernel.php`:<\/span><\/p>\n<pre class=\"lang:default decode:true \">$bundles = array(\r\n\/\/ ...\r\nnew JMS\\SerializerBundle\\JMSSerializerBundle(),\r\n\/\/ ...\r\n);<\/pre>\n<p>&nbsp;<\/p>\n<h3>4. <strong>Configurations<\/strong><\/h3>\n<p><strong>Configure <\/strong>the Response object to return JSON, as well as set a default format for our API calls. This can be achieved by\u00a0adding the following code in `app\/config\/config.yml`:<\/p>\n<pre class=\"lang:default decode:true\">fos_rest:\r\n    format_listener:\r\n          rules:\r\n              - { path: ^\/api\/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true }\r\n    routing_loader:\r\n        default_format: json\r\n    param_fetcher_listener: true\r\n    view:\r\n        view_response_listener: 'force'\r\n        formats:\r\n            xml: true\r\n            json: true\r\n        templating_formats:\r\n            html: true<\/pre>\n<p>&nbsp;<\/p>\n<h3>5. <strong>Routing<\/strong><\/h3>\n<p>I prefer using annotations as far as routes go, so all we need to do in this scenario would be to modify\u00a0our API Controllers registration in `\/app\/config\/routing.yml`. This registration should have already been created when you ran the generate bundle command. Now we&#8217;ll only need to add our version to that registration.\u00a0As far as the actual routes of each endpoint go, we&#8217;ll be manually defining them\u00a0later on, in each action&#8217;s annotation.<\/p>\n<pre class=\"lang:default decode:true\">api:\r\n    resource: \"@ApiBundle\/Controller\/V1\/\"\r\n    type: annotation\r\n    prefix: \/api\/v1<\/pre>\n<p>At this point we&#8217;re all set to start writing our first bit of code. First off, in our Controller namespace, we would want to create a new directory, called `V1`. This\u00a0will hold all of our v1 API Controllers. Whenever we want to create a new version, we&#8217;ll start from scratch by creating a `V2` namespace and so on.<\/p>\n<p>After that&#8217;s done let&#8217;s create an action that will GET\u00a0a user (assuming we&#8217;ve previously created a User entity and populated it with users). This would look something like:<\/p>\n<pre class=\"lang:php decode:true \">&lt;?php  \r\nnamespace ApiBundle\\Controller\\V1;  \r\n\r\nuse FOS\\RestBundle\\Controller\\FOSRestController;  \r\nuse FOS\\RestBundle\\Controller\\Annotations as Rest;  \r\n\r\nclass UserController extends FOSRestController  \r\n{     \r\n\/**       \r\n* @return array       \r\n* @Rest\\Get(\"\/users\/{id}\")       \r\n* @Rest\\View       \r\n*\/    \r\npublic function getUserAction($id)     \r\n{      \r\n     $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n     $user = $em-&gt;getRepository('AppBundle:User')-&gt;find($id);\r\n\r\n     return array('user' =&gt; $user);\r\n   }\r\n}<\/pre>\n<p>If we want to GET a list of all users, that&#8217;s pretty straightforward as well:<\/p>\n<pre class=\"lang:php decode:true \">\/**\r\n * GET Route annotation.\r\n * @return array\r\n * @Rest\\Get(\"\/users\/get.{_format}\")\r\n * @Rest\\View\r\n *\/\r\npublic function getUsersAction()\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n    $users = $em-&gt;getRepository('AppBundle:User')-&gt;findAll();\r\n\r\n    return array('users' =&gt; $users);\r\n}<\/pre>\n<p>With that done, when running a GET request on `https:\/\/yourapp.com\/api\/v1\/users\/1.json`, you should get a `json` response with\u00a0that specific user object.<\/p>\n<p>What about a POST request? Glad you asked!\u00a0There are actually quite a few options to do that. One would be to get the request data yourself,\u00a0validate it and create the new resource yourself. Another (simpler) option would be to use <a href=\"http:\/\/symfony.com\/doc\/current\/book\/forms.html\">Symfony Forms <\/a>which would handle all this for us.<\/p>\n<p>The\u00a0scenario here would be for us to add a new user resource into our database.<\/p>\n<p>If you&#8217;re also using\u00a0<strong>FosUserBundle<\/strong>\u00a0to manage your users, you can just use a similar\u00a0RegistrationFormType:<\/p>\n<pre class=\"lang:php decode:true\">&lt;?php  \r\nnamespace ApiBundle\\Form\\Type;  \r\n\r\nuse Symfony\\Component\\Form\\AbstractType;  \r\nuse Symfony\\Component\\Form\\FormBuilderInterface;  \r\nuse Symfony\\Component\\OptionsResolver\\OptionsResolver;  \r\n\r\nclass RegistrationFormType extends AbstractType  \r\n{      \r\npublic function buildForm(FormBuilderInterface $builder, array $options)     {                 \r\n         $builder\r\n             -&gt;add('email', 'email')\r\n             -&gt;add('username')\r\n             -&gt;add('plainPassword', 'password')\r\n        ;\r\n    }\r\n\r\n    public function configureOptions(OptionsResolver $resolver)\r\n    {\r\n        $resolver-&gt;setDefaults(array(\r\n            'data_class' =&gt; 'AppBundle\\Entity\\User',\r\n            'csrf_protection'   =&gt; false\r\n        ));\r\n    }\r\n\r\n\r\n    public function getName()\r\n    {\r\n        return 'my_awesome_form';\r\n    }\r\n}<\/pre>\n<p>Next, we&#8217;ll want to actually create our addUserAction():<\/p>\n<pre class=\"lang:php decode:true\">\/**\r\n * POST Route annotation.\r\n * @Rest\\Post(\"\/users\/new.{_format}\")\r\n * @Rest\\View\r\n * @return array\r\n *\/\r\npublic function addUserAction(Request $request)\r\n{\r\n    $userManager = $this-&gt;container-&gt;get('fos_user.user_manager');\r\n    $user = $userManager-&gt;createUser();\r\n\r\n    $form = $this-&gt;createForm(new \\ApiBundle\\Form\\Type\\RegistrationFormType(), $user);\r\n\r\n    $form-&gt;handleRequest($request);\r\n\r\n    if ($form-&gt;isValid())\r\n    {\r\n        $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n        $em-&gt;persist($user);\r\n        $em-&gt;flush();\r\n\r\n        return array('user' =&gt;  $user);\r\n    }\r\n\r\n    return View::create($form, 400);\r\n}<\/pre>\n<p>To make a request, you&#8217;ll need to send the data as raw JSON,\u00a0to our `https:\/\/yourapp.com\/api\/v1\/users\/new.json` POST endpoint:<\/p>\n<pre class=\"lang:default decode:true\">{\r\n    \"my_awesome_form\":{\r\n            \"email\":\"andrei@ibw.com\",\r\n            \"username\":\"sdsa\",\r\n            \"plainPassword\":\"asd\"\r\n    }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>And that&#8217;s all there is to it.<\/p>\n<p>We haven&#8217;t covered the cases where you&#8217;d want Delete or Update a user yet. Updating resources through the REST standards can be done using either PUT or PATCH. The difference between them is that PUT\u00a0will completely replace your resource, while PATCH will only, well, patch it&#8230; meaning that it will partially update your resource with the input it got from the API request.<\/p>\n<p>Let&#8217;s get to it then. We&#8217;ll use the same form as before, and we&#8217;ll try to only change (we&#8217;ll use the PATCH verb for that) the email address, username and password of our previously created user:<\/p>\n<pre class=\"lang:php decode:true\">\/**\r\n * PATCH Route annotation.\r\n * @Rest\\Patch(\"\/users\/edit\/{id}.{_format}\")\r\n * @Rest\\View\r\n * @return array\r\n *\/\r\npublic function editAction(Request $request, $id)\r\n{\r\n    $userManager = $this-&gt;container-&gt;get('fos_user.user_manager');\r\n    $user = $userManager-&gt;findUserBy(array('id'=&gt;$id));\r\n\r\n    $form = $this-&gt;createForm(new \\ApiBundle\\Form\\Type\\RegistrationFormType(), $user, array('method' =&gt; 'PATCH'));\r\n\r\n    $form-&gt;handleRequest($request);\r\n    if ($user) {\r\n        if ($form-&gt;isValid()) {\r\n            \r\n            $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n            $em-&gt;persist($user);\r\n            $em-&gt;flush();\r\n\r\n            return array('user' =&gt; $user);\r\n        } else {\r\n            return View::create($form, 400);\r\n        }\r\n    } else {\r\n        throw $this-&gt;createNotFoundException('User not found!');\r\n    }\r\n}<\/pre>\n<p>The request body is the same as the one shown for the POST method, above. There are a few small differences in our edit action\u00a0though. First off &#8211; we&#8217;re telling our form to use the PATCH method. Second &#8211; we are handling the case where the user ID provided isn&#8217;t found.<\/p>\n<p>The Delete method is the easiest one yet. All we need to do is to find the user and remove it from our database. If no user is found, we&#8217;ll throw a &#8220;user not found&#8221; error:<\/p>\n<pre class=\"lang:php decode:true\">\/**\r\n * DELETE Route annotation.\r\n * @Rest\\Delete(\"\/users\/delete\/{id}.{_format}\")\r\n * @Rest\\View(statusCode=204)\r\n * @return array\r\n *\/\r\npublic function deleteAction($id)\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n    $user = $em-&gt;getRepository('AppBundle:User')-&gt;find($id);\r\n\r\n    $em-&gt;remove($user);\r\n    $em-&gt;flush();\r\n}<\/pre>\n<h3><strong>Related: Routing in Symfony2<\/strong><\/h3>\n<h3><strong>Conclusions<\/strong><\/h3>\n<p>Aaand we&#8217;re done. We now have a fully working CRUD for our User Entity. Thanks for reading and please do share your opinions\/suggestions in the comment section below.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello all you Interwebs friends!\u00a0While we&#8217;re passing through the shallow mists of time, REST\u00a0is becoming more and more of a [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":1746,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[92,114,194,210,238,248],"yst_prominent_words":[340,373,394,437,629,798],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1707"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=1707"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1707\/revisions"}],"predecessor-version":[{"id":133324,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/1707\/revisions\/133324"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media\/1746"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=1707"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=1707"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=1707"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=1707"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}