KNP Labs RAD Components
Rapid Application development with Symfony
Rapid Application development with Symfony
Bring enhanced development experience to your Symfony projects and get rid of these tiresome tasks you feel doing over and over. Spend your time elsewhere!
Straight to the point usage.
No hidden complexity. Fast results.
Minimal configuration.
Components are plug & play.
Does not pollute your code. Breaks nothing.
Use it, or not :)
Each component is simple and stupid.
It does one thing and it does it well.
It is so green.
Follows the SOLID principles.
PSR compliant.
Resolve resources from the routing
Resource Resolver is a way to resolve request attributes and get directly the value. It is very similar to Param Converters, but however far more flexible and powerful.
# routing.yml
article_index:
path: /articles/
defaults:
# ...
article_show:
path: /articles/{id}
defaults:
# ...
<?php
namespace App\Controller;
class ArticlesController
{
public function indexAction()
{
$articles = $this->getDoctrine()
->getManager()
->getRepository('App:Article')
->findAll()
;
// ...
}
public function showAction($articleId)
{
$article = $this->getDoctrine()
->getManager()
->getRepository('App:Article')
->find($articleId)
;
// ...
}
}
# routing.yml
article_index:
path: /articles/
defaults:
# ...
_resources:
articles:
service: my.article.repository
method: findAll
article_show:
path: /articles/{id}
defaults:
# ...
_resources:
article:
service: my.article.repository
method: find
arguments: [$id]
<?php
namespace App\Controller;
use App\Entity\Article;
class ArticlesController
{
public function indexAction(array $articles)
{
// ...
}
public function showAction(Article $article)
{
// ...
}
}
Perform security check at routing level instead of in the controller.
Through simple configuration in the routing, this component allows to grant access according resolved resources. And combined with the rad-resource-resolver component, it also allows to set conditions on request attributes.
<?php
namespace App\Controller;
class ArticleController extends Controller
{
public function indexAction()
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY', null, 'Unable to access this page!');
// ...
}
public function detailsAction($articleId)
{
$article = $this->getDoctrine()
->getManager()
->getRepository('App:Article')
->find($articleId)
;
$owners = $this->getDoctrine()
->getManager()
->getRepository('App:Owner')
->findAllForArticle($article)
;
if (!$this->isGranted(['IS_MEMBER', 'ANOTHER_ROLE'], $article) ||
!$this->isGranted('IS_ADMIN', $owners)
) {
throw $this->createAccessDeniedException();
}
// ...
}
}
# routing.yml
article_index:
path: /articles/
defaults:
// ...
_security:
- roles: IS_AUTHENTICATED_FULLY
article_details:
path: /articles/{id}/owners/
defaults:
// ...
_resources:
article:
// ...
owners:
// ...
_security:
-
roles: [IS_MEMBER, ANOTHER_ROLE]
subject: article
-
roles: IS_ADMIN
subject: owners
Doctrine to Symfony events redispatcher
It is able to access to your doctrine events from Symfony DependencyInjection component easily.
services:
app.event_listener.some_entity_listener:
class: App\EventListener\SomeEntityListener
tags:
- { name: doctrine.event_listener, event: pre_persist, method: prePersist }
<?php
namespace App\EventListener;
use App\Entity\SomeEntity;
use Doctrine\ORM\Event\LifecycleEventArgs;
class SomeEntityListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (false === $entity instanceof SomeEntity) {
return;
}
// Some stuff
}
}
services:
app.event_listener.some_entity_listener:
class: App\EventListener\SomeEntityListener
tags:
- { name: kernel.event_listener, event: app.entity.some_entity.pre_persist, method: prePersist }
<?php
namespace App\EventListener;
use Knp\Rad\DoctrineEvent\Event\DoctrineEvent;
class SomeEntityListener
{
public function prePersist(DoctrineEvent $event)
{
$entity = $event->getEntity();
// Some stuff
}
}
Forget about those many common services declarations
Don't bother anymore registrate your repositories, form extensions, security voters or twig extensions as services... Just small configuration, stored files under specific sub-namespace, and all repositories, extensions and voters are auto-registred.
# services.yml
# Hurtful declarations...
app.form.extension.my_form_extension:
class: App\Form\Extension\MyFormExtension
tags:
- { name: form.type_extension, extended_type: SomeType }
app.repository.some_entity_repository:
class: App\Repository\SomeEntityRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments:
- App\Entity\SomeEntityRepository
app.security.some_security_voter
class: App\Security\SomeSecurityVoter
tags:
- { name: security.voter }
app.twig.some_extension:
class: App\Twig\SomeExtension
tags:
- { name: twig.extension }
# config.yml
# Just activate the lines you need
knp_rad_auto_registration:
enable:
doctrine: ~
doctrine_mongodb: ~
doctrine_couchdb: ~
form_type_extension: ~
security_voter: ~
twig_extension: ~
Allows you to use nelmio/alice library when loading your fixtures.
<?php
namespace App\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use App\Entity\User;
class LoadUserData implements FixtureInterface
{
public function load(ObjectManager $manager)
{
for ($i = 0; $i < 10; $i++) {
$timestamp = mt_rand(1, time());
$randomDate = new DateTime('@'.$timestamp);
$user = new User();
$user->setUsername($this->generateUsername());
$user->setBirthday($randomDate);
$user->setEmail($this->generateEmail());
$manager->persist($user);
}
$manager->flush();
}
private function generateUsername()
{
$usernames = ['artium', 'nondum', 'codicem', 'primae', 'firmato'];
return array_rand($usernames);
}
private function generateEmail()
{
$mails = ['mittendus@mail.com', 'fumo@mail.com', 'lanuginis@mail.com', 'spectante@mail.com', 'duci@mail.com'];
return array_rand($mails);
}
}
#Symfony2
$ php app/console doctrine:fixtures:load
#Symfony3
$ php bin/console doctrine:fixtures:load
# Resources/fixtures/orm/users.yml
App\Entity\User:
user_{1..10}:
username: <username()>
birthday: <dateTime()>
email: <email()>
#Symfony2
$ php app/console rad:fixtures:load
#Symfony3
$ php bin/console rad:fixtures:load
A simple way to handle password encryption and salt generation.
Before your entity is inserted or updated into your database, according traits you choose to use, the salt, plain password or password will be automaticly generated. And all of this is done through three interfaces and three listeners.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
private $id;
private $password;
// ...
public function __construct()
{
$this->salt = md5(uniqid(null, true));
}
// ...
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getSalt()
{
return $this->salt;
}
public function eraseCredentials()
{
}
}
<?php
namespace App\Controller;
use App\Controller\Controller;
use App\Entity\User;
class UserController extends Controller
{
public function newUserAction($email,$plainPassword)
{
$user = new User();
$salt = $user->getSalt();
$factory = $this->container->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($plainPassword, $salt);
$user->setPassword($password);
$user->setEmail($email);
// ...
}
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\Rad\User\HasPassword;
use Knp\Rad\User\HasSalt;
class User implements HasPassword, HasSalt
{
use HasPassword\HasPassword;
use HasSalt\HasSalt;
private $id;
private $password;
private $salt;
// ...
}
<?php
namespace App\Controller;
use App\Controller\Controller;
use App\Entity\User;
class UserController extends Controller
{
public function newUserAction($email,$plainPassword)
{
$user = new User();
$user->setPlainPassword($plainPassword);
$user->setEmail($email);
// ...
}
}
Don't bother with render functions anymore.
Following the Symfony conventions, the View Renderer component will try to render the appropriate twig template, if no Response object was returned from the Controller. You can still of course return a response when you want.
<?php
namespace App\Controller;
class BasicController
{
public function someAction($var)
{
return $this->render(
'App:Basic:some.html.twig',
['var' => $var]
);
}
}
<?php
namespace App\Controller;
class BasicController
{
public function someAction($var)
{
// Will look for App\Resources\views\Basic\some.html.twig
return ['var' => $var];
}
}
A lightweight domain event pattern implementation for Doctrine2
The raise method allows you to trigger any event in your entity which will be transformed to a Knp\Rad\DomainEvent\Event object and dispatched once the entity has been flushed.
<?php
namespace App\Entity;
class MyEntity
{
private $param;
public function setParam($param)
{
$this->param = $param;
return $this;
}
}
<?php
namespace App\EventListener;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use App\Entity\MyEntity;
class MyEventListener
{
public function preUpdate(PreUpdateEventArgs $event)
{
$entity = $event->getEntity();
if (!$entity instanceof MyEntity) {
return;
}
if ($event->hasChangedField('param')) {
// Some stuff
}
}
}
# services.yml
app.event_listener.my_event_listener:
class: AppBundle\EventListener\MyEventListener
tags:
- { name: doctrine.event_listener, event: preUpdate }
<?php
namespace App\Entity;
use Knp\Rad\DomainEvent;
class MyEntity implements DomainEvent\Provider
{
use DomainEvent\ProviderTrait;
private $param;
public function setParam($param)
{
$this->param = $param;
$this->raise('myEventName', ['anyKey' => 'anyValue']);
return $this;
}
}
<?php
namespace App\EventListener;
use Knp\Rad\DomainEvent\Event;
class MyEventListener
{
public function onMyEventName(Event $event)
{
$anyValue = $event->anykey;
// Some stuff
}
}
# services.yml
app.event_listener.my_event_listener:
class: App\EventListener\MyEventListener
tags:
- { name: kernel.event_listener, event: myEventName, method: 'onMyEventName' }
This component will simply auto-complete needed route parameters with existing ones.
Just continue to use former url generation, nothing changes concerning the implementation. The only change is you don't repeat current route parameters anymore.
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class TestController
{
/**
* @Route("/shop/{shopId}", name="shop_show")
*/
public function showAction($shopId)
{
return $this->redirectToRoute('shop_products', [
'shopId' => $shopId,
]);
}
/**
* @Route("/shop/{shopId}/products", name="shop_products")
*/
public function productsAction($shopId)
{
// ...
}
}
<?php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class TestController
{
/**
* @Route("/shop/{shopId}", name="shop_show")
*/
public function showAction($shopId)
{
return $this->redirectToRoute('shop_products');
}
/**
* @Route("/shop/{shopId}/products", name="shop_products")
*/
public function productsAction($shopId)
{
// ...
}
}
Works also with url and path Twig functions or everywhere else you use the Symfony Router.