29 de dezembro de 2009

Aceder a configurações gerais a partir de uma Task (v.1.2)

Como já devem saber, para criar configurações que não estejam dependentes da aplicação, basta criar um ficheiro "app.yml" dentro da directoria "config" geral. Normalmente só existe dentro do config de cada aplicação.

Usando o formato yml habitual conseguimos ter configurações disponíveis para todas as aplicações. Por exemplo (app.yml):
all:
  minha_variavel:  12345


Acontece que para uma task a coisa não funciona! Apesar da nossa configuração ser genérica e independente da aplicação, não conseguimos ir buscar com:

sfConfig::get('app_minha_variavel','meu_valor_default');

O que falta então?

Ao correr a task seria necessário indicar aque aplicação estamos a utilizar com o parametro:

php symfony MINHATASK --application="frontend"

por exemplo.

Visto que na maior parte dos casos não me interessa nada saber qual a aplicação, até porque as configurações são genéricas, mais vale definir um valor default. Na função configure() alterar a linha:

new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'),

Personalizar mensagens de erro nos formulários

Em symfony 1.4

No ficheiro ProjectConfiguration.class.php:
class FrontendConfiguration extends sfApplicationConfiguration {
public function configure()
{
sfValidatorBase::setDefaultMessage('required', 'This field is required.');
// etc etc
}


Em versões anteriores à 1.3

No ficheiro ProjectConfiguration.class.php:
class FrontendConfiguration extends sfApplicationConfiguration {
public function configure()
{
sfValidatorBase::setRequiredMessage("This field is required.");
sfValidatorBase::setInvalidMessage("This field is Invalid.");
// etc etc
}
}

21 de dezembro de 2009

Doctrine Autocomplete ou Autocompletion

Para quem estava habituado a desenvolver as suas aplicações recorrendo a ferramentas como o Netbeans ou o Eclipse, usando Propel, a passagem para Doctrine desiludiu no que toca ao desaparecimento das preciosas ajudas do "CTRL+Espaço" fornecidadas pelo IDE... Isto porque o Propel gerava todas as classes e métodos get/set para acesso aos objectos de tal forma que o IDE os podia interpretar...
Mas nem tudo está perdido... pode ser que haja uma esperança para o Doctrine... veja-se este post:

"Setting values on model classes is also a bit different: Doctrine uses magic properties, while Propel generates methods for setting and getting values. This gives Propel code the advantage of autocompletion in more IDE’s - as far as I know, only the latest versions of NetBeans can autocomplete Doctrine’s magic properties, thanks to their support of the @property PHPDoc annotation."

16 de dezembro de 2009

Estranho Timestampable Behaviour..

Quando no schema.yml definimos o Timestampable Behaviour (ver este post), os campos created_at e updated_at aparecem no formulário desse objecto.
Como não queremos ter que preencher esses campos manualmente cada vez que estamos a editar ou adicionar algo, temos que fazer o unset dos mesmos na classe do formulário desse objecto.

Exemplo:
Em: lib/form/doctrine/CategoryFormClass.php
public function configure() {
unset($this->widgetSchema['created_at']);
unset($this->validatorSchema['created_at']);

unset($this->widgetSchema['updated_at']);
unset($this->validatorSchema['updated_at']);
}
É necessário fazer essa alteração em todas as classes de formulários...

Alternativamente pode-se alterar directamente o ficheiro lib/form/doctrine/BaseFormDoctrine.class.php que esses campos não apareçam em nenhum formulário.
public function setup(){
unset($this->widgetSchema['created_at']); 
unset($this->validatorSchema['created_at']);

unset($this->widgetSchema['updated_at']); 
unset($this->validatorSchema['updated_at']); 
}

EDIT:
Para simplificar também se pode fazer o seguinte:
...
unset($this['created_at']);
unset($this['updated_at']);
...

11 de dezembro de 2009

Apagar ficheiros desnecessários do lib

Por vezes, enquanto estamos a acertar o modelo fazemos varias vezes build, e certamente que alguns ficheiros desnecessários vão ficar perdidos pelas paths lib/filter/, lib/model/ e lib/form/.

Como os únicos ficheiros que nos interessam são os que estão no schema.yml, com o seguinte comando podemos apagar de uma só vez todas as classes desnecessárias:
php symfony doctrine:clean
Exemplo:
[/sfdir/trunk]$ php symfony doctrine:clean --no-confirmation
>> file+ /private/var/tmp/doctrine_schema_18134.yml
>> doctrine Deleting "Article" files
>> file- /sfdir/trunk/lib/filter/doctrine/base/BaseArticleFormFilter.class.php
>> file- /sfdir/trunk/lib/filter/doctrine/ArticleFormFilter.class.php
>> file- /sfdir/trunk/lib/form/doctrine/base/BaseArticleForm.class.php
>> file- /sfdir/trunk/lib/form/doctrine/ArticleForm.class.php
>> file- /sfdir/trunk/lib/model/doctrine/base/BaseArticle.class.php
>> file- /sfdir/trunk/lib/model/doctrine/ArticleTable.class.php
>> file- /sfdir/trunk/lib/model/doctrine/Article.class.php
>> doctrine Deleting "Category" files
...

Caso pretendam limpar TODAS as classes do projecto, limpem o ficheiro schema.yml e corram o comando acima.

Definições gerais de tabelas, relacionamentos automáticos e Behaviors

Definir Collation e Charset para todas as tabelas

Há determinadas definições de classes/tabelas que por vezes queremos generalizar. Uma delas é, concerteza, a collation e o charset das tabelas.

Para não termos que repetir todas essas definições em cada tabela basta colocar o seguinte no topo do ficheiro config/doctrine/schema.yml:
options:
collate: utf8_unicode_ci
charset: utf8



Relacionamentos automáticos entre tabelas
(Apenas para tabelas do tipo InnoDB)

Com Doctrine, é possivel relacionar automaticamente tabelas uma vez que através dos nomes das colunas é detectado se existe tabela associada a esse nome.

Para tal, basta definir o seguinte atributo no topo do ficheiro config/doctrine/schema.yml:
detect_relations: true
No exemplo seguinte, a tabela Entity relaciona com a tabela Category através do campo category_id:
Category:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(255), notnull: true }

Entity:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(255), notnull: true }
category_id: { type: integer(4) }
Como o campo category_id tem o mesmo nome da tabela Category, o relacionamento é automaticamente detectado.

Para ler mais sobre sintax abreviada em Doctrine sigam este link:
http://www.doctrine-project.org/documentation/manual/1_2/en/yaml-schema-files#abbreviated-syntax



Behaviors

Timestampable - gera automaticamente campos created_at e updated_at

Estes campos também são gerados automaticamente utilizando o atributo Timestampable na definição da classe. Assim, para gerarmos esses campos para a tabela Category:
Category:
actAs: { Timestampable: ~ }
columns:
...
Sluggable - gera automaticamete campo slug

Com este behavior o campo slug é gerado automaticamente para um ou vários campos duma tabela. Quando o objecto é guardado, a respectiva slug user friendly é criada:
Glossary:
actAs:
Sluggable:
unique: true
fields: [word]
canUpdate: true
columns:
word: { type: string(255), notnull: true, unique: true }
...

Mais sobre behaviors:
http://www.doctrine-project.org/documentation/manual/1_2/en/behaviors

Definir tipo Enum com Doctrine

Utilizando o Doctrine podemos definir facilmente o tipo Enum para qualquer classe.

Para que tal seja possível temos que acrescentar o atributo use_native_enum no nosso ficheiro de configuração da base de dados em config/databases.yml:

all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=symfony12doctrine'
username: user
attributes:
use_native_enum: true
Agora, no ficheiro config/doctrine/schema.yml:
Contact:
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(255) }
gender:
type: enum
values: ['M','F']
default: M

E o resultado em SQL será algo do género:
CREATE TABLE `contact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`gender` enum('M','F') COLLATE utf8_unicode_ci DEFAULT 'M',
PRIMARY KEY (`id`)
) ENGINE=InnoDB

Para mais informações sobre os data types suportados pelo Doctrine/Sql sigam este link:

http://www.symfony-project.org/doctrine/1_2/en/04-Schema-Files

7 de dezembro de 2009

Imagens, css e javascripts de um novo projecto.

Depois de iniciar-mos um projeto no symfony pelo comando init-project o directório web/ não tem as imagens, css e javascripts que possivelmente vamos precisar.

Todos esses ficheiros estão na sandbox, por isso guardei todos os ficheiros da sandbox num directório e criei o seguinte script para automaticamente copiar todos os ficheiros necessários:
#!/bin/bash
STR="/Applications/XAMPP/xamppfiles/bin/sf_sandbox/web"
TO="${PWD}/web"

echo "* Copying css, js and image files from sandbox to working directory..."
echo

#### css
CSS_FROM="${STR}/css/main.css"
CSS_TO="${TO}/css/"
echo "> ${CSS_FROM} ==> ${CSS_TO}"
cp $CSS_FROM $CSS_TO
echo "* Copied 'main.css'"

#### sf
SF_FROM="${STR}/sf/"
SF_TO="${TO}/sf/"
echo "> ${SF_FROM} ==> ${SF_TO}"
echo "* 'sf' directory created at ${SF_TO}"
cp -R $SF_FROM $SF_TO

#### js
JS_FROM="/Applications/XAMPP/xamppfiles/bin/sf_sandbox/lib/symfony/plugins/sfProtoculousPlugin/web/js/prototype.js"
JS_TO="${SF_TO}js/"
mkdir -p "${JS_TO}"
echo "> ${JS_FROM} ==> ${JS_TO}"
cp $JS_FROM $JS_TO
echo "* Copied 'prototype.js'"

echo
echo "* All done."


Alterem as variáveis STR e JS_FROM para os vossos directórios e guardem o script com o nome sfsandboximgs e com permissão de execução (chmod +x sfsandboximgs).

Agora, é mais fácil criar um projecto symfony com todos os ficheiros necessários no directório web:
symfony init-project <nome_do_projecto>
./sfsandboximgs

2 de dezembro de 2009

Utilizar diferentes separadores de segmentos nos routings

Por defeito os separadores de segmentos de routing no symfony são: '/' e '.'

Para utilizar um conjunto mais alargado de caracteres separadores de segmentos basta:

Editar o ficheiro: apps/app/config/factories.yml
all:
routing:
class: sfPatternRouting
param:
generate_shortest_url: true
extra_parameters_as_query_string: true
segment_separators: [/, ., -]
Neste exemplo, foi adicionado o separador "-".

É agora possivel criar routings do género:
course:
url: /formacao/cursos-:sub_category/:entity/curso-:course/:id