Development

MyFirstSymfonyProjectPtBr

You must first sign up to be able to contribute.

Texto original: http://www.symfony-project.com/tutorial/my_first_project.html

Meu primeiro projeto com o symfony

Então você quer testá-lo? Vamos construir juntos uma aplicação totalmente funcional em uma hora. Você escolhe. Uma livraria? Ok, outra idéia. Um weblog! Boa pedida. Vamos começar.

Nós vamos assumir que você possui apache/PHP5 instalado e funcionando em sua máquina. Você também vai precisar da extensão SQLite, que já vem compilada e empacotada por padrão com o PHP5. Entretanto, desde o PHP 5.1.0 você precisa ativar manualmente esta extensão no php.ini (informações aqui).

Instalando o symfony e iniciando o projeto

Para irmos rapidamente, usaremos o symfony-sandbox. É uma projeto vazio do symfony onde todas as bibliotecas necessárias estão incluidas, e onde todas as configurações estão prontas. A grande vantagem do sandbox sobre os outros tipos de instalação é que você pode começar a experimentar o symfony imediatamente.

Pegue o aqui: sf_sandbox.tgz, e descompacte-o no diretório raiz do seu servidor web. Olhe o arquivo readme incluso no pacote para maiores informações. A estrutura de arquivos resultante deve ser assim:

www/
  sf_sandbox/
    apps/
      frontend/
    batch/
    cache/
    config/
    data/
      sql/
    doc/
      api/
    lib/
      model/
    log/
    test/
    web/
      css/
      images/
      js/
      uploads/

Isto mostra um projeto sf_sandbox contendo uma aplicação frontend. Teste o sandbox requisitando a seguinte URL:

http://localhost/sf_sandbox/web/index.php/

Você deve ver uma página de congratulações.

http://www.symfony-project.com/images/tutorials/first_congrats.gif

Nota: Se você não vir esta página, verifique se a sua configuração do php.ini possue o magic_quotes_gpc como {{{off}} (desligado). Para mais ajuda, verifique o fórum de instalação onde muitos casos de erro já foram solucionados.

Você também pode instalar o Symfony em um diretório qualquer e configurar seu servidor web com um Virtual Hosts ou um Alias. O livro Symfony contém capítulos detalhados sobre a instalação do symfony, criação de projetos e estrutura de arquivos.

Inicializando o modelo de dados

Então, o weblog poderá manusear os "posts" (informação publicada no weblog), e você poderá habilitar os comentários neles. Crie um arquivo schema.yml em sf_sandbox/config/ e cole o seguinte modelo de dados:

propel:
  weblog_post:
    _attributes: { phpName: Post }
    id:
    title:       varchar(255)
    excerpt:     longvarchar
    body:        longvarchar
    created_at:
  weblog_comment:
    _attributes: { phpName: Comment }
    id:
    post_id:
    author:      varchar(255)
    email:       varchar(255)
    body:        longvarchar
    created_at:

Este arquivo de configuração utiliza uma sintaxe YAML. É uma linguagem muito simples que permite estruturas em árvore XML-like descritas por identação. Por esta razão, é mais rápida de ler e escrever que XML. O único detalhe é que a identação tem um significado e tabulações são proibidas, portanto lembre-se de usar espaços para identar. Você encontrará mais sobre YAML e configuração do symfony no capítulo de configuração.

Este esquema descreve uma estrutura de duas tabelas necessárias para o weblog. Post e Comment são os nomes das classes relacionadas a serem geradas. Salve o arquivo, entre na linha de comando, vá até o diretório the sf_sandbox/ e digite:

$ symfony propel-build-model

Nota: Nas platforms *nix, você pode ter que executar ./symfony.sh ao invés de apenas symfony.

Algumas classes são criadas no diretório sf_sandbox/lib/model/. Estas são as classes do mapeamento objeto-relacional, o qual nos permite ter acesso ao banco de dados relacional a partir de um código orientado a objeto sem a necessidade de escrever uma linha SQL. Symfony usa a biblioteca Propel para isto. Nós chamremos estes objetos de modelo (mais informações no capítulo modelo).

Agora digite na linha de comando:

$ symfony propel-build-sql

O arquivo schema.sql é criado em sf_sandbox/data/sql/. Esta chamada SQL pode ser usada para inicializar um banco de dados com a mesma estrutura de tabelas. Você pode criar um banco de dados MySQL a partir da linha de comando ou pela interface web (como descrito no capítulo modelo).Oportunamente, a symfony sandbox já vem configurada para trabalhar com um simples arquivo SQLite, assim nenhuma inicialização é necessária. Caso nada seja expecificado, o projeto sf_sandbox usará um arquivo chamado sandbox.db localizado em sf_sandbox/data/. Para construir a estrutura de tabelas baseada no arquivo SQL, digite:

$ symfony propel-insert-sql

Nota: Não fique preocupado caso surja algum aviso de alerta, isto é normal. O comando insert-sql remove as tabelas existente antes de adiciona-las ao seu schema.sql, e não há nenhuma tabela a ser removida agora.

Criando o "andaime" da aplicação

As funções básicas de um weblog são Criar, Listar, Atualizar e Excluir (Create, Retrieve, Update and Delete (CRUD)) mensagens e comentários. Como você é novo no symfony, você não vai criar códigos do symfony do zero, mas deixar ele criar plataforma para você usar e modificar conforme o necessário. O symfony pode interpretar o modelo de dados e gerar a interface CRUD automaticamente:

$ symfony propel-generate-crud frontend post Post $ symfony propel-generate-crud frontend comment Comment

Nota: Tenha certeza de estar no diretório raiz do seu projeto (sf_sandbox/) quando você chamar o comando symfony.

Agora nós temos dois módulos (post e comment) que nos permitirão manipular registros das tabelas de Post e Comment (o atributo 'phpName' definido no arquivo schema.xml é utilizado para definir o nome destas tabelas). Um módulo geralmente representa uma página ou um grupo de páginas com propósitos similares. Nossos novos módulos estão localizados no diretório sf_sandbox/apps/frontend/modules/ , e estão acessíveis pelas URLs:

http://localhost/sf_sandbox/web/frontend_dev.php/post http://localhost/sf_sandbox/web/frontend_dev.php/comment

Sinta-se à vontade para criar um novo post para que nosso weblog pareça menos vazio.

http://www.symfony-project.com/images/tutorials/first_crud.gif

post CRUD

Saiba mais sobre "andaime" (scaffolding) e a explicação sobre estruturas de projetos symfony (projeto, aplicação, módulo).

Nota: Nas URLs acima, o nome do script principal - chamado de "controlador frontal" (front controller) no symfony - foi mudado de index.php para frontend_dev.php. O dois scripts acessam a mesma aplicação (frontend), mas em ambientes diferentes. Usando frontend_dev.php, acessaremos a aplicação em ambiente de desenvolvimento, que provem ferramentas úteis de desenvolvimento como a barra de debug on topo direito da tela e a estrutura de configuração em tempo real (live configuration engine). Esta é a razão pela qual o processamento de cada página é mais demorado do que acessando através de index.php, que por sua vez é o controlador frontal do ambiente de produção, otimizado para ser mais veloz. Se quisermos continuar utilizando o ambiente de produção, devemos substituir frontend_dev.php/ por index.php/ nas URLs seguintes, mas não devemos esquecer de de limpar o cache antes de realizar as mudanças:

$ symfony clear-cache

http://localhost/sf_sandbox/web/index.php/

Saiba mais sobre ambientes.

Modificando la mise en page

In order to navigate between the two new modules, nosso weblog precisa de alguma navegação global.

Modifique o template global sf_sandbox/apps/frontend/templates/layout.php e altere o conteúdo da tag <body> para:

<div id="container" style="width:600px;margin:0 auto;border:1px solid grey;padding:10px">
  <div id="navigation" style="display:inline;float:right">
    <ul>
      <li><?php echo link_to('List of posts', 'post/list') ?></li>
      <li><?php echo link_to('List of comments', 'comment/list') ?></li>
    </ul>
  </div>
  <div id="title">
    <h1><?php echo link_to('My first symfony project', 'default/index') ?></h1>
  </div>
 
  <div id="content" style="clear:right">
    <?php echo $sf_content ?>
  </div>
</div>

Por favor perdoe pelo design pobre e o uso de css inner-tag, mas uma hora é tempo curto demais.

http://www.symfony-project.com/images/tutorials/first_crud_layout.gif

While you are at it, you can change the title of your pages. Edit the view configuration file of the application (sf_sandbox/apps/frontend/config/view.yml), locate the line showing the title key and change it to:

default:
  http_metas:
    content-type: text/html; charset=utf-8

  metas:
    title:        The best weblog ever
    robots:       index, follow
    description:  symfony project
    keywords:     symfony, project
    language:     en

Este arquivo de configuração utiliza sintaxe YAML. Trata-se de uma linguagem bem simples que permite estruturas em árvore similares a XML, atributos e de uma boa apresentação. Furthermore, ela é mais rápida de ler e escrever que XML. The only thing is, the indentation has a meaning and tabulations are forbidden, so remember to use spaces for indentation. You will find more about YAML and the symfony configuration in the configuration chapter.

A home page também precisa ser modificada. Ela usa um template padrão de um módulo padrão, que é mantido pelo framework, portanto não está presente no diretório da aplicação. Para sobrescrever isso, teremos que criar um módulo padrão customizado:

$ cd apps/frontend/modules
$ mkdir default
$ cd default
$ mkdir templates
$ cd templates

Criaremos então um arquivo indexSuccess.php mostrando uma simpática mensagem de boas-vindas:

<h1>Welcome to my swell weblog</h1>
<p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>

Verifiquemos o resultado chamando a home page novamente:

http://localhost/sf_sandbox/web/frontend_dev.php/

http://www.symfony-project.com/images/tutorials/first_welcome.gif

Go ahead, start using your new web app: Create a new test post, and a test comment for your this post.

Find more about templates and the view configuration.

Pass data from the action to the template

That was fast, wasn't it? Now it is time to mix the comment module into the post one to get comments displayed below posts.

First, you need to make the post comments available for the post display template. In symfony, this kind of logic is kept in actions. Edit the actions file sf_sandbox/apps/frontend/modules/post/actions/actions.class.php and change the executeShow() method by adding the 4 middle lines:

public function executeShow ()
{
  $this->post = PostPeer::retrieveByPk($this->getRequestParameter('id'));
 
  $c = new Criteria();
  $c->add(CommentPeer::POST_ID, $this->getRequestParameter('id'));
  $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
  $this->comments = CommentPeer::doSelect($c);
 
  $this->forward404Unless($this->post instanceof Post);
}

The Criteria and -Peer objects are part of Propel's object-relational mapping. Basically, these four lines will handle a SQL query to the Comment table to get the comments related to the current Post (the one designated by the URL parameter id). The $this->comments line in the action will give access to a $comments variable in the corresponding template. Now, modify the post display template sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php by adding at the end:

...
<?php use_helper('Text') ?>
<?php use_helper('Date') ?>
 
<hr />
<?php if($comments) : ?>
  <p><?php echo count($comments) ?> comment<?php if(count($comments)>1) : ?>s<?php endif ?> to this post.</p>
  <?php foreach ($comments as $comment): ?>
    <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p>
    <div class="comment" style="margin-bottom:10px;">
      <?php echo simple_format_text($comment->getBody()) ?>
    </div>    
  <?php endforeach ?>
<?php endif ?>

This page uses new PHP functions (format_date() and simple_format_text()) provided by symfony, and called 'helpers' because they do some tasks for you that would normally require more time and code. Create a new comment for your first post, then check again the first post, either by clicking on its number in the list, or by typing directly:

http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1

http://www.symfony-project.com/images/tutorials/first_comments_under_post.gif

This is getting good.

Find more about the naming conventions linking an action to a template, and the text and date helpers.

Add a record relative to another table

When adding a comment, you can choose the id of the related post. That's not very user-friendly. Let's change this, and make sure that the user comes back to the post he was looking at after adding a comment.

First, in the still fresh modules/post/templates/showSuccess.php template, add a line at the bottom:

<?php echo link_to('Add a comment','comment/create?post_id='.$post->getId()) ?>

The link_to() helper creates a hyperlink pointing to the create action of the comment module, so you can add a comment directly from the post details page. Next, open the modules/comment/templates/editSuccess.php and replace the following lines:

<tr>
  <th>Post*:</th>
  <td><?php echo object_select_tag($comment, 'getPostId', array (
  'related_class' => 'Post',
)) ?></td>
</tr>

By:

<?php if ($sf_request->hasParameter('post_id')): ?>
  <?php echo input_hidden_tag('post_id',$sf_request->getParameter('post_id')) ?> 
<?php else: ?>
  <tr>
    <th>Post*:</th>
    <td><?php echo object_select_tag($comment, 'getPostId', array (
    'related_class' => 'Post',
    )) ?></td>
  </tr>
<?php endif ?>

The form in the comment/create page points to a comment/update action, which redirects to comment/show when submitted (this is the default behaviour in generated CRUDs). For the weblog, that means that after adding a comment to a post, the detail of the comment is displayed. It is better to display the post with the comments at that point. So open the modules/comment/actions/actions.class.php and look for the executeUpdate() method. Note that the created_at field is not defined by the action: Symfony knows that a field named created_at has to be set to the system time when a record is created. The final redirect of the action has to be modified to point to the correct action. Change it to:

public function executeUpdate ()
{
  if (!$this->getRequestParameter('id', 0))
  {
    $comment = new Comment();
  }
  else
  {
    $comment = CommentPeer::retrieveByPk($this->getRequestParameter('id'));
    $this->forward404Unless($comment);
  }    
 
  $comment->setId($this->getRequestParameter('id'));
  $comment->setPostId($this->getRequestParameter('post_id'));
  $comment->setAuthor($this->getRequestParameter('author'));
  $comment->setEmail($this->getRequestParameter('email'));
  $comment->setBody($this->getRequestParameter('body'));
 
  $comment->save();
 
  return $this->redirect('post/show?id='.$comment->getPostId());
}

Usuários poderão agora inserir comentários para um post and come back to the post afterwards. You wanted a weblog? You have a weblog.

Saiba mais sobre ações.

Validação de Formulários

Visitantes poderão inserir comentários, mas o que aconteceria se eles submeterem o formulário sem qualquer informação nele ? Teríamos um database suja, inconsistente. Para evitar isso, criaremos um arquivo chamado update.yml no diretório sf_sandbox/apps/frontend/modules/comment/validate/ (teremos também que criar o diretório) e escreveremos nele:

methods:
  post:           [author, email, body]
  get:            [author, email, body]

fillin:
  activate:       on

names:
  author:
    required:     Yes
    required_msg: The name field cannot be left blank

  email:  
    required:     No
    validators:   emailValidator

  body:
    required:     Yes
    required_msg: The text field cannot be left blank

emailValidator:
  class:          sfEmailValidator
  param:
    email_error:  The email address is not valid.

Note: Beware that you don't copy 4 extra spaces at the beginning of each line, since the YAML parser would fail in that case. The first letter of this file must be the 'm' of 'methods'.

The fillin activation enables the repopulation of the form with the value previously entered by the user in case of failed validation. The names declarations set the validation rules for each input of the form.

By itself, the controller will redirect the user to a updateError.php template if an error is detected. It would be better to display the form again with an error message. To do that, add a handleError method to the action class of the modules/comment/actions/actions.class.php file:

public function handleError()
{
  $this->forward('comment', 'create');
}

Now to finish, open again the modules/comment/templates/editSuccess.php template and insert at the top:

<?php if ($sf_request->hasErrors()): ?>  
  <div id="errors" style="padding:10px;">
    Please correct the following errors and resubmit:
    <ul>
    <?php foreach($sf_request->getErrors() as $error): ?>
      <li><?php echo $error ?></li>
    <?php endforeach ?>
    </ul>
  </div>
<?php endif ?>

You now have a robust form.

http://www.symfony-project.com/images/tutorials/first_form_validation.gif

Find more about form validation.

Change the URL aspect

Did you notice the way the URLs are rendered? You can make them more user and search engine-friendly. Let's use the post title as an URL for posts.

The problem is that post titles can contain special characters like spaces. If you just escape them, the URL will show some ugly %20 kind of things, so you'd better extend the model to add a new method to the Post object to get a clean, stripped title. To do that, edit the file Post.php located in the sf_sandbox/lib/model/ directory, and add the following method:

public function getStrippedTitle()
{
  $result = strtolower($this->getTitle());
 
  // strip all non word chars
  $result = preg_replace('/\W/', ' ', $result);
 
  // replace all white space sections with a dash
  $result = preg_replace('/\ +/', '-', $result);
 
  // trim dashes
  $result = preg_replace('/\-$/', '', $result);
  $result = preg_replace('/^\-/', '', $result);
 
  return $result;
}

Now you can create a permalink action for the post module. Add the following method to the modules/post/actions/actions.class.php:

public function executePermalink()
{
  $posts = PostPeer::doSelect(new Criteria());
  $title = $this->getRequestParameter('title');
  foreach ($posts as $post)
  {
    if ($post->getStrippedTitle() == $title)
    {
      break;
    }
  }
  $this->forward404Unless($post);
 
  $this->getRequest()->setParameter('id', $post->getId());
 
  $this->forward('post', 'show');
}

The post list can call this permalink action instead of the show one for each post. In modules/post/templates/listSuccess.php, delete the id table header and cell, and change the Title cell from:

<td><?php echo $post->getTitle() ?></td>

To:

<td><?php echo link_to($post->getTitle(), '/'.$sf_last_module.'/permalink?title='.$post->getStrippedTitle()) ?></td>

Just one more step: Edit the routing.yml located in the sf_sandbox/apps/frontend/config/ directory and add these rules at the top:

list_of_posts:
  url:   /latest_posts
  param: { module: post, action: list }

post:
  url:   /weblog/:title
  param: { module: post, action: permalink }

Now navigate again in your application and watch the URLs.

http://www.symfony-project.com/images/tutorials/first_routing.gif

En savoir plus sur les smart URLs.

Cleanup in the frontend

Well, if this is a weblog, then everybody has the right to post. This isn't exactly what you thought about, right? Ok, let's clean up our templates a bit.

In the template modules/post/templates/showSuccess.php, get rid of the 'edit' link by removing the line:

<?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>

Do the same for the modules/post/templates/listSuccess.php template and remove:

<?php echo link_to('create', 'post/create') ?>

You also have to remove the following methods from the modules/post/actions/actions.class.php:

* executeCreate * executeEdit * executeUpdate * executeDelete

All right, readers cannot post anymore.

Generation of the backend

For you to write posts, let's create a backend application by typing in the command line (still from the sf_sandbox project directory):

$ symfony init-app backend
$ symfony propel-init-admin backend post Post
$ symfony propel-init-admin backend comment Comment

This time, we use the admin generator. It offers much more features and customization than the very basic CRUD generator.

Just like you did for the frontend application, edit the layout (apps/backend/template/layout.php) to add global navigation:

<div id="navigation">
  <ul style="list-style:none;">
    <li><?php echo link_to('Manage posts', 'post/list') ?></li>
    <li><?php echo link_to('Manage comments', 'comment/list') ?></li>
  </ul>
</div>      
<div id="content">
  <?php echo $sf_content ?>
</div>

Note: Because you are using a sandbox, you also need to copy the sf_sandbox/web/sf/images/sf_admin/ directory in a sf/images/sf_admin/ directory in your web root folder (this is due to image paths in CSS stylesheets).

You can access your new back-office application in the development environment by calling:

http://localhost/sf_sandbox/web/backend_dev.php/post

http://www.symfony-project.com/images/tutorials/first_basic_admin.gif

The great advantage of the generated admin is that you can easily customize it by editing a configuration file.

Change the backend/modules/post/config/generator.yml to:

generator: 
  class:              sfPropelAdminGenerator
  param:
    model_class:      Post
    theme:            default
    fields:
      title:          { name: Title }
      excerpt:        { name: Exerpt }
      body:           { name: Body }
      nb_comments:    { name: Comments }
      created_at:     { name: Creation date }
    list:
      title:          Post list
      layout:         tabular
      display:        [=title, excerpt, nb_comments, created_at]
      object_actions:
        _edit:        -
        _delete:      -
      max_per_page:   5
      filters:        [title, created_at]
    edit:  
      title:          Post detail        
      fields:
        title:        { type: input_tag, params: size=53 }
        excerpt:      { type: textarea_tag, params: size=50x2 }
        body:         { type: textarea_tag, params: size=50x10 }
        created_at:   { type: input_date_tag, params: rich=on }

Note that among the existing columns of the Post table, the admin will look for a nb_comments. There is no associated getter yet, but it is simple to add to the sf_sandbox/lib/model/Post.php:

public function getNbComments()
{
  return count($this->getComments()); 
}

Now refresh the Post administration an see the changes:

http://www.symfony-project.com/images/tutorials/first_custom_admin.gif

Restrict access to the backend

The backend can be accessed by everybody. You have to add access restriction.

In apps/backend/modules/post/config/, add a security.yml with the following content:

all:
  is_secure: on

Repeat the operation for the comment module. Now you can't access these modules anymore unless you are logged.

But the logging action doesn't exist! Ok, so you can easily add it. First, create the security module skeleton:

$ symfony init-module backend security

This new module will be used to handle the login form and the request. Edit the apps/backend/modules/security/templates/indexSuccess.php to create the login form:

<h2>Authentication</h2>
 
<?php if ($sf_request->hasErrors()): ?>
  Identification failed - please try again
<?php endif ?>
 
<?php echo form_tag('security/login') ?>
  <label for="login">login:</label>
  <?php echo input_tag('login', $sf_params->get('login')) ?>
 
  <label for="password">password:</label>
  <?php echo input_password_tag('password') ?>
 
  <?php echo submit_tag('submit', 'class=default') ?>
</form>

Add the login action that is called by the form to the security module (in the apps/backend/modules/security/actions/actions.class.php file):

public function executeLogin()
{
  if ($this->getRequestParameter('login') == 'admin' && $this->getRequestParameter('password') == 'password')
  {
    $this->getUser()->setAuthenticated(true);
    return $this->redirect('default/index');
  }
  else
  {
    $this->getRequest()->setError('login', 'incorrect entry');
    return $this->forward('security', 'index');
  }
}

The last thing to do is to set the security module as the default module to handle login actions. To do that, open the apps/backend/config/settings.yml configuration file and add:

all:
  .actions:
    login_module:           security
    login_action:           index

At that point, if you try to access the Posts management, you will have to enter a login and a password:

http://www.symfony-project.com/images/tutorials/first_login.gif

Find more about security.

Conclusion

Ok, the hour is out. You made it. Now you can use both applications in the production environment and play with them:

frontend:   http://localhost/sf_sandbox/web/index.php/
backend:    http://localhost/sf_sandbox/web/backend.php/

At this point, if you meet an error, it might be because you changed the model after some actions were put in cache (cache isn't activated in the development environment). To clear the cache, simply type:

$ symfony cc

See, the application is fast and runs smoothly. Pretty darn cool, isn't it? Feel free to explore the code, add new modules, and change the design of pages.

And don't forget to mention your working symfony applications in the symfony Wiki!