Thursday, August 15, 2013

Making a framework on top of symfony2

Summary from http://fabien.potencier.org/article/51/create-your-own-framework-on-top-of-the-symfony2-components-part-1 -12
 
 
$ mkdir framework
$ cd framework 
 
$ vi composer.json
   
{
    "require": {
        "symfony/class-loader": "2.1.*"
    }
}
 
:wq
 
$ wget http://getcomposer.org/composer.phar
$ # or
$ curl -O http://getcomposer.org/composer.phar
$ php composer.phar install
 
$ vi autoload.php
<?php
 
// framework/autoload.php
 
require_once __DIR__.'/vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php';
 
use Symfony\Component\ClassLoader\UniversalClassLoader;
 
$loader = new UniversalClassLoader();
$loader->register();
 
:wq
 
$ php autoload.php 
 
$ vi index.php  (error version if no get parameter supply)
<?php
// framework/index.php
$input = $_GET['name'];
printf('Hello %s', $input);
:wq
 
=> go to http://localhost and it will show hello

$ vi index.php  (unsecure version : Internet security issue, XSS (Cross-Site Scripting))
 
 <?php
 
// framework/index.php
 
$input = isset($_GET['name']) ? $_GET['name'] : 'World';
 
printf('Hello %s', $input);
:wq
 

$vi index.php (secure versinon)

<?php
$input = isset($_GET['name']) ? $_GET['name'] : 'World';
header('Content-Type: text/html; charset=utf-8');
printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'))
:wq 
 

==>test unit

vi test.php
<?php
 
// framework/test.php
 
class IndexTest extends \PHPUnit_Framework_TestCase
{
    public function testHello()
    {
        $_GET['name'] = 'Fabien';
 
        ob_start();
        include 'index.php';
        $content = ob_get_clean();
 
        $this->assertEquals('Hello Fabien', $content);
    }
}
:wq
 
=> At this point, if you are not convinced that security and testing are indeed
two very good reasons to stop writing code the old way and adopt a framework
instead 
 
 In PHP, the request is represented by global variables ($_GET, $_POST,
$_FILE, $_COOKIE, $_SESSION...) and the response is generated by
functions (echo, header, setcookie, ...).
 
=>better code : Object-Oriented approach, Symfony2 HttpFoundation component
 

$ vi framework/composer.json
{
    "require": {
        "symfony/class-loader": "2.1.*",
        "symfony/http-foundation": "2.1.*"
    }
}

:wq

php composer.phar update

vi autoload.php add at bootom :
 
<?php
 
// framework/autoload.php
 
$loader->registerNamespace('Symfony\\Component\\HttpFoundation', __DIR__.'/vendor/symfony/http-foundation')
 
wq:  
 
$ vi index.php

<?php
 
// framework/index.php
 
require_once __DIR__.'/autoload.php';
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
$request = Request::createFromGlobals();
$input = $request->get('name', 'World');
$response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
$response->send();
 
:wq 
 
$ vi bye.php
 
<?php
 
// framework/bye.php
 
require_once __DIR__.'/autoload.php';
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
$request = Request::createFromGlobals();
 
$response = new Response('Goodbye!');
$response->send();
:wq
 
$ vi init.php
<?php
 
// framework/init.php
 
require_once __DIR__.'/autoload.php';
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
$request = Request::createFromGlobals();
$response = new Response();
 
:wq
 
$ vi index.php<?php
 
// framework/index.php
 
require_once __DIR__.'/init.php';
 
$input = $request->get('name', 'World');
 
$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
$response->send();
wq
 
$ vi bye.php
<?php
 
// framework/bye.php
 
require_once __DIR__.'/init.php';
 
$response->setContent('Goodbye!');
$response->send();
 
:wq
 
$ vi front.php
<?php
 
// framework/front.php
 
require_once __DIR__.'/autoload.php';
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
$request = Request::createFromGlobals();
$response = new Response();
 
$map = array(
    '/hello' => __DIR__.'/hello.php',
    '/bye'   => __DIR__.'/bye.php',
);
 
$path = $request->getPathInfo();
if (isset($map[$path])) {
    require $map[$path];
} else {
    $response->setStatusCode(404);
    $response->setContent('Not Found');
}
 
$response->send();
 
:wq
 
$ vi hello.php
<?php
 
// framework/hello.php
 
$input = $request->get('name', 'World');
$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
 
:wq

==> http://example.com/front.php/hello?name=Fabien
==> http://example.com/front.php/bye
 
  
Now that the web server always access the same script (front.php) for all our pages, we can secure our code further by moving all other PHP files outside the web root directory:
example.com
??? composer.json
?        src
?        ??? autoload.php
?        ??? pages
?        ??? hello.php
?        ??? bye.php
??? vendor
??? web
??? front.php  

Now, configure your web server root directory to point to web/ and all other files won't be accessible from the client anymore.

vi src/pages/hello.php

<!-- example.com/src/pages/hello.php -->
 
<?php $name = $request->get('name', 'World') ?>
 
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
 
:wq
 
$ vi web/front.php
 <?php
 
// example.com/web/front.php
 
require_once __DIR__.'/../src/autoload.php';
 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
$request = Request::createFromGlobals();
$response = new Response();
 
$map = array(
    '/hello' => __DIR__.'/../src/pages/hello.php',
    '/bye'   => __DIR__.'/../src/pages/bye.php',
);
 
$path = $request->getPathInfo();
if (isset($map[$path])) {
    ob_start();
    include $map[$path];
    $response->setContent(ob_get_clean());
} else {
    $response->setStatusCode(404);
    $response->setContent('Not Found');
}
 
$response->send();
 
:wq

 
.
.
.
.
.
.
.
.
.
That's it! Our application has now four different layers and each of them has a well defined goal:
  • web/front.php: The front controller; the only exposed PHP code that makes the interface with the client (it gets the Request and sends the Response) and provides the boiler-plate code to initialize the framework and our application;
  • src/Simplex: The reusable framework code that abstracts the handling of incoming Requests (by the way, it makes your controllers/templates easily testable -- more about that later on);
  • src/Calendar: Our application specific code (the controllers and the model);
  • src/app.php: The application configuration/framework customization.
 


No comments:

Post a Comment