Transaction: 5e89a50a56d9f5f8a58cd1c70cfd7ad991c62e96

Included in block 20,854,846 at 2018/03/21 00:20:21 (UTC).

Transaction overview

Loading...
Transaction info
transaction_id 5e89a50a56d9f5f8a58cd1c70cfd7ad991c62e96
ref_block_num 14,380
block_num20,854,846
ref_block_prefix 2,538,936,631
expiration2018/03/21T00:30:15
transaction_num 7
extensions[]
signatures 204680febe9ef32c99bbe18241da71112276404b8331316d6a57f18b7ba29ac3d96427819ceff31ec0296c0c21ea2b8fa8064277da04bb565a2cbc68b6a38e4c85
operations
comment
"parent_author":"",<br>"parent_permlink":"utopian-io",<br>"author":"piotr42",<br>"permlink":"web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm-part-3",<br>"title":"Web Development with Symfony: An application capable of dispatching push notifications to mobile devices via FCM [part 3 ",<br>"body":"<center>![email-marketing-2362038_640.png (https:\/\/res.cloudinary.com\/hpiynhbhq\/image\/upload\/v1521590581\/enzgzrrro89cmwoptyf8.png)\n<\/center>\n\n### What will I learn?\n* How to create a form for an entity\n* How to add validation to a form\n* How to create a controller using a REST\/SOAP API design principle\n\n### Requirements\n* UNIX\/Linux based OS\n* Apache2 server with PHP7 installed\n* MySQL database\n* Text editor of choice\n* Existing Firebase project\n* Composer\n* Base project found [here (https:\/\/github.com\/ptrio42\/message-bundle-demo-part-2)\n\n### Difficulty\n* Intermediate\n\n### Tutorial contents\n\nThis tutorial is a third one in the Web Development series where we jump into details on how to develop applications with Symfony using a sample project capable of sending push notifications to mobile devices. In the previous article the process of creating an entity repository and extending a manager service were briefly described. In addition,<br> a way of defining service container parameters to ease application maintenance was shown.\n\nA sample project used with this tutorial can be found\u00a0[here (https:\/\/github.com\/ptrio42\/message-bundle-demo-part-2). It is basically the code you will end up with after completing the previous tutorials. It is recommended to clone the aforementioned project from the given github repository.\n\n#### What aspects of Symfony web development will be covered in this tutorial?\n* The process of creating a form for an entity,<br> which allows to add new entities through a HTTP POST request.\n* The list of activities required to add validation rules to an entity,<br> which will prevent from persisting incorrect values to the database.\n* The process of creating a controller capable of processing REST\/SOAP API requests in JSON and XML formats.\n\nAn example usage for the created functionalities will be shown at the end of this tutorial.\n\n#### How to create a form for an entity?\n\nThe Form Component provided by the creators of the Symfony framework is designed to make a life of a web developer much easier. When dealing with HTTP forms,<br> with a bit of help from the Form Component,<br> form objects can be separated out to standalone `Type` classes which can be used throughout the whole application.\n\nThe Form Component can be installed with Composer.\n\n```\n$ composer require form\n```\n\n##### A form class\n\nA device form will be built in a standalone class called `DeviceType` and then used in a controller which will be created later on.\n\n> Note: By separating a logic responsible for a form building process you are actually following the **separation of concerns** design principle.\n\nSince a `AbstractType` base class comes already bundled with the Form Component,<br> an abstraction layer for a form object definition is not necessary.\n\n###### A concrete class\n\nBegin by creating a directory called `src\/Ptrio\/MessageBundle\/Form\/Type` and add a file named `DeviceType.php` inside.\n\n```\n<?php\n\/\/ src\/Ptrio\/MessageBundle\/Form\/Type\/DeviceType.php\nnamespace App\\Ptrio\\MessageBundle\\Form\\Type;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\OptionsResolver\\OptionsResolver;\nclass DeviceType extends AbstractType\n \n \/**\n * @var string\n *\/\n private $class;\n \/**\n * DeviceType constructor.\n * @param string $class\n *\/\n public function __construct(string $class)\n \n $this->class = $class;\n \n \/**\n * @param FormBuilderInterface $builder\n * @param array $options\n *\/\n public function buildForm(FormBuilderInterface $builder,<br> array $options)\n \n $builder\n ->add('name')\n ->add('token')\n ;\n \n \/**\n * @param OptionsResolver $resolver\n *\/\n public function configureOptions(OptionsResolver $resolver)\n \n $resolver->setDefaults([\n 'base_class' => $this->class,<br>\n 'csrf_protection' => false,<br>\n );\n \n \n```\n\nLet\u2019s go over some of the most important aspects found in an example `DeviceType` class presented above.\n\nA fully-qualified entity class name is passed as a constructor argument to avoid value hardcoding. \n\n> Note: In the previous tutorial a device entity class name was separated out to a service container parameter. That is great news,<br> because it can now be used for dependency injection.\n\nA `AbstractType::buildForm(FormBuilderInterface $builder,<br> array $options)` method contains the actual logic used to build a form with a `FormBuilder` object. A device form will contain two properties: `name` and `token`. A type for each form property (or field) will be determined automatically.\n\n> Note: Field names has to correspond with the properties defined in a device entity class.\n\nA `AbstractType::configureOptions(OptionsResolver $resolver)` is where the defaults for a form object are set.\n\n> The OptionsResolver component is\u00a0array_replace\u00a0on steroids. It allows you to create an options system with required options,<br> defaults,<br> validation (type,<br> value),<br> normalization and more.\n\n*Source: [https:\/\/symfony.com (https:\/\/symfony.com).*\n\nIn our example a `base_class` option is set to a value held in `DeviceType::$class`. A form object uses this value to determine the appropriate data mapper.\n\nA `csrf_protection` option determines if a protection against CSRF attacks should be enabled for a form. This option should be set to `false`,<br> since a form object will not be rendered (no client interface yet),<br> and thence a `csrf_token` will not be generated.\n\n\n\n##### Service configuration\n\nA definition service has to be created in a `src\/Ptrio\/MessageBundle\/Resources\/config\/services.yaml` configuration file so dependency injection can be performed on a `FormType` object and an entity class name can be passed as a constructor argument.\n\n```\n # src\/Ptrio\/MessageBundle\/Resources\/config\/services.yaml\n ptrio_message.device_type:\n class: 'App\\Ptrio\\MessageBundle\\Form\\Type\\DeviceType'\n arguments:\n - '%ptrio_message.model.device.class%'\n tags:\n - name: form.type,<br> alias: 'ptrio_message_device_type' \n```\n\nA `FormType` service must be tagged as a `form.type` for the above configuration to work properly.\n\n> Note: A `ptrio_message.model.device.class` service container parameter which contains a fully-qualified `Device` entity class name was used as a constructor argument.\n\n#### How to add validation to a form?\n\nThere are two properties,<br> except `Device::$id`,<br> which are required for every device entity object: `Device::$name` and `Device::$token`. Validation rules should be applied to a device entity class to ensure that empty values will not be assigned to the aforementioned properties while persisting an entity to a database. This can be achieved with the Validator Component.\n\nThe Validator Component can be installed with Composer.\n\n```\n$ composer require validator\n```\n\nValidation rules are applied to an entity properties by adding appropriate annotations.\n\nStart by adding an import statement for a validator class with a `Assert` alias. \n\n```\nuse Symfony\\Component\\Validator\\Constraints as Assert;\n```\n\nNext,<br> add a `@Assert\\NotBlank()` annotation over `Device::$name` and `Device::$token` properties. A rule applied by this annotation,<br> as the name implies,<br> is responsible for preventing a blank value to be passed.\n\n```\n \/\/ src\/Ptrio\/MessageBundle\/Entity\/Device.php\n \/**\n * @ORM\\Column(type=\"string\")\n * @Assert\\NotBlank() \n *\/\n protected $name;\n \/**\n * @ORM\\Column(type=\"string\")\n * @Assert\\NotBlank()\n *\/\n protected $token;\n```\n\n\n\n#### How to create a controller using a REST\/SOAP API design principle?\n\nAt this stage,<br> the process of creating a controller service that follows a REST\/SOAP API design principle will be described. By taking this approach,<br> any client capable of communicating via HTTP protocol will be able to interact with our server application.\n\n> REST\/SOAP principle is a set of rules,<br> which define how the operations should be performed on a API (Application Programming Interface). A REST web service request\/response is in JSON,<br> while a corresponding one in SOAP is in XML format.\n\nA component called `FOSRestBundle` is provided by the community to kickstart a REST\/SOAP application development process.\n\n> Note: `FOSRestBundle` needs a serializer service to work properly. The Serializer Component can be installed with Composer by requiring a package called `serializer`.\n\n\nLet\u2019s install the `FOSRestBundle` component with Composer.\n\n```\n$ composer require friendsofsymfony\/rest-bundle\n```\n\nBefore jumping over to the next step,<br> a configuration for `FOSRestBundle` has to be created to handle JSON and XML formats for requests\/responses made behind a specified API endpoint.\n\nReplace the default configuration in `config\/packages\/fos_rest.yaml` with the configuration presented below.\n\n```\n# config\/packages\/fos_rest.yaml\nfos_rest:\n format_listener:\n rules:\n - path: ^\/api\/v1,<br> prefer_extension: true,<br> fallback_format: json,<br> priorities: [ json,<br> xml \n```\n\nIn the example above,<br> a rule tells the `format_listener` that all requests\/responses made behind a `\/api\/v1` API endpoint should be in JSON and XML formats (JSON being the default one).\n\n##### A controller class\n\nA controller class will hold definitions responsible for creating a new device entity object,<br> removing it and also displaying details for a device found by a given name.\n\n> Note: `FOSRestBundle` component comes with a base controller class,<br> so an abstraction layer will not be necessary for a controller class definition.\n\n###### A concrete class\n\nA controller concrete class `DeviceController` will extend a `FOSRestController` base class which comes with a bunch of methods to help processing requests and returning responses without being dependent on a specific format. This way a format agnostic controller is created.\n\n```\n<?php\n\/\/ src\/Ptrio\/MessageBundle\/Controller\/DeviceController.php\nnamespace App\\Ptrio\\MessageBundle\\Controller;\nuse App\\Ptrio\\MessageBundle\\Model\\DeviceInterface;\nuse App\\Ptrio\\MessageBundle\\Model\\DeviceManagerInterface;\nuse FOS\\RestBundle\\Controller\\FOSRestController;\nuse Symfony\\Component\\Form\\FormFactoryInterface;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nclass DeviceController extends FOSRestController\n \n \/**\n * @var DeviceManagerInterface\n *\/\n private $deviceManager;\n \/**\n * @var FormFactoryInterface\n *\/\n private $formFactory;\n \/**\n * DeviceController constructor.\n * @param DeviceManagerInterface $deviceManager\n * @param FormFactoryInterface $formFactory\n *\/\n public function __construct(\n DeviceManagerInterface $deviceManager,<br>\n FormFactoryInterface $formFactory\n )\n \n $this->deviceManager = $deviceManager;\n $this->formFactory = $formFactory;\n \n \/**\n * @param string $deviceName\n * @return Response\n *\/\n public function getDeviceAction(string $deviceName): Response\n \n if ($device = $this->deviceManager->findDeviceByName($deviceName)) \n $view = $this->view($device,<br> 200);\n else \n $view = $this->view(null,<br> 404);\n \n return $this->handleView($view);\n \n \/**\n * @param Request $request\n * @return Response\n *\/\n public function postDeviceAction(Request $request): Response\n \n \/** @var DeviceInterface $device *\/\n $device = $this->deviceManager->createDevice();\n $form = $this->formFactory->create(DeviceType::class,<br> $device);\n $form->submit($request->request->all());\n if ($form->isSubmitted() && $form->isValid()) \n $device = $form->getData();\n $this->deviceManager->updateDevice($device);\n $view = $this->view(null,<br> 204);\n else \n $view = $this->view($form->getErrors(),<br> 422);\n \n return $this->handleView($view);\n \n \/**\n * @param string $deviceName\n * @return Response\n *\/\n public function deleteDeviceAction(string $deviceName): Response\n \n if ($device = $this->deviceManager->findDeviceByName($deviceName)) \n $this->deviceManager->removeDevice($device);\n $view = $this->view(null,<br> 204);\n else \n $view = $this->view(null,<br> 404);\n \n return $this->handleView($view);\n \n \n```\n\nSince there is a lot going on in our controller class,<br> let\u2019s go over the most important aspects found in the code above.\n\nA controller class controller accepts two arguments: `DeviceManagerInterface` and `FormFactoryInterface` objects. The first one is a device manager object,<br> which definition was created earlier in this Web Development series. The later one is a `FormFactory` instance which allows for creating a form object. \n\nA form object is created with a `FormFactory::create($type = 'Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType',<br> $data = null,<br> array $options = array())` method. In the example above,<br> two arguments are passed: a fully-qualified class name for a `FormType` object and an entity object created with a device manager service.\n\nA form is submitted with a `Form::submit($submittedData,<br> $clearMissing = true)` method. In our example,<br> an array returned by a `$request->request->all()` method,<br> containing form data,<br> is passed as a `$submittedData` argument.\n\nA `FOSRestController::view($data = null,<br> $statusCode = null,<br> array $headers = [ )` method is responsible for creating a `View` object that can be assigned some data,<br> a HTTP response code and additionally headers. \n\nA `FOSRestController::handleView(View $view)` method takes care of transforming a `View` object to a `Response` object which then can be returned to a client.\n\n**A list of HTTP Codes returned by our controller**\nCode|Meaning\n-|-\n204|Indicates that a request was processed successfully but no content was returned. \n404| A server was not able to find a given resource.\n422|A server was not able to process a request.\n\n\n##### Service configuration\n\nA `DeviceController` will be defined as a service since it is dependent on other services defined in a container. \n\nAdd a `ptrio_message.device_controller` service definition to a `services.yaml` file.\n\n```\n # src\/Ptrio\/MessageBundle\/Resources\/config\/services.yaml\n ptrio_message.device_controller:\n class: 'App\\Ptrio\\MessageBundle\\Controller\\DeviceController'\n arguments:\n - '@ptrio_message.device_manager'\n - '@form.factory'\n tags:\n - name: controller.service_arguments \n```\n\nA `ptrio_message.device_controller` service has to be tagged as `controller.service_arguments`.\n\n##### Route configuration\n\nFor a controller methods to be accessible via HTTP protocol,<br> corresponding routes have to be defined.\n\nCreate a file called `routes.yaml` in a `src\/Ptrio\/MessageBundle\/Resources\/config` directory.\n\n```\n# src\/Ptrio\/MessageBundle\/Resources\/config\/routes.yaml\ndevice:\n type: rest\n prefix: \/\n resource: 'ptrio_message.device_controller'\n```\n\nIn order for the route configuration to be loaded,<br> it has to be referenced in a `config\/routes.yaml` file.\n\n```\nptrio_message:\n type: rest\n prefix: \/api\/v1\n resource: '@PtrioMessageBundle\/Resources\/config\/routes.yaml'\n```\n\nAn API *endpoint* for a `MessageBundle` related routes will be available at `http:\/\/yourdomain.com\/api\/v1`.\n\n> Note: Setting a type to `rest` is necessary for the routes are to be loaded by a correct `RouteLoader` object.\n\nRoutes defined by an application can be listed with a `php bin\/console debug:router` command.\n\n```\n --------------- -------- -------- ------ ---------------------------------------- \n Name Method Scheme Host Path \n --------------- -------- -------- ------ ---------------------------------------- \n get_device GET ANY ANY \/api\/v1\/devices\/ deviceName . _format \n post_device POST ANY ANY \/api\/v1\/devices. _format \n delete_device DELETE ANY ANY \/api\/v1\/devices\/ deviceName . _format \n --------------- -------- -------- ------ ---------------------------------------- \n```\n\n#### Examples\n\nThe functionalities created in this tutorial allow for interacting with a web server application through a large variety of client interfaces. In this particular case,<br> tests will be performed with Curl.\n\nThere is one more thing to do before we can jump into examples part.\n\nGetter methods `Device::getName()` and `Device::getToken()` defined in a `Device` entity class are supossed to only return string values. However,<br> during the process of adding a new device entity object,<br> a `FormFactory` object calls the getters while they are not yet assigned any values,<br> and thence `null` values are returned. In this situation an error will be raised and code execution interrupted since the definition and return types are different from each other. To fix the problem,<br> `Device::$name` and `Device::$token` properties should have empty string values assigned as defaults.\n\n```\n \/\/ other class declarations\n protected $name = ''; \/\/ assigned an empty value\n \/\/ other class declarations\n protected $token = ''; \/\/ assigned an empty value\n```\n\n##### Adding a new device\n\n###### JSON\n\n```\n$ curl -H 'Content-Type: application\/json' -X POST -d ' \"name\":\"test-device\",<br>\"token\":\"example-token\" ' -w 'Response code: % http_code \\n' http:\/\/localhost\/api\/v1\/devices`\n```\n\n###### XML\n\n```\n$ curl -H 'Content-Type: application\/xml' -X POST -d '<xml><name>test-device<\/name><token>example-token<\/token><\/xml>' -w 'Response code: % http_code \\n' http:\/\/localhost\/api\/v1\/devices\n```\n\nA HTTP response code should be returned after executing the commands presented above.\n\n> Note: Replace `test-device`,<br> `example-token` and `http:\/\/localhost` with adequate values.\n\n\n##### Removing a device\n\n```\n$ curl -w 'Response code: % http_code \\n' -X DELETE http:\/\/localhost\/api\/v1\/devices\/test-device\n```\n\nA HTTP response code should be returned after executing the command presented above.\n\n> Note: Replace `test-device` with a device name that you want to remove.\n\n#### Displaying device details\n\n##### JSON\n\n```\ncurl -H 'Accept: application\/json' http:\/\/localhost\/api\/v1\/devices\/iphone-piotr\n```\n\nIf a device was found,<br> a JSON serialized device object will be returned as a response.\n\n```\n \"id\":1,<br>\"name\":\"iphone-piotr\",<br>\"token\":\"d1KeQHgkIoo:APA91bGBG7vg...\" \n```\n\n##### XML\n\n```\ncurl -H 'Accept: application\/xml' http:\/\/localhost\/api\/v1\/devices\/iphone-piotr\n```\n\nIf a device was found,<br> a XML serialized device object will be returned as a response.\n\n```\n<?xml version=\"1.0\"?>\n<response><id>1<\/id><name>iphone-piotr<\/name><token>d1KeQHgkIoo:APA91bGBG7vg...<\/token><\/response>\n```\n\n### Curriculum \n* [Web Development with Symfony: An application capable of dispatching push notifications to mobile devices via FCM [part 2 (https:\/\/steemit.com\/utopian-io\/@piotr42\/web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm-part-2)\n* [Web Development with Symfony: An application capable of dispatching push notifications to mobile devices via FCM [part 1 (https:\/\/steemit.com\/utopian-io\/@piotr42\/web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm)\n\n<br \/><hr\/><em>Posted on <a href=\"https:\/\/utopian.io\/utopian-io\/@piotr42\/web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm-part-3\">Utopian.io - Rewarding Open Source Contributors<\/a><\/em><hr\/>",<br>"json_metadata":" \"community\":\"utopian\",<br>\"app\":\"utopian\/1.0.0\",<br>\"format\":\"markdown\",<br>\"repository\"<br>\"id\":458058,<br>\"name\":\"symfony\",<br>\"full_name\":\"symfony\/symfony\",<br>\"html_url\":\"https:\/\/github.com\/symfony\/symfony\",<br>\"fork\":false,<br>\"owner\"<br>\"login\":\"symfony\" ,<br>\"pullRequests\":[ ,<br>\"platform\":\"github\",<br>\"type\":\"tutorials\",<br>\"tags\":[\"utopian-io\",<br>\"utopian-io\",<br>\"tutorial\",<br>\"php\",<br>\"symfony\" ,<br>\"users\":[\"var\",<br>\"param\",<br>\"Assert\",<br>\"ORM\",<br>\"return\",<br>\"ptrio\",<br>\"form.factory\",<br>\"PtrioMessageBundle\",<br>\"piotr42\" ,<br>\"links\":[\"https:\/\/res.cloudinary.com\/hpiynhbhq\/image\/upload\/v1521590581\/enzgzrrro89cmwoptyf8.png\",<br>\"https:\/\/github.com\/ptrio42\/message-bundle-demo-part-2\",<br>\"https:\/\/symfony.com\",<br>\"https:\/\/steemit.com\/utopian-io\/@piotr42\/web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm-part-2\",<br>\"https:\/\/steemit.com\/utopian-io\/@piotr42\/web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm\" ,<br>\"image\":[\"https:\/\/res.cloudinary.com\/hpiynhbhq\/image\/upload\/v1521590581\/enzgzrrro89cmwoptyf8.png\" "
comment_options
"author":"piotr42",
"permlink":"web-development-with-symfony-an-application-capable-of-dispatching-push-notifications-to-mobile-devices-via-fcm-part-3",
"max_accepted_payout":"1000000.000 SBD",
"percent_steem_dollars":10000,
"allow_votes":true,
"allow_curation_rewards":true,
"extensions":[[0,
"beneficiaries":[ "account":"utopian.pay",
"weight":2500
* The API used to generate this page is provided by @steemchiller.