Blog Archives
Sign-in with Twitter in a Silex application.
I’ve working in a pet-project with Silex and I wanted to perform a Sign-in with Twitter. Implementing Sign in with Twitter is pretty straightforward and it’s also well explained in the Twitter’s developers site. Now we only need to implement those HTTP client requests within PHP. We can create the REST client with curl but nowadays I prefer to use the great library called Guzzle to perform those kind of opperations. So let’s start.
The idea is to create something reusable. I don’t want to spend too much time including the Sign-in with Twitter in my proyects, so my first idea was to create a class with all the needed code and mount this class as group of Silex controllers (as it’s defined here). I also want to keep the class as standard as possible and avoiding the usage of any other external dependencies (except Guzzle)..
Imagine a simple Silex application:
<?php
// www/index.php
include __DIR__ . "/../vendor/autoload.php";
$app = new Silex\Application();
$app->get('/', function () {
return 'Hello';
});
$app->run();
Now I want to use a Sign-in with Twitter, so I will change the application to:
<?php
include __DIR__ . "/../vendor/autoload.php";
$app = new Silex\Application();
$app->register(new Silex\Provider\SessionServiceProvider());
$consumerKey = "***";
$consumerSecret = "***";
$twitterLoggin = new SilexTwitterLogin($app, 'twitter');
$twitterLoggin->setConsumerKey($consumerKey);
$twitterLoggin->setConsumerSecret($consumerSecret);
$twitterLoggin->registerOnLoggin(function () use ($app, $twitterLoggin) {
$app['session']->set($twitterLoggin->getSessionId(), [
'user_id' => $twitterLoggin->getUserId(),
'screen_name' => $twitterLoggin->getScreenName(),
'oauth_token' => $twitterLoggin->getOauthToken(),
'oauth_token_secret' => $twitterLoggin->getOauthTokenSecret()
]);
});
$twitterLoggin->mountOn('/login', function () {
return '<a href="/login/requestToken">login</a>';
});
$app->get('/', function () use ($app){
return 'Hello ' . $app['session']->get('twitter')['screen_name'];
});
$app->run();
The application will redirects all requests (without the correct session) to the route “/login”. The login page has a simple link to the route: “/login/requestToken” (we can create a fancy template with Twig if we want, indeed). This route redirects the request to Twitter’s login page and after a successful login it will redirects back to the route that we have defined within our Twitter application. The library assumes that this callback’s url is “/login/callbackUrl”. All this default routes can be defined by the user using the proper setters of the class.
When the sign-in is finished the application will trigger the callback defined in registerOnLoggin function and will redirects to the route “/”. This route (called internally “redirectOnSuccess”) is also customizable with a setter.
And that’s all. Library available at github and packagist
{
"require": {
"gonzalo123/silex-twitter-login": "dev-master"
}
}
Scaling Silex applications (part II). Using RouteCollection
In the post Scaling Silex applications I wanted to organize a one Silex application. In one comment Igor Wiedler recommended us to use RouteCollections instead of define the routes with a Symfony’s Dependency Injection Container. Because of that I started to hack a little bit about it and here I show you my outcomes:
I want to build an imaginary application with silex. This application has also one Api and one little blog. I want to organize those parts. Our index.php file
<?php
// www/index.php
require_once __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\RouteCollection;
use Silex\Application;
$app = new Application();
$app['routes'] = $app->extend('routes', function (RouteCollection $routes, Application $app) {
$loader = new YamlFileLoader(new FileLocator(__DIR__ . '/../config'));
$collection = $loader->load('routes.yml');
$routes->addCollection($collection);
return $routes;
});
$app->run();
Now our routes.yml file:
# config/routes.yml
home:
path: /
defaults: { _controller: 'Gonzalo123\AppController::homeAction' }
hello:
path: /hello/{name}
defaults: { _controller: 'Gonzalo123\AppController::helloAction' }
api:
prefix: /api
resource: api.yml
blog:
prefix: /blog
resource: blog.yml
As we can see we have separated the main routing file into different files: api.yml (for the Api) and blog.yml (for the blog)
# config/api.yml
api.list:
path: /list
defaults: { _controller: 'Gonzalo123\ApiController::listAction' }
# blog.yml
blog.home:
path: /
defaults: { _controller: 'Gonzalo123\BlogController::homeAction' }
And now we can create our controllers:
<?php
// lib/Gonzalo123/AppController.php
namespace Gonzalo123;
use Symfony\Component\HttpFoundation\Response;
use Silex\Application;
class AppController
{
public function homeAction()
{
return new Response("AppController::homeAction");
}
public function helloAction(Application $app, $name)
{
return new Response("Hello" . $app->escape($name));
}
}
<?php
// lib/Gonzalo123/ApiController.php
namespace Gonzalo123;
use Symfony\Component\HttpFoundation\Response;
class ApiController
{
public function listAction()
{
return new Response("AppController::listAction");
}
}
<?php
// lib/Gonzalo123/BlogController.php
namespace Gonzalo123;
use Symfony\Component\HttpFoundation\Response;
class BlogController
{
public function homeAction()
{
return new Response("BlogController::homeAction");
}
}
And that’s all. Here also the needed dependencies within our composer.json file
{
"require":{
"silex/silex":"1.0.*@dev",
"symfony/yaml":"v2.2.0",
"symfony/config":"v2.2.0"
},
"autoload":{
"psr-0":{
"":"lib/"
}
}
}
source code at github.
Scaling Silex applications
In my humble opinion Silex is great. It’s perfect to create prototypes, but when our application grows up it turns into a mess. That was what I thought until the last month, when I attended to a great talk about Silex with Javier Eguiluz. OK. Scaling Silex it’s not the same than with a Symfony application, but it’s possible.
It’s pretty straightforward to create a Silex application with composer:
{
"require": {
"silex/silex": "1.0.*"
},
"minimum-stability": "dev"
}
But there’s a better way. We can use the Fabien Potencier’s skeleton. With this skeleton we can organize our code better.
We also can use classes as controllers instead of using a closure with all the code. Igor Wiedler has a great post about this. You can read it here.
Today I’m playing with Silex and I want to show you something. Let’s start:
Probably you know that I’m a big fan of Symfony’s Dependency Injection Container (you can read about it here and here), but Silex uses Pimple. In fact the Silex application extends Pimple Class. My idea is the following one:
In the Igor’s post we can see how to use things like that:
$app->match('/video/{id}', 'Gonzalo123\ApiController::indexAction')->method('GET')->bind('video_info');
My idea is to store this information within a Service Container (we will use Symfony’s DIC). For example here we can see our routes.yml:
routes:
video_info:
pattern: /video/{id}
controller: Gonzalo123\ApiController::initAction
requirements:
_method: GET
As we can see we need to implement one Extension for the alias “routes”. We only will implement the needed functions for YAML files in this example.
<?php
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class SilexRouteExtension implements ExtensionInterface
{
/**
* Loads a specific configuration.
*
* @param array $config An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @throws InvalidArgumentException When provided tag is not defined in this extension
*
* @api
*/
public function load(array $config, ContainerBuilder $container)
{
}
/**
* Returns the namespace to be used for this extension (XML namespace).
*
* @return string The XML namespace
*
* @api
*/
public function getNamespace()
{
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*
* @api
*/
public function getXsdValidationBasePath()
{
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*
* @api
*/
public function getAlias()
{
return "routes";
}
}
And now we only need to prepare the DIC. According to Fabien’s recommendation in his Silex skeleton, we only need to change the src/controllers.php
<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
// Set up container
$container = new ContainerBuilder();
$container->registerExtension(new SilexRouteExtension);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config/'));
// load configuration
$loader->load('routes.yml');
$app['container'] = $container;
$app->mount('/api', include 'controllers/myApp.php');
$container->compile();
$app->error(function (\Exception $e, $code) use ($app) {
if ($app['debug']) {
return;
}
$page = 404 == $code ? '404.html' : '500.html';
return new Response($app['twig']->render($page, array('code' => $code)), $code);
});
and now we define the config/routes.yml
routes:
video_info:
pattern: /video/{videoId}
controller: Gonzalo123\ApiController::initAction
requirements:
_method: GET
And finally the magic in our controllers/myApp.php:
<?php
$myApp = $app['controllers_factory'];
foreach ($container->getExtensionConfig('routes')[0] as $name => $route) {
$controller = $myApp->match($route['pattern'], $route['controller']);
$controller->method($route['requirements']['_method']);
$controller->bind($name);
}
return $myApp;
The class for this example is: src/Gonzalo123/ApiController.php
<?php
namespace Gonzalo123;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiController
{
public function initAction(Request $request, Application $app)
{
return new JsonResponse(array(1, 1, $request->get('id')));
}
}
As you can see the idea is to use classes as controllers, define them within the service container and build the silex needed code iterating over the configuration. What do you think?
Building a Silex application from one Behat/Gherkin feature file
Last days I’ve playing with Behat. Behat is a behavior driven development (BDD) framework based on Ruby’s Cucumber. Basically with Behat we defenie features within one feature file. I’m not going to crate a Behat tutorial (you can read more about Behat here). Behat use Gherkin to write the features files. When I was playing with Behat I had one idea. The idea is simple: Can we use Gherking to build a Silex application?. It was a good excuse to study Gherking, indeed
.
Here comes the feature file:
Feature: application API
Scenario: List users
Given url "/api/users/list.json"
And request method is "GET"
Then instance "\Api\Users"
And execute function "listUsers"
And format output into json
Scenario: Get user info
Given url "/api/user/{userName}.json"
And request method is "GET"
Then instance "\Api\User"
And execute function "info"
And format output into json
Scenario: Update user information
Given url "/api/user/{userName}.json"
And request method is "POST"
Then instance "\Api\User"
And execute function "update"
And format output into json
Our API use this simple library:
<?php
namespace Api;
use Symfony\Component\HttpFoundation\Request;
class User
{
private $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function info()
{
switch ($this->request->get('userName')) {
case 'gonzalo':
return array('name' => 'Gonzalo', 'surname' => 'Ayuso');
case 'peter':
return array('name' => 'Peter', 'surname' => 'Parker');
}
}
public function update()
{
return array('infoUpdated');
}
}
<?php
namespace Api;
use Symfony\Component\HttpFoundation\Request;
class Users
{
public function listUsers()
{
return array('gonzalo', 'peter');
}
}
The idea is simple. Parse the feature file with behat/gherkin component and create a silex application. And here comes the “magic”. This is a simple working prototype, just an experiment for a rainy sunday.
<?php
include __DIR__ . '/../vendor/autoload.php';
define(FEATURE_PATH, __DIR__ . '/api.feature');
use Behat\Gherkin\Lexer,
Behat\Gherkin\Parser,
Behat\Gherkin\Keywords\ArrayKeywords,
Behat\Gherkin\Node\FeatureNode,
Behat\Gherkin\Node\ScenarioNode,
Symfony\Component\HttpFoundation\Request,
Silex\Application;
$keywords = new ArrayKeywords([
'en' => [
'feature' => 'Feature',
'background' => 'Background',
'scenario' => 'Scenario',
'scenario_outline' => 'Scenario Outline',
'examples' => 'Examples',
'given' => 'Given',
'when' => 'When',
'then' => 'Then',
'and' => 'And',
'but' => 'But'
],
]);
function getMatch($subject, $pattern) {
preg_match($pattern, $subject, $matches);
return isset($matches[1]) ? $matches[1] : NULL;
}
$app = new Application();
function getScenarioConf($scenario) {
$silexConfItem = [];
/** @var $scenario ScenarioNode */
foreach ($scenario->getSteps() as $step) {
$route = getMatch($step->getText(), '/^url "([^"]*)"$/');
if (!is_null($route)) {
$silexConfItem['route'] = $route;
}
$requestMethod = getMatch($step->getText(), '/^request method is "([^"]*)"$/');
if (!is_null($requestMethod)) {
$silexConfItem['requestMethod'] = strtoupper($requestMethod);
}
$instance = getMatch($step->getText(), '/^instance "([^"]*)"$/');
if (!is_null($instance)) {
$silexConfItem['className'] = $instance;
}
$method = getMatch($step->getText(), '/^execute function "([^"]*)"$/');
if (!is_null($method)) {
$silexConfItem['method'] = $method;
}
if ($step->getText() == 'format output into json') {
$silexConfItem['jsonEncode'] = TRUE;
}
}
return $silexConfItem;
}
/** @var $features FeatureNode */
$features = (new Parser(new Lexer($keywords)))->parse(file_get_contents(FEATURE_PATH), FEATURE_PATH);
foreach ($features->getScenarios() as $scenario) {
$silexConfItem = getScenarioConf($scenario);
$app->match($silexConfItem['route'], function (Request $request) use ($app, $silexConfItem) {
function getConstructorParams($rClass, $request) {
$parameters =[];
foreach ($rClass->getMethod('__construct')->getParameters() as $parameter) {
if ('Symfony\Component\HttpFoundation\Request' == $parameter->getClass()->name) {
$parameters[$parameter->getName()] = $request;
}
}
return $parameters;
}
$rClass = new ReflectionClass($silexConfItem['className']);
$obj = ($rClass->hasMethod('__construct')) ?
$rClass->newInstanceArgs(getConstructorParams($rClass, $request)) :
new $silexConfItem['className'];
$output = $obj->{$silexConfItem['method']}();
return ($silexConfItem['jsonEncode'] === TRUE) ? $app->json($output, 200) : $output;
}
)->method($silexConfItem['requestMethod']);
}
$app->run();
You can see the source code in github. What do you think?























