📜 ⬆️ ⬇️

Symfony how to use FOSRestBundle

In this post, I would like to talk about how to properly build the RESTfull API for AngularJS and other frontend frameworks with backend on symfony.
And, as you probably already guessed, I will use the FOSRestBundle - a wonderful bundle, which will help us to implement the backend.
There will not be examples of how to work with Angulyar, I will only describe working with Symfony FosRestBundle.

To work, we also need JMSSerializerBundle to serialize data from Entity to JSON or other formats, exclude certain fields for a particular entity (for example, the password for the API method for getting a list of users) and much more. You can read more in the documentation.


Installation and configuration
1) Download the necessary dependencies in our composer.json
')
"friendsofsymfony/rest-bundle": "^1.7",
"jms/serializer-bundle": "^1.1"


2) Configuration
 // app/AppKernel.php class AppKernel extends Kernel { public function registerBundles() { $bundles = array( // ... new JMS\SerializerBundle\JMSSerializerBundle(), new FOS\RestBundle\FOSRestBundle(), ); // ... } } 


Now edit our config.yml
To begin, we will customize our FOSRestBundle
 fos_rest: body_listener: true view: view_response_listener: true serializer: serialize_null: true body_converter: enabled: true format_listener: rules: - { path: '^/api', priorities: ['json'], fallback_format: json, exception_fallback_format: html, prefer_extension: true } - { path: '^/', priorities: [ 'html', '*/*'], fallback_format: html, prefer_extension: true } 

body_listener includes an EventListener in order to keep track of which response format a user needs, based on his Accept- * headers
view_response_listener - this setting allows you to simply return the View for a particular request
serializer.serialize_null - this setting says that we also want NULL to be serialized, like everything, if it is not set or set to false, then all fields that are null will simply not be displayed in the server response.
PS: thank you for reminding lowadka
body_converter.rules - contains an array for settings oriented to a particular address, in this example we will return JSON for all requests that have the prefix / api , in all other cases - html.

Now we will start setting up our JMSSerializeBundle
 jms_serializer: property_naming: separator: _ lower_case: true metadata: cache: file debug: "%kernel.debug%" file_cache: dir: "%kernel.cache_dir%/serializer" directories: FOSUserBundle: namespace_prefix: FOS\UserBundle path: %kernel.root_dir%/config/serializer/FosUserBundle AppBundle: namespace_prefix: AppBundle path: %kernel.root_dir%/config/serializer/AppBundle auto_detection: true 


Here, it makes sense to dwell on the moment with jms_serializer.metadata.directories , where we tell the serializer that the configuration for a particular class (entity) is there or there :)
We agree that we need to display the entire list of users, I personally use FosUserBundle in my projects and here is my essence:
 <?php namespace AppBundle\Entity; use JMS\Serializer\Annotation\Expose; use JMS\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\Exclude; use JMS\Serializer\Annotation\VirtualProperty; use JMS\Serializer\Annotation\ExclusionPolicy; use Doctrine\ORM\Mapping as ORM; use FOS\UserBundle\Model\User as BaseUser; use FOS\UserBundle\Model\Group; /** * User * * @ORM\Table(name="user") * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository") * @ExclusionPolicy("all") */ class User extends BaseUser { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") * @Exclude */ protected $id; /** * @ORM\Column(type="integer") * @Groups({"user"}) * @Expose */ private $balance = 0; /** * Set balance * * @param integer $balance * * @return User */ public function setBalance($balance) { $this->balance = $balance; return $this; } /** * Get balance * * @return integer */ public function getBalance() { return $this->balance; } } 


I cite as an example precisely this entity, which is inherited from the main FosUserBundle model. This is important because both classes will have to be configured separately for the JmsSerializerBundle.
So back jms_serializer.metadata.directories :
 directories: FOSUserBundle: namespace_prefix: FOS\UserBundle path: %kernel.root_dir%/config/serializer/FosUserBundle AppBundle: namespace_prefix: AppBundle path: %kernel.root_dir%/config/serializer/AppBundle 

Here we just specify that for AppBundle classes we will look for a configuration for serialization in app / config / serializer / AppBundle, and for FosUserBundle - in app / config / serializer / FosUserBundle.
The configuration for the class will be automatically in the format:
For the AppBundle \ Entity \ User class - app / config / serializer / AppBundle / Entity.User. (Yml | xml | php)
For the base model class FosUserBundle - app / config / serializer / FosUserBundle / Model.User. (Yml | xml | php)

Personally, I prefer to use YAML. Let's finally start telling JMSSerializer how we need it to set up a particular class.
app / config / serializer / AppBundle / Entity.User.yml
 AppBundle\Entity\User: exclusion_policy: ALL properties: balance: expose: true 


app / config / serializer / FosUserBundle / Model.User.yml
 FOS\UserBundle\Model\User: exclusion_policy: ALL group: user properties: id: expose: true username: expose: true email: expose: true balance: expose: true 


So simply we could tell that we want to see approximately the following response format from the server when receiving data from 1 user:
 {"id":1,"username":"admin","email":"admin","balance":0} 


In principle, this configuration is not necessary to register and the server will return all the data about the entity. Only in this case it is illogical for us to show many things, for example, such as a password. Therefore, I considered it necessary to demonstrate in this example exactly such an implementation.

Now let's start creating the controller.
First of all, create a route:
 backend_user: resource: "@BackendUserBundle/Resources/config/routing.yml" prefix: /api 

Pay attention to / api - do not forget to add it, and if you want to change, you will have to change the configuration for fos_rest in config.yml

Now BackendUserBundle / Resources / config / routing.yml itself:
 backend_user_users: type: rest resource: "@BackendUserBundle/Controller/UsersController.php" prefix: /v1 


Now you can start creating the controller itself:
 <?php namespace Backend\UserBundle\Controller; use AppBundle\Entity\User; use FOS\RestBundle\Controller\FOSRestController; use FOS\RestBundle\Controller\Annotations as Rest; use FOS\RestBundle\Controller\Annotations\View; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class UsersController * @package Backend\UserBundle\Controller */ class UsersController extends FOSRestController { /** * @return \Symfony\Component\HttpFoundation\Response * @View(serializerGroups={"user"}) */ public function getUsersAllAction() { $users = $this->getDoctrine()->getRepository('AppBundle:User')->findAll(); $view = $this->view($users, 200); return $this->handleView($view); } /** * @param $id * @return \Symfony\Component\HttpFoundation\Response * @View(serializerGroups={"user"}) */ public function getUserAction($id) { $user = $this->getDoctrine()->getRepository('AppBundle:User')->find($id); if (!$user instanceof User) { throw new NotFoundHttpException('User not found'); } $view = $this->view($user, 200); return $this->handleView($view); } } 


Note that we now inherit from FOS \ RestBundle \ Controller \ FOSRestController .
By the way, you probably noticed the View annotation (serializerGroups = {"user"}).
The fact is that we want to see both the App \ Entity \ User data and the main FosUserBundle model, in which all the other fields are stored, we need to create a specific group, in this case “user”.

So, we have 2 actions getUserAction and getUsersAllAction. Now you will understand the specifics of the names of the controller methods.
Make a debug of all routes:
$ app / console debug: route | grep api
We get:
 get_users_all GET ANY ANY /api/v1/users/all.{_format} get_user GET ANY ANY /api/v1/users/{id}.{_format} 


Consider the following example with new methods:
 <?php class UsersComment extends Controller { public function postUser($id) {} // "post_user_comment_vote" [POST] /users/{id} public function getUser($id) {} // "get_user" [GET] /users/{id} public function deleteUserAction($id) {} // "delete_user" [DELETE] /users/{id} public function newUserAction($id) {} // "new_user" [GET] /users/{id}/new public function editUserAction($slug, $id) {} // "edit_user" [GET] /users/{id}/edit public function removeUserAction($slug) {} // "remove_user" [GET] /users/{slug}/remove } 

Reminds Laravel Resource Controller, right?

The comments show which address and method of the request will be executed by one or another method.
Next time I will tell you how to correctly use FOSRestBundle to, for example, display comments of a specific user at the address: "/ users / {id} / comments", create / update user data.

Source: https://habr.com/ru/post/278123/


All Articles