Tekst oryginał: http://www.symfony-project.com/tutorial/my_first_project.html
Mój pierwszy projekt w symfony.
Chcesz wypróbować symfony? Zbudujmy więc razem pełną funkcjonalną aplikację internetową w jedną godzinę. Ty nazwiesz ją. Księgarnią internetową ? Ok. inny pomysł. Weblog! Więc zaczynajmy.
Zakładam że pracujesz na apache/PHP5 zainstalowanym na twoim localhost.
Instalacja Symfony i inicjacja projektu
Aby szybko rozpocząć, użyjemy Symfony sandbox. Jest to pusty projekt symfony gdzie wszystkie potrzebne biblioteki zostały już dołączone, oraz podstawowa konfiguracja wykonana. Wielką zaletą sandbox-u ponad innymi rodzajami instalacji jest to że od razu można zacząć eksperymentować w symfony.
Pobierz go stąd: sf_sandbox.tgz i rozpakuj go do twojego katalogu głównego (root/www).
Przeczytaj plik readme jeśli chcesz uzyskać więcej informacji. W rezultacie uzyskasz strukturę plików która wygląda tak:
www/
sf_sandbox/
apps/
frontend/
batch/
cache/
config/
data/
sql/
doc/
api/
lib/
model/
log/
test/
web/
css/
images/
js/
uploads/
Pokazuje to projekt sf_sandbox projekt wraz z aplikacją frontend. Przetestuj sandbox przez wywołanie następującego URL:
http://localhost/sf_sandbox/web/index.php/
Powinieneś zobaczyć stronę z gratulacjami:
http://www.symfony-project.com/images/tutorials/first_congrats.gif
Jezeli nie widzisz tej strony, sprawdź swoije php.ini, opcja magic_quotes_gpc musi byc ustawiona na off. Więcej pomocy o instalacji symfony uzyskasz na forum installation forum
Możesz także zainstalowaś symfony w dowolnym folderze i ustawić twój serwer z Virtual Host lub Alias. Symfony book ma szczegółowe rozdziały na temat instalacji symfony installation, tworzenia projektu : project creation oraz struktury plików : file structure.
Inicjalizacja modelu danych (data model)
Weblog będzie przechowywał posty a ty będziesz mógł je komentować. Edytuj plik: sf_sandbox/config/schema.xml i wstaw następującą konfigurację:
<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
<table name="weblog_post" phpName="Post">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="title" type="varchar" size="255" />
<column name="excerpt" type="longvarchar" />
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
</table>
<table name="weblog_comment" phpName="Comment">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="post_id" type="integer" required="true" />
<foreign-key foreignTable="weblog_post">
<reference local="post_id" foreign="id"/>
</foreign-key>
<column name="author" type="varchar" size="255" />
<column name="email" type="varchar" size="255" />
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
</table>
</database>
Ten kod opisuje dwie tabele Post i Comment. Zapisz go i otwórz linie poleceń wejdź do katalogu sf_sandbox/ i wykonaj polecenie:
$ symfony propel-build-model
Jeżeli używasz linuxa może to być ./symfony.sh
Kilka klas zostanie stworzonych w katalogu sf_sandbox/lib/model/. Są to klasy object-relational mapping (mapowanie relacyjno-obiektowe), które pozwalają na dostęp do relacyjnej bazy danych z obiektowego kodu bez pisania nawet jednej linii kodu SQL. Symfony używa do tego biblioteki Propel. Będziemy nazywali te obiekty: model. (więcej znajdziesz w dziale : model chapter )
Teraz wykonaj w linii komend następujące polecenie:
$ symfony propel-build-sql
Symfony stworzy plik schema.sql w katalogu sf_sandbox/data/sql/. Ten plik może służyć do inicjacji bazy danych strukturą tabel. Możesz teraz stworzyć bazę danych w Mysql. Symfony sandbox jest standardowo skonfigurowane by pracować z bazą Sqlite więc nie musisz teraz nic tworzyć. Sf_sandbox używa bazy sandbox.db znajdującej się w katalogu sf_sandbox/data/. Aby zbudować stukturę bazy dla naszej aplikacji, bazującą na naszym schema.sql wystarczy wydać polecenie:
$ symfony propel-insert-sql
Nie przejmuj się jeżeli zobaczysz jakieś ostrzeżenia. Polecenie insert-sql usuwa najpierw stare tabele zanim doda nowe, a w tej chwili nie ma jeszcze w bazie nic.
Tworzenie szkieletu aplikacji (application scaffolding)
Na początku dla naszych tabel Post i Comment potrzebujemy prostych operacji: dodaj, edytuj, wyszukaj i usuń (Create, Retrieve, Update and Delete (CRUD)). Nie musisz tworzyć tego kodu ręcznie pozwól symfony wykonać za ciebie tą pracę a ty tylko dostosujesz go do swoich potrzeb. Symfony potrafi interpretować model aby wygenerować akcję CRUD automatycznie.
Wykonaj dwa polecenia:
$ symfony propel-generate-crud frontend post Post $ symfony propel-generate-crud frontend comment Comment
Upewnij się że jesteś w głównym katalogu twojego projektu: (sf_sandbox/) kiedy wywołujesz symfony
Posiadasz juz dwa moduły twojej aplikacji (post i comment), które pozwalają ci na manipulację danymi z tabel Post i Comment w bazie danych (Atrybut phpName z pliku schema.xml jest użyty jako nazwy tabel). Moduł zazwyczaj reprezentuję stronę lub grupę stron z podobna tematyką. Twoje nowe moduły znajdują się w katalogu sf_sandbox/apps/frontend/modules/ i można się do nich odwołać w następujący sposób:
http://localhost/sf_sandbox/web/frontend_dev.php/post http://localhost/sf_sandbox/web/frontend_dev.php/comment
Stwórz kilka postów aby twój weblog nie był pusty:
http://www.symfony-project.com/images/tutorials/first_crud.gif
Więcej o budowaniu szkieletów znajdziesz : scaffolding wyjaśnienie struktury projektu symfony: structure
W URL-ach powyżej nazwa głównego skryptu nazywanego front controller w symfony została zmieniona z index.php na frontend_dev.php. Te dwa skrypty dają dostęp do tej samej aplikacji ale różnych środowiskach. Przez frontend_dev.php masz dostęp do aplikacji w środowisku development environment który posiada użyteczne narzędzia jak debug toolbar w prawym górnym rogu ekranu i silnik konfiguracyjny w czasie rzeczywistym. Dlatego wyświetlenie strony zajmuje dłużej niż poprzez index.php który z kolei jest front controlerem w środowisku produkcyjnym production environment, zoptymalizowanym pod względem szybkości działania. Jeżeli chcesz używać środowiska produkcyjnego zamień frontend_dev.php na index.php w URL-ach powyżej. Ale jednocześnie musisz pamiętać aby wyczyścić cache zanim obejrzysz zmiany na stronie.
$ symfony clear-cache http://localhost/sf_sandbox/web/index.php/
Więcej informacji na temat środowisk : environments
Modyfikacja wyglądu aplikacji
Aby nawigować pomiędzy dwoma modułami Weblog musi mieć jakąś globalną nawigację (menu).
Edytuj szablon globalny sf_sandbox/apps/frontend/templates/layout.php i zamień zawartość tagu <body> na
<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 $content ?>
</div>
</div>
Proszę wybaczcie skromny design ale jedna godzina to zbyt krótki czas.
http://www.symfony-project.com/images/tutorials/first_crud_layout.gif
Kiedy jesteś w tym miejscu możesz zmienić tytuł twojej strony. Edytuj plik konfiguracyjny sf_sandbox/apps/frontend/config/view.yml, zlokalizuj linię z kluczem title i zmień ja na:
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
Ten plik konfiguracyjny używa składni YAML. Jest to to bardzo prosty język, który ma strukturę drzewa jak XML i ładną prezentację. Co więcej jest łatwiejszy i szybszy do czytania i pisania. Jedynie wcięcia mają znaczenie a tabulacje są zabronione, więc pamiętaj by używać spacji do robienia wcięć. Znajdziesz więcej o YAML i konfiguracji symfony w dziale configuration chapter Strona domowa musi też być zmieniona. Używa ona domyślnego szablonu w domyślnym module, który jest przechowywany we frameworku a nie w katalogu aplikacji. Aby go nadpisać musisz stworzyć swój domyślny moduł.
$ cd apps/frontend/modules $ mkdir default $ cd default $ mkdir templates $ cd templates
Utwórz tam plik indexSuccess.php i wstaw do niego ładną wiadomość powitalną:
<h1>Welcome to my swell weblog</h1> <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
Sprawdź rezultat odświeżając stronę domową:
http://localhost/sf_sandbox/web/frontend_dev.php/
http://www.symfony-project.com/images/tutorials/first_welcome.gif
No dalej, zacznij używać twoją nową aplikację. Dodaj nowe posty i komentarze do nich.
Więcej informacji o szablonach templates, i konfiguracji widoku view configuration
Przekazywanie danych z akcji do szablonu
To było szybkie czyż nie? Nadszedł czas żeby połączyć moduł comment z modułem post tak aby komentarze były wyświetlane pod postem którego dotyczą. Najpierw musisz sprawić żeby komentarze były dostępne dla szablonu wyświetlającego post. W symfony ten rodzaj logiki jest przechowywany w akcjach. Edytuj plik akcji sf_sandbox/apps/frontend/modules/post/actions/actions.class.php i zmień metodę executeShow() przez dodanie czterech środkowych linii:
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);
}
Obiekty Criteria i Peer są częścią Propela-mapowania obiektowo-relacyjnego. Najprościej mówiąc te cztery linie wykonują zapytanie SQL z tabeli comment aby pobrać komentarze powiązane z konkretnym postem (przekazywanym w parametrze URL nazwanym 'id'). Linia $this->comments wewnątrz akcji daję dostęp do zmiennej $comments wewnątrz szablonu. Teraz zmień szablon wyświetlający Post sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php przez dodanie na końcu:
...
<?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 ?>
Ta strona używa nowych funkcji (format_date() i simple_format_text()) dostarczanych przez symfony i nazywanymi 'helpers' ponieważ wykonują one dla ciebie niektóre zadania, które normalnie potrzebowały by więcej kodu i czasu. Dodaj nowy komentarz do pierwszego postu potem znowu zobacz swój post poprzez kliknięcie na jego numer na liście albo po prostu napisz:
http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1
http://www.symfony-project.com/images/tutorials/first_comments_under_post.gif
Zobacz więcej o naming conventions powiązaniu akcji z szablonami, oraz z text i date helpers
Dodawanie rekordów odpowiadających innej tabeli
Kiedy dodajesz komentarz musisz wybrać id odpowiadającego postu. To nie jest zbyt user-firendly. Zmieńmy to i upewnijmy się że użytkownik powróci do posta po jego skomentowaniu.
Najpierw w pliku szablonu modules/post/templates/showSuccess.php dodaj linię na końcu:
<?php echo link_to('Add a comment','comment/create?post_id='.$post->getId()) ?>
Helper link_to() dodaje link do akcji edit w module comment więc możesz teraz dodać komentarz bezpośrednio ze strony wyświetlającej post. Następnie edytuj szablon modules/comment/templates/editSuccess.php i zamień następujące linie:
<tr> <th>Post*:</th> <td><?php echo object_select_tag($comment, 'getPostId', array ( 'related_class' => 'Post', )) ?></td> </tr>
Przez:
<?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 ?>
Formularz na stronie comment/edit wywołuje akcję comment/update która przekierowuje na stronę comment/show (to jest domyślne ustawienie generowanych CRUD). W naszej aplikacji znaczy to że jeżeli dodamy komentarz do postu to zobaczymy ten komentarz. Lepszym rozwiązaniem jest wyświetlenie postu z listą komentarzy pod spodem. Więc edytuj plik modules/comment/actions/actions.class.php i znajdź metodę executeUpdate(). Zobacz że pole created_at nie jest zdefiniowane w akcji, symfony wie że pole nazwane created_at ma być ustawione na datę systemową kiedy tworzony jest rekord. Przekierowanie musi być zmienione aby pokazywało na post a nie na comment (zmień linie redirect):
public function executeUpdate ()
{
$comment = $this->getCommentOrCreate();
$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());
}
Użytkownicy mogą teraz bezpośrednio dodawać komentarze do postów i wracać do nich po dodaniu komentarza. Chciałeś webloga i masz webloga.
Walidacja formularzy
Użytkownicy mogą dodawać komentarze, ale co jeśli wyślą formularz bez żadnych danych? Będziesz miał pustą bazę danych. Aby temu zapobiec dodaj plik update.yml w katalogu sf_sandbox/apps/frontend/modules/comment/validate/ (musisz też dodać katalog) i napisz w nim:
methods:
post: [author, email, body]
get: [author, email, body]
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.
Pamiętaj jakie zasady są w YAML, tylko spację są akceptowane plik też musi się zaczynać od 'methods'.
Kontroler przekieruje użytkownika do szablonu updateError.php jeżeli błąd zostanie wykryty. Będzie lepiej gdy wyświetlimy formularz ponownie z odpowiednim komunikatem błędu. Aby to zrobić dodaj metodę handleError do klasy modules/comment/actions/actions.class.php:
public function handleError()
{
$this->forward('comment', 'edit');
}
aby zakończyć otwórz jeszcze raz modules/comment/templates/editSuccess.php i wstaw na początku:
<?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 ?>
Teraz masz silny formularz:
http://www.symfony-project.com/images/tutorials/first_form_validation.gif
Czytaj więcej o form validation
Zmiana sposobu wyświetlania URL-i
Zauważyłeś jak są wyświetlane URLe. Możesz je zrobić trochę bardziej user i search-engine frendly. Użyjmy tytułu postu jako URLa do niego.
Problemem jest to że tytuł może mieć znaki specjalne jak spację. Jeżeli po prostu opuścisz je URL może pokazywać brzydki znaki jak %20, lepiej będzie jeżeli rozszerzymy model o nowe metody pobierające czyste tytuły postów. Aby to zrobić edytuj plik Post.php w katalogu sf_sandbox/lib/model/ i dodaj takie metody:
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;
}
Teraz możesz dodać akcję permalink do modułu post:
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');
}
W pliku modules/post/templates/listSuccess.php usuń komórkę id z tabeli i zmień komórke title z :
<td><?php echo $post->getTitle() ?></td>
na:
<td><?php echo link_to($post->getTitle(), '/'.$sf_last_module.'/permalink?title='.$post->getStrippedTitle()) ?></td>
Jeszcze jeden krok edytuj plik routing.yml w katalogu sf_sandbox/apps/frontend/config/ i dodaj te reguły na górze:
list_of_posts:
url: /latest_posts
param: { module: post, action: list }
post:
url: /weblog/:title
param: { module: post, action: permalink }
Teraz zobacz jeszcze raz swoją aplikację i sprawdź URLe:
http://www.symfony-project.com/images/tutorials/first_routing.gif
Czytaj więcej o smart URLs
Porządki w frontend
Więc jeśli to jest weblog to każdy ma prawo do pisania postów. To nie jest to o co nam chodziło do końca. Wyczyśćmy nasze szablony troszeczkę. W szablonie modules/post/templates/showSuccess.php usuń link do akcji edit:
<?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>
To samo zrób w szablonie modules/post/templates/listSuccess.php :
<?php echo link_to('create', 'post/edit') ?>
Jeszcze usuń następujące metody w klasie modules/post/actions/actions.class.php:
* executeEdit * executeUpdate * executeDelete * getPostOrCreate
Ok. czytający nie będą teraz mogli dodawać postów nigdy więcej.
Generowanie Bekend-u
Abyś mógł dodawać posty wygenerujmy dla ciebie aplikację beckend. Przez linie komend wykonaj polecenia (pamiętaj abyś był w katalogu sf_sandbox):
$ symfony init-app backend $ symfony propel-init-admin backend post Post $ symfony propel-init-admin backend comment Comment
Tym razem użyjemy admin-generator oferuję on dużo więcej możliwości niż prosy CRUD generator.
Tak jak to robiłeś dla frontend edytuj plik apps/backend/template/layout.php aby ustawić globalna nawigację.
<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 $content ?>
</div>
Ponieważ używasz sf_sandbox musisz skopiować jeszcze katalog sf_sandbox/web/sf/images/sf_admin/ do sf/images/sf_admin/ w katalogu główny serwera www, tak są ustawione ścieżki do ikonek i plików css.
Możesz teraz zobaczyć twoją aplikacje administracyjną przez adres:
http://localhost/sf_sandbox/web/backend_dev.php/post
http://www.symfony-project.com/images/tutorials/first_basic_admin.gif
Możesz w prosty sposób zarządzać twoim modułem administracyjnym z poziomu jednego pliku. Edytuj plik backend/modules/post/config/generator.yml i wstaw tam :
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 }
Pomiędzy kolumnami tabeli Post admin generator szuka nb_comments. Nie jest to nigdzie jeszcze napisane. Ale łatwo jest dodać tą funkcjonalność. Po prostu dodaj do sf_sandbox/lib/model/Post.php:
public function getNbComments()
{
return count($this->getComments());
}
Teraz odśwież administrację postami i zobacz zmiany:
http://www.symfony-project.com/images/tutorials/first_custom_admin.gif
Blokada dostępu do Beckendu
Beckend może być odwiedzany przez wszystkich. Dodaj logowanie do zablokowania nieupoważnionym dostępu.
W katalogu apps/backend/modules/post/config/ dodaj plik security.yml z następującym kodem:
all: is_secure: on
Powtórz tą operację dla modułu Comment. Teraz nie możesz się dostać do tych modułów dopóki sie nie zalogujesz. Ale akcja logowania jeszcze nie istnieje więc dodaj teraz nowy moduł security:
$ symfony init-module backend security
Nowy moduł będzie użyty do logowania użytkowników. Edytuj apps/backend/modules/security/templates/indexSuccess.php aby stworzyć formularz logowania:
<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>
Dodaj akcję login którą wywołuje formularz w klasie apps/backend/modules/security/actions/actions.class.php
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');
}
}
Ostatnia rzeczą do zrobienia jest ustawienie modułu security jako domyślnego modułu do przechwytywania akcji logowania. Aby to zrobić otwórz apps/backend/config/settings.yml i dodaj:
all:
.actions:
login_module: security
login_action: index
Od tego czasu będziesz proszony o podanie loginu i hasła
http://www.symfony-project.com/images/tutorials/first_login.gif
Czytaj więcej o security
Podsumowanie
Ok godzina minęła. Udało ci się. teraz możesz używać obu aplikacji w środowisku produkcyjnym:
frontend: http://localhost/sf_sandbox/web/index.php/ backend: http://localhost/sf_sandbox/web/backend.php/
Jeżeli zobaczysz błędy w tym momencie musisz wyczyścić cache, nie jest on używany w środowisku development. Poprostu wywołaj z linii komend:
$ symfony cc
Zobacz aplikacja jest szybka i gładka. Poczuj sie wolny przeglądaj kod, dodawaj nowe moduły i zmieniaj wygląd stron.
I nie zapomnij aby pokazać swoja działającą aplikacje na symfony wiki.

