czwartek, 11 września 2008

Symfony krok 5

W pliku /apps/frontend/config/routing.yml można ustawić która strona zostaje wyświetlona jako "index" naszej aplikacji:


homepage:
url: /
param: { module: question, action: list }

wtorek, 9 września 2008

Symfony user managmet and sessions

Sesje użytkowników są przechowywane w symfony w obiekcie User. Możemy pobrać ten obiekt w dowolnym miejscu za pomocą metody getUser();


class mymoduleActions extends sfActions
{
public function executeFirstPage()
{
$nickname = $this->getRequestParameter('nickname');

// Store data in the user session
$this->getUser()->setAttribute('nickname', $nickname);
}

public function executeSecondPage()
{
// Retrieve data from the user session with a default value
$nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
}
}


Oczywiście w tym obiekcie możemy przechowywać obiekty (niezalecane) i zmienne które będą dostępne pomiędzy wywołaniami stron.

Zmienne możemy ustawiać, pobierać i czyścić


class mymoduleActions extends sfActions
{
public function executeRemoveNickname()
{
$this->getUser()->getAttributeHolder()->remove('nickname');
}

public function executeCleanup()
{
$this->getUser()->getAttributeHolder()->clear();
}
}


Do sesji użytkownika możemy dobrać się także z szablonów



Hello, getAttribute('nickname') ?>




Jeśli potrzebujemy przechować informację tylko do kolejnego wywołania, np potwierdzenie zapisania/usunięcia/edycji danych możemy skorzystać z metody setFlash i getFlash. Dane zapisane za pomocą tych metod zostaną usunięte przy kolejnym wywołaniu.
np.

$this->setFlash('attrib', $value);
$value = $this->getFlash('attrib');

// i w szablonach

has('attrib')): ?>
get('attrib') ?>



Ciasteczka

W symfony możemy decydować gdzie zapisane są ciasteczka. Określamy to w pliku apps/myapp/config/factories.yml


all:
storage:
class: sfSessionStorage
param:
session_name: my_cookie_name


Po stronie aplikacji sesje są przechowywane domyślnie w plikach. Możemy to zmienić w pliku apps/myapp/config/factories.yml


all:
storage:
class: sfMySQLSessionStorage
param:
db_table: SESSION_TABLE_NAME # Name of the table storing the sessions
database: DATABASE_CONNECTION # Name of the database connection to use


Czas przechowywania sesji można ustawić w pliku apps/myapp/config/settings.yml


default:
.settings:
timeout: 1800 # Session lifetime in seconds

Symfony krok 4

W niektórych przypadkach potrzebujemy tylko wysłać nagłówek:


public function executeRefresh()
{
$output = '<"title","My basic letter"],["name","Mr Brown">';
$this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');

return sfView::HEADER_ONLY;
}


lub wybrać szablon o dowolnej nazwie


$this->setTemplate('myCustomTemplate');


Przydatnymi metodami są także forward, redirect i forward404 (nie można znaleźć strony. Dzięki nim możemy przejść do innej akcji w innym module lub załadować dowolną stronę.


$this->forward('otherModule', 'index');

$this->redirect('otherModule/index');
$this->redirect('http://www.google.com/');

/**
* szablon do strony 404 znajduje się w $sf_symfony_data_dir/modules/default/
* możemy także nadpisać akcje error404 w dowolnym module, oraz dołączyć do modułu
*/
forward404();
/**
* istnieje tez kilka funkcji pomocniczych które ułatwią nam pracę
*/
forwardIf();
forwardUnless();
forward404If();
forward404Unless();
redirectIf();
redirectUnless();

$this->forward404If(!$article);


Jeśli mamy czynność którą musimy powtórzyć przed / po wykonaniu każdej akcji możemy utworzyć metody preExecute() i postExecute()

public function preExecute()
{
// The code inserted here is executed at the beginning of each action call
...
}

public function executeIndex()
{
...
}

public function executeList()
{
...
$this->myCustomMethod(); // Methods of the action class are accessible
}

public function postExecute()
{
// The code inserted here is executed at the end of each action call
...
}


Funkcje pomocne przy wykonywaniu akcji:


Name Function Sample Output
Request Information
getMethod() Request method Returns sfRequest::GET or sfRequest::POST constants
getMethodName() Request method name 'POST'
getHttpHeader('Server') Value of a given HTTP header 'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6'
getCookie('foo') Value of a named cookie 'bar'
isXmlHttpRequest()* Is it an Ajax request? true
isSecure() Is it an SSL request? true
Request Parameters
hasParameter('foo') Is a parameter present in the request? true
getParameter('foo') Value of a named parameter 'bar'
getParameterHolder()->getAll() Array of all request parameters
URI-Related Information
getUri() Full URI 'http://localhost/myapp_dev.php/mymodule/myaction'
getPathInfo() Path info '/mymodule/myaction'
getReferer()** Referrer 'http://localhost/myapp_dev.php/'
getHost() Host name 'localhost'
getScriptName() Front controller path and name 'myapp_dev.php'
Client Browser Information
getLanguages() Array of accepted languages Array( [0] => fr [1] => fr_FR [2] => en_US [3] => en )
getCharsets() Array of accepted charsets Array( [0] => ISO-8859-1 [1] => UTF-8 [2] => * )
getAcceptableContentTypes() Array of accepted content types Array( [0] => text/xml [1] => text/html



Przykłady wykorzystania:

pobieranie parametrów:

class mymoduleActions extends sfActions
{
public function executeIndex()
{
$hasFoo = $this->getRequest()->hasParameter('foo');
$hasFoo = $this->hasRequestParameter('foo'); // Shorter version
$foo = $this->getRequest()->getParameter('foo');
$foo = $this->getRequestParameter('foo'); // Shorter version
}
}


upload plików:


class mymoduleActions extends sfActions
{
public function executeUpload()
{
if ($this->getRequest()->hasFiles())
{
foreach ($this->getRequest()->getFileNames() as $uploadedFile)
{
$fileName = $this->getRequest()->getFileName($uploadedFile);
$fileSize = $this->getRequest()->getFileSize($uploadedFile);
$fileType = $this->getRequest()->getFileType($uploadedFile);
$fileError = $this->getRequest()->hasFileError($uploadedFile);
$uploadDir = sfConfig::get('sf_upload_dir');
$this->getRequest()->moveFile($uploadedFile, $uploadDir.'/'.$fileName);
}
}
}
}

niedziela, 7 września 2008

Symfony krok 3

Uruchomienie kodu symfony z lini poleceń:



define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..'));
define('SF_APP', 'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG', false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

// add code here


Front controller to coś co przyjmuje zapytania i interpretuje je. Można dodać nowy kontroler, ale mnie interesują tylko dwa kontrolery, które już są zdefiniowane. Mianowicie:


http://localhost/index.php/mymodule/index
http://localhost/mymodule/index

i

http://localhost/myapp_dev.php/mymodule/index


Nazewnictwo klas akcji

Nazwa klasy musi zaczynać się od nazwy modułu z małej litery i kończyć się na Actions. Klasa musi być umieszczona w katalogu pps/myapp/modules/mymodule/actions/ a plik musi nazywać się actions.class.php

Każda metoda przynosząca nowe akcje musi zaczynać się od execute, a cała klasa musi dziedziczyć po sfActions.


class mymoduleActions extends sfActions
{
public function executeIndex()
{

}
}


Nazwa wywołania url jest wzięta z nazwy funkcji w ten sposób że jeśli utworzymy funkcję executeList poprawnym url dla tej akcji jest

http://localhost/myapp_dev.php/mymodule/list
lub
http://localhost/mymodule/list

Możemy również tworzyć całe klasy dla poszczególnych akcji. Wtedy musimy dziedziczyć z klasy sfAction i tworzyć klasy o nazwie NazwaAkcjiAction.

np.
myapp/modules/mymodule/actions/indexAction.class.php


class indexAction extends sfAction
{
public function execute()
{
...
}
}


myapp/modules/mymodule/actions/listAction.class.php


class listAction extends sfAction
{
public function execute()
{
...
}
}


Pobieranie informacji o wywołaniu:


class mymoduleActions extends sfActions
{
public function executeIndex()
{
// Retrieving request parameters
$password = $this->getRequestParameter('password');

// Retrieving controller information
$moduleName = $this->getModuleName();
$actionName = $this->getActionName();

// Retrieving framework core objects
$request = $this->getRequest();
$userSession = $this->getUser();
$response = $this->getResponse();
$controller = $this->getController();
$context = $this->getContext();

// Setting action variables to pass information to the template
$this->setVar('foo', 'bar');
$this->foo = 'bar'; // Shorter version

}
}


Templates

Jeśli akcję zakończymy return sfView::SUCCESS;, lub jeśli nie podamy return do wywołania zostanie dołączony szablon actionNameSuccess.php. Możemy także zwrócić sfView::ERROR wtedy zostanie dołączony szablon actionNameError.php, ogólnie symfony szuka w szablonach pliku zwróconego prze return 'MyResult' + prefix nazwa akcji.

Można też:


return sfView::NONE;


i


$this->getResponse()->setContent("Hello, World!");

return sfView::NONE;


lub


return $this->renderText("Hello, World!");

sobota, 6 września 2008

Symfony krok 2

Definicja bazy danych jest przechowywana w pliku config/schema.yml lub config/schema.xml.
Przykładowy plik xml może wyglądać następująco:


<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
<table name="ask_question" phpName="Question">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="user_id" type="integer" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="title" type="longvarchar" />
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
<column name="updated_at" type="timestamp" />
</table>

<table name="ask_answer" phpName="Answer">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="question_id" type="integer" />
<foreign-key foreignTable="ask_question">
<reference local="question_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
</table>

<table name="ask_user" phpName="User">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="nickname" type="varchar" size="50" />
<column name="first_name" type="varchar" size="100" />
<column name="last_name" type="varchar" size="100" />
<column name="created_at" type="timestamp" />
</table>

<table name="ask_interest" phpName="Interest">
<column name="question_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_question">
<reference local="question_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="created_at" type="timestamp" />
</table>

<table name="ask_relevancy" phpName="Relevancy">
<column name="answer_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_answer">
<reference local="answer_id" foreign="id"/>
</foreign-key>
<column name="user_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="score" type="integer" />
<column name="created_at" type="timestamp" />
</table>

</database>


Jeśli chcemy wygenerować plik konfiguracyjny z istniejącej bazy danych wystarczy w pliku config/propel.ini
wyedytować linie:

propel.database.createUrl = mysql://marian@localhost/
propel.database.url = mysql://marian@localhost/askeet

i wydać komendę:

$ symfony propel-build-schema

Dla mnie najprzyjemniejszym narzędziem go graficznego tworzenia baz danych jest wtyczka Clay do Eclipse.
Aby wygenerować klasy reprezentujące tabele w bazie danych (na podstawie pliku xml lub yml) wystarczy wydać polecenie:

$ symfony propel-build-model

Aby symfony mogło połączyć się do bazy danych trzeba wyedytować plik config/database.yml (nie mylić z konfiguracją pluginu propel):

all:
propel:
class: sfPropelDatabase
param:
dsn: mysql://marian@localhost/askeet

! Ważna uwaga: w plikach yml trzeba używać spacji zamiast tabulatorów.

Jeśli nie mamy gotowej bazy danych możemy wygenerować skrytp sql z konfiguracji schema.xml lub schema.yml:

$ symfony propel-build-sql
$ mysql -u youruser -p askeet <> lub
$ symfony propel-insert-sql

Kolejnym krokiem jest wygenerowanie formularzy do operacji CRUD (miło szybko zobaczyć coś działającego):

// ciekawe że trzeba wykonać ten krok w symfony 1.1 (w askeet tutorial ten krok jest pominięty gdyż tutorial traktuje o symfony 1.0)
$ symfony propel:build-forms

// no i właściwy proces generowania
$ symfony propel-generate-crud frontend question Question

czwartek, 4 września 2008

System ratownictwa PCK - możliwości usprawnień

Od dłuższego czasu część mojego wolnego czasu poświęcam na wolontariat w grupie ratownictwa PCK w Opolu. Szkolenia dotyczące klęsk żywiołowych, jak i rozmowy z ratownikami dłużej pracującymi w tej formacji przyniosły mi kilka pytań co do możliwości usprawnienia pracy ratowników przy pomocy nowoczesnej technologi.

Jak wygląda akcja ratownicza w obecnym systemie.
W wypadku niespodziewanego kataklizmu czy wypadku zaczyna się od zebrania jak największej ilości informacji o zdarzeniu (jego miejscu, ilości poszkodowanych, warunkach panujących na miejscu zdarzenia itp).
Począwszy od tego miejsca można by wprowadzić usprawnienia które przyśpieszyły by ten proces.
Najważniejsza według mnie jest łączność pomiędzy ratownikami (tymi zbierającymi informacje jak i niosącymi doraźną pomoc poszkodowanym) a kierownictwem akcji ratowniczej. Owszem takowa już istnieje (łączność radiowa na odpowiednio przydzielonych do tego kanałach radiowych) lecz polega ona tylko na głosowym informowaniu kierownictwa. Według mnie powinna istnieć możliwość przesyłania do kierownictwa informacji o położeniu które ciężko opisać słownie.

W terenie gdzie wynikła jakaś katastrofa (np. rozległych budynkach, czy całych miejscowościach) gdzie potrzebna jest pomoc na większym obszarze robione są mapki z rozłożeniem poszkodowanych, obiektów, miejsc niebezpiecznych itp. Problem polega na tym że informacje przynoszone do "centrum dowodzenia" nie są najdokładniejsze. No bo nie każdy jest w stanie prawidłowo określić odległości i w dodatku w czytelny sposób nanieść je na kartkę papieru. Dlaczego nie wykorzystać do tego celu technologi GPS?

Przypuśćmy że w każdej grupie ratowniczej wysyłanej w teren znalazła by się osoba posiadająca odbiornik GPS, mogący w dowolnym momencie połączyć się do "centrum dowodzenia" i wysłać informacje o bieżącej lokalizacji ratowników. Idąc o krok dalej powinna istnieć możliwość zaznaczenia przez ratownika lokalizacji budynków, poszkodowanych i niebezpiecznych miejsc przy pomocy GPS i przesłania ich do kierownictwa akcji. Dane od różnych grup mogły by być połączone w centralnym komputerze i wyświetlone kierującemu akcją. Dało by mu to przybliżony obraz sytuacji i możliwość podjęcia decyzji o kierunku dalszych działań (np. gdzie wysłać więcej ratowników, ile przygotować środków potrzebnych do kontynuacji akcji itp).

Wiedza, marzenia, pomysły

To niesamowite ile pomysłów chodzi mi po głowie.
Część przychodzi sama z siebie, gdy odpoczywam, słucham muzyki, czy pracuje.
Inne wpadają gdy rozwiązuję jakiś problem czy rozważam usprawnienie jakiejś pracy.
Wszystko to kłębi się w głowie i dojrzewa, część zapisuję, rozważam i szukam sposobów realizacji. Szkoda że nie mam wystarczającej wiedzy w niektórych dziedzinach aby zacząć pracę nad ich realizacją. Szukamy wyjścia - może kolejne studia, np. automatyka i robotyka. A może wystarczy przeczytać kilka książek o rozważanych problemach, lub zostać słuchaczem na interesujących wykładach?

Na pewno zacznę publikować "owoce" moich przemyśleń. A nuż ktoś podpowie mi jak najlepiej zabrać się do ich realizacji.

Ostatnio spotkałem się z opinią że pracujemy i tworzymy po to aby osiągać z tego wymierne zyski. W moim przypadku chyba nie do końca o to chodzi. Samo tworzenie jest dla mnie ogromną frajdą.

Więcej usystematyzowanej wiedzy - to jest rozwiązanie.

środa, 3 września 2008

Nazwa dla nowego projektu...

niby nic trudnego, wystarczy przegrepować słownik (np z kurnikowego słownika) wyrażeniem regularnym

może tak:


marian@miranda:~/programming/bazy> grep -E '^s.*p.*j.*r.*;' thesaurus-1.5.utf8.txt
spisek;tajne porozumienie;zmowa


hmmm, raczej nie o to mi chodziło - chyba pozostanę przy dłuższej nazwie :)

wtorek, 2 września 2008

symfony i SuSe

Instalacja.

Najprościej skorzystać z PEAR (jest dostępny w repozytorium yast)
Następnie wystarczy wydać komendę:

> pear channel-discover pear.symfony-project.com
> pear remote-list -c symfony
> pear install symfony/symfony

Tworzenie projektu:

> mkdir myproject
> cd myproject
> symfony generate:app frontend
> symfony generate:project myproject
> symfony generate:module frontend content

Ponadto aby testować stronę z dowolnym url musimy dodać wpis do virtualnych hostów.
W suse wystarczy dodać plik z rozszerzeniem .conf do katalogu /etc/apache2/vhosts.d


<virtualhost>
ServerName przykladowa-aplikacja.pl
DocumentRoot "/home/marian/przykladowa-aplikacja/web"
DirectoryIndex index.php
Alias /sf "/usr/share/php5/PEAR/data/symfony/web/sf"
<directory>
AllowOverride All
Allow from All
</directory>
<directory>
AllowOverride All
Allow from All
</directory>
</virtualhost>


Musimy również dodać do /etc/hosts

127.0.0.1 przykladowa-aplikacja.pl