Sunday, May 22, 2011

Customize the route compiler for Silex

Do you know Flask which is a microframework for Python. It looks like Silex and sinatora.
It allows you to use 'Variable Rules'. This is like below:

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    pass

You can add the rule for each parameters, which are "int:", "float:" or "path:".
If you add "int:", this route accepts only integers and you add "path:", it is like the default but also accepts slashes.
you can implement by an assert method on Silex. Here is an example:

$app->get('/post/{post_id}', function ($post_id) {
    ...
})
->assert('post_id', '\d+')

You can see details in Requirements section of the document.

So I tried to implement it on the route of Silex. You can get the source code from my github branch.

If you use this version, you can write like this:

$app = new Silex\Application();
$app['route.compiler_class'] = 'Silex\\RouteAssertCompiler';

$app->get('/foo/{int:id}/{path:name}', function ($id, $name) {
    return sprintf("%d:%s", $id, $name);
});

I wrote a new RouteCompiler class, which is "RouteAssertCompiler" class and customized Application class which could load this new compiler class. So you have only to do is to set 'Silex\\RouteAssertCompiler' as 'route.compiler_class'.

If you access "/foo/3/a/b/c", this app returns "3:a/b/c" and if you access "/foo/a/b/c", then this route does not match.

I think this is simple and like it, but Silex has an assert method which is much flexible :)

Saturday, April 9, 2011

If your Phar doesn't work well ...

Do you know Phar extension? Here is a introduction in PHP official manual below:
What is phar? Phar archives are best characterized as a convenient way to group several files into a single file. As such, a phar archive provides a way to distribute a complete PHP application in a single file and run it from that file without the need to extract it to disk. Additionally, phar archives can be executed by PHP as easily as any other file, both on the commandline and from a web server. Phar is kind of like a thumb drive for PHP applications. (via. http://jp.php.net/manual/en/intro.phar.php)

Phar requires PHP 5.2.0 or newer. If your PHP is PHP 5.3, the Phar extension is build into PHP in default, otherwise Phar may be installed via the PECL extension with previous PHP versions.
If your Phar doesn't work well after you installed; the error has occured and so on, you have to check your php configure below.

check configure options

If you installed or compiled php with "--enable-zend-multibyte" option, Phar doesn't work well. So you have to recompile your php without "--enable-zend-multibyte".
For example, you installed php with brew, you have to modify your file.

Launch your text editor, and comment out the "--enable-zend-multivyte" line and save it.

    $ brew edit php

      "--enable-bcmath",
      "--enable-calendar",
      "--enable-memcache",
#      "--enable-zend-multibyte",
      "--with-openssl=/usr",
      "--with-zlib=/usr",
      "--with-bz2=/usr",

Suhosin Phar "URL not allowed"


If you use Suhosin PHP, Phar doesn't work well becase of its high security setting. So you have to check here.
In addition, Kagaya Masaki mailed me how to modify the php.ini for the environment of Suhosin PHP.
You may have to check your php.ini and modify it:

    detect_unicode = Off
    suhosin.executor.include.whitelist = phar

(via. http://www.php.net/manual/ja/phar.using.intro.php#100127)

I hope these tips will help you :)

Sample Micro Application "MameForm" which is made by Sliex

Silex is a microframework written in PHP. It's inspired by sinatora. You have only to do is to require silex.phar!

A new official web site of Silex has been published, so I tried to create a sample application with it.



At this time, I decided to create a simple contact form application with it. there are 2 screens, entry and complete one. when users complete, an email will be sent to the administration.


Phar



[Notice] If your PHP is compiled with "--enable-zend-multibyte", Phar doesn't work. Check your compile option.


Install MameForm



On the official site, you can read the detail documentation about Silex.

Now you can get my sample application code by my git repository.

    $ git clone git://github.com/brtriver/MameForm.git ./MameForm
    $ cd ./MameForm
    $ git submodule init
    $ git submodule update

This sample application use Twig for a template engine and SwiftMailer4 for sending emails, so you have to call submodule init and update.


Structure of MameForm



The structure of MameForm is very simple because of phar archive.
index.php file is a front controller file and which we write logics code in.
"views" directory is to set twig templates, in this application, only 4 templates are set: base.twig (layout), entry.twig, complete.twig and mail.twig(used for mail body).

There are some .htaccess files to deny to access this directory directly.




Call the application



simple call

Then we read index.php.
It is very simple to use Silex. You have only to write below:

  
    require __DIR__.'/silex.phar';
    use Silex\Application;
  
    $app = new Application();
    ....
    $app->run();


Entry Page



If you install MameForm in your local environment, you can see below with accessing like "http://localhost/MameForm"



Then let's see the code to be execused in index.php

  
    ...
    // entry
    $app->get('/', function() use ($app) {
        return $app['twig']->render('entry.twig', array());
    });

Silex helps us to write easily. If the request method is "GET", $app->get method is called and because of our using anonymouse function, this code is very readability.
In this code, there is no logic in it: you have only to return the results which is rendered by Twig.


Validation



Silex has no library or extention to validate, so this time, I wrote the code in index.php below: It's not best way to write validation code in index.php directly, if you always use Silex, You have to prepare your validation library with a Silex extention.


  ...
  // send email
  $app->post('/', function() use ($app) {
   // validate
   $errors = array();
   // check empty
   foreach (array('name', 'email', 'message') as $k) {
   if (trim($app['request']->get($k)) === "") {
   $errors[] = sprintf("%s is required.", $k);
   }
   }
   // check email
   if (!filter_var($app['request']->get('email'), FILTER_VALIDATE_EMAIL)) {
   $errors[] = sprintf("email is not valid.");
   }
   // send email
   if (count($errors) === 0) {
            //after sending email, redirect to complete page.
   }
   return $app['twig']->render('entry.twig', compact('errors'));
  });

If the request method is "POST" and access to "/", $app->post("/") method is called. If errors are occured, an error message pushed to the $errors array. It's simple.


Before Filter



I want to use request parameters in templates. in default, we can access to "{{app.requset.request.get('name')}}" but it's not simple, so I made a shorter name for this property like this:


  ...
  // filter
  $app->before(function() use($app){
   // assign request parameters to "request" for Twig templates with shorter name
   $app['twig']->addGlobal('request', $app['request']->request);
  });

Thanks to this fileter, I can do access to the request parameters like "{{request.get('name')}}", It's very read readability.

Finally, the template of the entry page is like below:



{% extends "base.twig" %}
{% block title %}Entry Page{% endblock %}
{% block content %}

 <h1>Contact Form</h1>

 <ul class="error">
{% for error in errors %}
   <li>{{error}}</li>
{% endfor %}
 </ul>

 <div id="entryForm">
   <form method="post" action="{{app.request.baseUrl}}/">
   <div id="formName">
       <label for="Name">Name:</label>
       <input type="text" name="name" id="Name" value="{{request.get('name')}}">
     </div>
     <div id="formEmail">
       <label for="Email">Email:</label>
       <input type="text" name="email" id="Email" value="{{request.get('email')}}">
     </div>
   <div id="formMessage">
       <label for="Message">Message:</label><br>
       <textarea name="message" rows="20" cols="20" id="Message">{{request.get('message')}}</textarea>
      </div>
      <div>
       <input type="submit" name="submit" value="Submit" class="submit-button">
      <div>
   </form>
 </div>
{% endblock %}


Thanks to Twig, the default params are escaped so we don't need to care of XSS.


Send Emails



This sample code is similar to demo code in the official documentation, only in this code, we can use Twig for body content.


  ...
// send email
if (count($errors) === 0) {
   $body = $app['twig']->render('mail.twig'); // set Twig template
   $message = \Swift_Message::newInstance()
       ->setSubject(EMAIL_SUBJECT)
       ->setFrom(array($app['request']->get('email')))
       ->setTo(array(EMAIL_ADDRESS_FROM))
       ->setBody($body);
   $transport = \Swift_MailTransport::newInstance();
   $mailer = \Swift_Mailer::newInstance($transport);
   $mailer->send($message);
   return $app->redirect($app['request']->getBaseUrl() .'/complete');
}


After sending a email, Silex force to redirect to the complete page. this redirect function is default method in Silex.


Conclusion



The total line number which I had to write is only 65. I think it is very shorter than if we write plain php codes.
It is not difficult to add a new feature to your Silex application because of the extension and it is easy to upload your Silex application to your server by FTP because of a phar archive.
If you want to watch another code written by Silex, there is Sismo,which is brought to you by Fabien Potenci. It's very useful to understand Silex.

Wednesday, March 23, 2011

New Japanese Symfony 1.4 book has been published!

On March 23, a new Japanese symfony(1.4) book had been published!

In Japan, there are a lot of technological books for PHP. but there was no Japanese symfony book for ver 1.4.
Until now there were two books for ver 1.0.
I had written one of Japanese symfony books for ver 1.0 in 2007. It's 4 years ago! Those who wanted to learn symfony and buy Japanese books always said that they couldn't buy symfony books because it's too old. I think that a lot of Japanese developers would like to buy books as well as read it online. Now People focus on Symfony2 but symfony1.4 is stable and the current Long Term Support release, so symfony 1.4 is used in a lot of projects. It's very important for developers to buy this book.

This book has been written by members of the Japanese Symfony user group. They are specialists who use symfony in Japan. So this book includes a lot of tips in practice. We hope this book helps Japanese developers who learn and understand symfony.

And Fabien and Stefan gave us messages for this book. Thanks a lot!

via: http://books.symfony.gr.jp/14book/index.html (Japanese Only)

Friday, March 11, 2011

How to custmize the error page in Symfony2

We can see the new character, which name I don't know at the error page.

Speaking of the character, there is an unofficial symfony character, which name is "Symfonyan".

Symfonyan is "Symfony" + "Nayn". "Nyan" is Japanese which mean mew. She is pretty :D

So, I tried to change the character to Symfonyan in this error page.

How to customize the error page


At the first, I read the documentation Symfony - How to customize Error Pages. But I could not custmize it! Perhaps the path where we put the customized html is changed in PR7. So I read the source code and I saw the correct path.

So I decided to make an original bundle, "SymfonyanBundle" because of making everyone use it easily.

How to install SymfonyanBundle


the source code of SymfonyanBundle is managed in Github. So you can install it by git command.
$ git clone git://github.com/brtriver/SymfonyanBundle.git src/Acme/SymfonyanBundle

You have to add this bundle in app/AppKernel.php

....
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
    $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
    $bundles[] = new Symfony\Bundle\WebConfiguratorBundle\SymfonyWebConfiguratorBundle();
    $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
    $bundles[] = new Acme\SymfonyanBundle\SymfonyanBundle(); // <= addition
}


Confirm by the console. If you can add this bundle, you can see like below.
$ ./app/console
...
symfonyan
:exception-install Change the icon of the exception page to Symfonyan
:welcome-install Change the welcome page to Symfonyan

At the last, you have only to run this command from console. It's very simple and easy!

$ ./app/console symfonyan:exception-install --symlink
$ ./app/console assets:install web --symlink

Result


[404 in development env]

[404 in production env]

cute!

One more thing

You can customize the default welcome page with Symfonyan. It's easy to run the command below.

$ ./app/console symfonyan:welcome-install --symlink
The default page is translated to Japanese, too :)