To start writing RestFull API in symfony we will need bundles:
"friendsofsymfony/oauth-server-bundle": "1.6",
"friendsofsymfony/rest-bundle": "^2.3",
"friendsofsymfony/user-bundle": "^2.1",
"jms/serializer-bundle": "^2.4",
"nelmio/api-doc-bundle": "^2.13",
"sensio/framework-extra-bundle": "^5.2",
"symfony/apache-pack": "^1.0",
"symfony/console": "^4.1",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.1",
"symfony/lts": "^4@dev",
"symfony/maker-bundle": "^1.5",
"symfony/orm-pack": "^1.0",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/templating": "^4.1",
"symfony/yaml": "^4.1"So let’s get started:
Firstly we create project in symfony. To do this we will need composer you can download and install from page:
https://getcomposer.org/download/We can create skeleton project in symfony using command prompt:
create-project symfony/skeleton restBut more simple is using this composer.json create project running command composer install.
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"friendsofsymfony/oauth-server-bundle": "1.6",
"friendsofsymfony/rest-bundle": "^2.3",
"friendsofsymfony/user-bundle": "^2.1",
"jms/serializer-bundle": "^2.4",
"nelmio/api-doc-bundle": "^2.13",
"sensio/framework-extra-bundle": "^5.2",
"symfony/apache-pack": "^1.0",
"symfony/console": "^4.1",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.1",
"symfony/lts": "^4@dev",
"symfony/maker-bundle": "^1.5",
"symfony/orm-pack": "^1.0",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/templating": "^4.1",
"symfony/yaml": "^4.1"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0",
"symfony/dotenv": "^4.1",
"symfony/var-dumper": "^4.1"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false
}
}
}
Then we got a question Do you want to execute this recipe? Hit a. Because our config files isn't propertly filled. To work this propertly we need to create some config files under the config/packages folder.
fos_user.yml
fos_user:
db_driver: orm # other valid values are 'mongodb' and 'couchdb'
firewall_name: main
user_class: App\Entity\User
from_email:
address: '[email protected]' # for example your email
sender_name: '[email protected]' # for example your emailfos_oauth_user.yml
fos_oauth_server:
db_driver: orm
client_class: App\Entity\Client
access_token_class: App\Entity\AccessToken
refresh_token_class: App\Entity\RefreshToken
auth_code_class: App\Entity\AuthCode
service:
user_provider: fos_user.user_provider.username
options:
access_token_lifetime: 28800
template:
engine: twigThese class we use to store authcode and access tokens to authorise to our API.
We also need to as to the framework.yml templating:
framework:
templating:
engines: ['twig']And also nelmio_api_doc.yaml
nelmio_api_doc:
sandbox:
enabled: true
authentication:
delivery: http
type: bearer
body_format:
formats: [ json, form ] # array of enabled body formats,
default_format: json
swagger:
api_base_path: /api
swagger_version: '1.2'
api_version: '0.1'
info:
title: Rest
description: 'Rest app in symfony 4'Let’s create fos_oauth_server enity classes.
Offtop: Because we use Symfony 4 and I figured the problem with key length in default mariadb configurations there is two options to resolve very frustrating error. Charset utf8mb4 cause that error when we doctrine try to create key.
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
One without changing any configuration on the server change config file : Doctrine.yaml and use setting as follows(Simply we will simple utf8 instead utf8mb4):
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8
default_table_options:
charset: utf8
collate: utf8_unicode_ciAnd second update the database engine on server :)
AccessToken class:
<?php
namespace App\Entity;
use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="oauth2_access_tokens")
* @ORM\Entity
*/
class AccessToken extends BaseAccessToken
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var Client
*
* @ORM\ManyToOne(targetEntity="Client")
* @ORM\JoinColumn(nullable=false)
*/
protected $client;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="User")
*/
protected $user;
}AuthCode class
<?php
namespace App\Entity;
use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="oauth2_auth_codes")
* @ORM\Entity
*/
class AuthCode extends BaseAuthCode
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var Client
*
* @ORM\ManyToOne(targetEntity="Client")
* @ORM\JoinColumn(nullable=false)
*/
protected $client;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="User")
*/
protected $user;
}Client class
<?php
namespace App\Entity;
use FOS\OAuthServerBundle\Entity\Client as BaseClient;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="oauth2_clients")
* @ORM\Entity
*/
class Client extends BaseClient
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="type", type="string", length=150, nullable=true)
*/
protected $type;
/**
* @param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}
}Refresh token class
<?php
namespace App\Entity;
use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="oauth2_refresh_tokens")
* @ORM\Entity
*/
class RefreshToken extends BaseRefreshToken
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var Client
*
* @ORM\ManyToOne(targetEntity="Client")
* @ORM\JoinColumn(nullable=false)
*/
protected $client;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="User")
*/
protected $user;
}These classes are simple. As you can see we also need create User class.
<?php
namespace App\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as Serializer;
/**
* User
*
* @ORM\Table(name="user", indexes={
* @ORM\Index(name="search_idx_username", columns={"username"}),
* @ORM\Index(name="search_idx_email", columns={"email"}),
* })
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
*
* @UniqueEntity(fields={"email"}, message="EMAIL_IS_ALREADY_IN_USE")
*
* @Serializer\ExclusionPolicy("all")
*/
class User extends BaseUser
{
const ROLE_SUPER_ADMIN = "ROLE_SUPER_ADMIN";
const ROLE_ADMIN = "ROLE_ADMIN";
const ROLE_USER = "ROLE_USER";
/**
* To validate supported roles
*
* @var array
*/
static public $ROLES_SUPPORTED = array(
self::ROLE_SUPER_ADMIN => self::ROLE_SUPER_ADMIN,
self::ROLE_ADMIN => self::ROLE_ADMIN,
self::ROLE_USER => self::ROLE_USER,
);
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @Assert\NotBlank(message="FIELD_CAN_NOT_BE_EMPTY")
* @Assert\Email(
* message = "INCORRECT_EMAIL_ADDRESS",
* checkMX = true
* )
*/
protected $email;
/**
* @var string
*
* @ORM\Column(name="first_name", type="string", length=100, nullable=true)
*
* @Assert\Length(
* min = 1,
* max = 100,
* minMessage = "FIELD_LENGTH_TOO_SHORT",
* maxMessage = "FIELD_LENGTH_TOO_LONG"
* )
*/
private $firstName;
/**
* @var string
*
* @ORM\Column(name="last_name", type="string", length=100, nullable=true)
*
* @Assert\Length(
* min = 1,
* max = 100,
* minMessage = "FIELD_LENGTH_TOO_SHORT",
* maxMessage = "FIELD_LENGTH_TOO_LONG"
* )
*/
private $lastName;
/**
* @var boolean
*
* @ORM\Column(name="deleted", type="boolean")
*
* @Assert\Type(
* type="bool",
* message="FIELD_MUST_BE_BOOLEAN_TYPE"
* )
*/
private $deleted;
/**
* User constructor.
*/
public function __construct()
{
parent::__construct();
$this->deleted = false;
}
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* @param string $firstName
*
* @return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* @return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set lastName
*
* @param string $lastName
*
* @return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* @return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Set deleted
*
* @param boolean $deleted
*
* @return User
*/
public function setDeleted($deleted)
{
$this->deleted = $deleted;
return $this;
}
/**
* Get deleted
*
* @return boolean
*/
public function getDeleted()
{
return $this->deleted;
}
}And UserRepositiory it can be empty :)
Let’s create some datafixtures
Run command : php bin/console make:fixtures
ClientDataHere we create Oauth2ClientData.
Edit file what we just created.
<?php
namespace App\DataFixtures;
use App\Entity\Client;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
class ClientData extends Fixture
{
public function load(ObjectManager $manager)
{
$oauth2Client = new Client();
$oauth2Client->setId(1);
$oauth2Client->setRandomId('5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s');
$oauth2Client->setRedirectUris(array());
$oauth2Client->setSecret('sdgggskokererg4232404gc4csdgfdsgf8s8ck5s');
$oauth2Client->setAllowedGrantTypes(array('password', 'refresh_token'));
$manager->persist($oauth2Client);
/** @var ClassMetadata $metadata */
$metadata = $manager->getClassMetadata(get_class($oauth2Client));
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
$metadata->setIdGenerator(new AssignedGenerator());
$manager->flush();
}
}And another one with user.
php bin/console make:fixtures
UserDataAnd like in previews one edit file that we just created.
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use FOS\UserBundle\Doctrine\UserManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class UserData extends Fixture implements ContainerAwareInterface
{
const USER_MANAGER = 'fos_user.user_manager';
/**
* @var ContainerInterface
*/
private $container;
/**
* @var UserManager
*/
private $userManager;
/**
* @param ContainerInterface|null $container
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
$this->userManager = $this->container->get(static::USER_MANAGER);
}
public function load(ObjectManager $manager)
{
/** @var User $user */
$user = $this->userManager->createUser();
$user
->setFirstName("Admin")
->setLastName("admin")
->setEnabled(true)
->setRoles(array(User::ROLE_SUPER_ADMIN))
->setUsername("admin")
->setPlainPassword("admin")
->setEmail("[email protected]")
;
$manager->persist($user);
$manager->flush();
}
}Also we need to define password encoder for entity User we can do it in security.yaml by adding these lines
encoders:
FOS\UserBundle\Model\UserInterface: sha512After this run command
php bin/console doctrine:fixtures:loadto update database with datafixtures. We should also define routes routes.yaml
fos_oauth_server_token:
resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml"
fos_oauth_server_authorize:
resource: "@FOSOAuthServerBundle/Resources/config/routing/authorize.xml"After this using f.e. built in in PHPStorm HTTPClient we can request a token from our OauthServer
Create POST query to path: /oauth/v2/token giving following parameters:
grant_type-password
client_id=1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40g #notice that we add id_ before client_id which we create using datafixtures
client_secret=sdgggskokererg4232404gc4csdgfdsgf8s8ck5w
username=admin
password=adminYou should get responce like this:
{"access_token":"Zjg2NGFiMWQ4YzMwOGRiMjBkZTE3NzQ0MDdiNGUyYzBhNDFhZDFhN2JmNGNjYzM4YWVlYjYyMjdkODA3OTk3OQ","expires_in":28800,"token_type":"bearer","scope":null,"refresh_token":"NzcxNWZjNTY3ZmFiY2QzMTMyZWE5NmZiOTFlZmJiODg2OTk0ZDA5YmZmODM2ODYxODcxOGI5ZmJmNWIyODg1MA"}
If you get it you probably configured all correctly.