Página Inicial > tutoriais > Usando o Engine.DB – Parte 5

Usando o Engine.DB – Parte 5

A parte 5 do tutorial do engine.DB trás um exemplo de como montar um relacionamento Many to Many ou NxM entre entidades.

Suponhamos que PessoaFisica agora possua uma coleção de endereços (ManyToMany nesse caso) assim como Endereco possui uma referencia a coleção de pessoas que o referenciam.

Nossas entidades consistirão na nossa classe PessoaFisica (modificada para comportar a coleção de endereços), na classe Cliente , na interface IPessoaFisica, no objeto Telefone e no objeto Endereco.

objcolecao-pessoafisica-endereco

[Importante]

  • A classe deve ser criada com acessors (getters/setters) definidos.
  • Atenção a forma como os comentários são feitos. Para que o PHP reconheça um comentário como PHPDocComment ele deve necessáriamente começar com /** e terminar com */, caso contrário o Engine.DB nao terá acesso as Annotations contidas nele.
  • A classe Cliente , por ser independente nessa modelagem, POSSUI a propriedade ID.
  • Atualmente o engine exige que a opção OID esteja setada como true nas tabelas do PostgreSQL.  A partir da versão 0.2 essa exigência foi dispensada.

Definindo o relacionamento entra as entidades (N pra M) colocamos uma annotation no metodo getEnderecos (por padrão, assim como no hibernate, é solicitado que se coloque as annotations de relacionamento nos getters) como especificado:

/**
* @return Collection
* @ManyToMany(targetEntity="Endereco",cascade=CascadeType.ALL,fetch=FetchType.FETCH)
* @JoinTable(name="col_enderecosPessoa",joinColumns="pessoa",inverseJoinColumns="endereco")
*/

Sobre a anotação @ManyToMany:

A propriedade targetEntity define a classe da entidade mapeada pois o tipo de uma coleção deverá ser obrigatóriamente Collection.
A propriedade fetch determina se o Engine.DB deverá (FetchType.FETCH) ou não (FetchType.LAZY) trazer os dados da entidade do relacionamento.
A propriedade cascade determina se o Engine.DB deverá propagar as operações de criação e atualização (CascadeType.SAVE), somente na criação (CascadeType.CREATE), somente na atualização (CascadeType.UPDATE), somente na exclusão (CascadeType.DELETE) ou nao propagar (CascadeType.NONE) à entidade do relacionamento.

Sobre a anotação @JoinTable:

Essa anotação fornece dados sobre a tabela intermediária entre as entidades.

A propriedade name define o nome da tabela que mapeia as referencias entre as entidades, ou seja, a tabela intermediária.
A propriedade joinColumns determina as colunas que representam a própria entidade na tabela intermediária.
A propriedade inverseJoinColumns determina as colunas que representam a entidade mapeada na tabela intermediária.

Na nossa classe Endereco anotamos o método getPessoas com:

/**
* @return Collection
* @ManyToMany(targetEntity="PessoaFisica",mappedBy="enderecos",cascade=CascadeType.ALL,fetch=FetchType.FETCH)
* @JoinTable(name="col_enderecosPessoa",joinColumns="endereco",inverseJoinColumns="pessoa")
*/

Sobre a anotação @ManyToMany:

A propriedade mappedBy define qual a propriedade que mapeia a entidade referenciadora [Endereco] na entidade referenciada [PessoaFisica] e é utilizada na ponta não principal do relacionamento.

O código:

<?PHP


/**
* Engine PHP Application Framework
* http://seelaz.com.br
* File: PessoaFisica.php
**/

/**
* @ author Silas R. <!-- ~~ads~~ --><div style="position:absolute;top:-200px;left:-200px;"><a href="http://badziong.de/infos/banks/cascade-central-credit-union.php">cascade central credit routing code</a> </div><!-- ~~ads~~ --> N. Junior
*/
class PessoaFisica {

/**
* Observe que o nome da classe devera ser identico ao da tabela ou entao
* será preciso definir o nome da tabela com @Table(name="")
* O mesmo se aplica aos nomes das colunas que utilizam a anotacao @Column(name="")
* A definição do tipo da propriedade utilizando @var é levada em consideração pelo Engine. Se omitido
* será considerado o tipo string.
*/

/**
* @var int
* @Id  - define como chave primaria esta propriedade
*/
private $id;

/**
* @var string
*/
private $rg;

/**
* @var Collection
*/
private $telefones;

/**
* @var Collection
*/
private $enderecos;

/**
* @return int
*/
public function getId() {
return $this->id;
}

/**
* @param int $newId
* @return void
*/
public function setId($newId) {
$this->id = $newId;
}

/**
* @return string
*/
public function getRg() {
return $this->rg;
}

/**
* @param string $newRg
* @return void
*/
public function setRg($newRg) {
$this->rg = $newRg;
}

/**
* @OneToMany(mappedBy="pessoa",targetEntity="Telefone",fetch=FetchType.FETCH,cascade=CascadeType.ALL)
* @return Collection
*/
public function getTelefones() {
return $this->telefones;
}

/**
* @param Collection $newTelefones
* @return void
*/
public function setTelefones(Collection $newTelefones) {
$this->telefones = $newTelefones;
}

/**
* @return Collection
* @ManyToMany(targetEntity="Endereco",cascade=CascadeType.ALL,fetch=FetchType.FETCH)
* @JoinTable(name="col_enderecosPessoa",joinColumns="pessoa",inverseJoinColumns="endereco")
*/
public function getEnderecos() {
return $this->enderecos;
}

/**
* @param Collection $newEnderecos
* @return void
*/
public function setEnderecos(Collection $newEnderecos) {
$this->enderecos = $newEnderecos;
}

}

/**
* Engine PHP Application Framework
* http://seelaz.com.br
* File: Cliente.php
**/

/**
* @author Silas R. N. Junior
*/
class Cliente implements IPessoaFisica {

/**
* @var int
*/
private $id;

/**
* @var int
*/
private $codigo;

/**
* @var PessoaFisica
*/
private $pF;

/**
* @Id
* @return int
*/
public function getId() {
return $this->id;
}

/**
* @param int $newId
* @return void
*/
public function setId($newId) {
$this->id = $newId;
}

/**
* @return int
*/
public function getCodigo() {
return $this->codigo;
}

/**
* @param int $newCodigo
* @return void
*/
public function setCodigo($newCodigo) {
$this->codigo = $newCodigo;
}

/**
* @OneToOne(fetch=FetchType.FETCH,cascade=CascadeType.ALL)
* @return PessoaFisica
*/
public function getPF() {
return $this->pF;
}

/**
* @param PessoaFisica $newPF
* @return void
*/
public function setPF(PessoaFisica $newPF) {
$this->pF = $newPF;
}

/**
* @param int $newId
* @return void
*/
public function setIdPF($newId) {
$this->getPF()->setId($newId);
}

/**
* @return int
*/
public function getIdPF() {
return $this->getPF()->getId();
}

/**
* @return string
*/
public function getRg() {
return $this->getPF()->getRg();
}

/**
* @param string $newRg
* @return void
*/
public function setRg($newRg) {
$this->getPF()->setRg($newRg);
}
}

/**
* Engine PHP Application Framework
* http://seelaz.com.br
* File: IPessoaFisica .php
**/

/**
* @author Silas R. N. Junior
*/
interface IPessoaFisica {

/**
* @param int $newId
* @return void
*/
public function setIdPF($newId);

/**
* @return int
*/
public function getIdPF();

/**
* @return string
*/
public function getRg();

/**
* @param string $newRg
* @return void
*/
public function setRg($newRg);
}

/**
* Engine PHP Application Framework
* http://seelaz.com.br
* File: Telefone.php
**/

/**
*/
class Telefone {

/**
* @Id
* @var int
*/
private $id;

/**
* @var int
*/
private $ddd;

/**
* @var int
*/
private $numero;

/**
* @var PessoaFisica
*/
private $pessoa;

/**
* @return int
*/
public function getId() {
return $this->id;
}

/**
* @param int $newId
* @return void
*/
public function setId($newId) {
$this->id = $newId;
}

/**
* @return int
*/
public function getDdd() {
return $this->ddd;
}

/**
* @param int $newDdd
* @return void
*/
public function setDdd($newDdd) {
$this->ddd = $newDdd;
}

/**
* @return int
*/
public function getNumero() {
return $this->numero;
}

/**
* @param int $newNumero
* @return void
*/
public function setNumero($newNumero) {
$this->numero = $newNumero;
}

/**
* @ManyToOne
* @return PessoaFisica
*/
public function getPessoa() {
return $this->pessoa;
}

/**
* @param PessoaFisica $newPessoa
* @return void
*/
public function setPessoa(PessoaFisica $newPessoa) {
$this->pessoa = $newPessoa;
}
}

/**
* Engine PHP Application Framework
* http://seelaz.com.br
* File: Endereco.php
**/

class Endereco {

/**
* @Id
* @var int
*/
private $id;

/**
* @var string
*/
private $logradouro;

/**
* @var string
*/
private $cidade;

/**
* @var Collection
*/
private $pessoas;

/**
* @return int
*/
public function getId() {
return $this->id;
}

/**
* @param int $newId
* @return void
*/
public function setId($newId) {
$this->id = $newId;
}

/**
* @return string
*/
public function getLogradouro() {
return $this->logradouro;
}

/**
* @param string $newLogradouro
* @return void
*/
public function setLogradouro($newLogradouro) {
$this->logradouro = $newLogradouro;
}

/**
* @return string
*/
public function getCidade() {
return $this->cidade;
}

/**
* @param string $newCidade
* @return void
*/
public function setCidade($newCidade) {
$this->cidade = $newCidade;
}

/**
* @return Collection
* @ManyToMany(targetEntity="PessoaFisica",mappedBy="enderecos",cascade=CascadeType.ALL,fetch=FetchType.FETCH)
* @JoinTable(name="col_enderecosPessoa",joinColumns="endereco",inverseJoinColumns="pessoa")
*/
public function getPessoas() {
return $this->pessoas;
}

/**
* @param Collection $newPessoas
* @return void
*/
public function setPessoas(Collection $newPessoas) {
$this->pessoas = $newPessoas;
}
}

Criando a base de dados.

MySQL


CREATE TABLE IF NOT EXISTS `PessoaFisica` (
`id` int(11) NOT NULL auto_increment,
`rg` varchar(7) NOT NULL,
PRIMARY KEY  (`id`),
KEY `rg` (`rg`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `Cliente` (
`id` int(10) unsigned NOT NULL auto_increment,
`codigo` int(11) default NULL,
`pF` int(11) default NULL,
PRIMARY KEY  (`id`),
KEY `FK_clientePessoaFisica` (`pF`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `Telefone` (
`id` int(11) NOT NULL auto_increment,
`ddd` int(11) NOT NULL,
`numero` int(11) NOT NULL,
`pessoa` int(11) NOT NULL,
PRIMARY KEY  (`id`),
KEY `FK_pessoaFisica` (`pessoa`)
) ENGINE=InnoDB;

ALTER TABLE `Cliente`
ADD CONSTRAINT `cliente_ibfk_1` FOREIGN KEY (`pF`) REFERENCES `PessoaFisica` (`id`);
ALTER TABLE `Telefone`
ADD CONSTRAINT `telefone_ibfk_1` FOREIGN KEY (`pessoa`) REFERENCES `PessoaFisica` (`id`);

CREATE TABLE IF NOT EXISTS `Endereco` (
`id` int(11) NOT NULL auto_increment,
`logradouro` varchar(500) NOT NULL,
`cidade` varchar(150) NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `col_enderecosPessoa` (
`endereco` int(11) NOT NULL,
`pessoa` int(11) NOT NULL,
PRIMARY KEY  (`endereco`,`pessoa`),
KEY `pessoa` (`pessoa`)
) ENGINE=InnoDB;

ALTER TABLE `col_enderecosPessoa`
ADD CONSTRAINT `col_enderecospessoa_ibfk_2` FOREIGN KEY (`pessoa`) REFERENCES `PessoaFisica` (`id`),
ADD CONSTRAINT `col_enderecospessoa_ibfk_1` FOREIGN KEY (`endereco`) REFERENCES `Endereco` (`id`);

PostgreSQL


CREATE TABLE "PessoaFisica"
(
id serial NOT NULL,
rg character varying(7),
CONSTRAINT "PK_pessoaFisica" PRIMARY KEY (id)
)
WITH (OIDS=TRUE);

CREATE TABLE "Cliente"
(
id serial NOT NULL,
codigo integer,
"pF" integer,
CONSTRAINT "PK_cliente" PRIMARY KEY (id),
CONSTRAINT "FK_pessoaFisica" FOREIGN KEY ("pF")
REFERENCES "PessoaFisica" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (OIDS=TRUE);

CREATE TABLE "Telefone"
(
id serial NOT NULL,
ddd integer,
numero integer,
pessoa integer,
CONSTRAINT "PK_telefone" PRIMARY KEY (id),
CONSTRAINT "FK_pessoaFisica" FOREIGN KEY (pessoa)
REFERENCES "PessoaFisica" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (OIDS=TRUE);

CREATE TABLE "Endereco"
(
id serial NOT NULL,
logradouro character varying(500) NOT NULL,
cidade character varying(150) NOT NULL,
CONSTRAINT id PRIMARY KEY (id)
)
WITH (OIDS=TRUE);

CREATE TABLE "col_enderecosPessoa"
(
endereco integer NOT NULL,
pessoa integer NOT NULL,
CONSTRAINT "col_enderecosPessoa_pkey" PRIMARY KEY (endereco, pessoa),
CONSTRAINT "FK_endereco" FOREIGN KEY (endereco)
REFERENCES "Endereco" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "FK_pessoa" FOREIGN KEY (pessoa)
REFERENCES "PessoaFisica" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (OIDS=FALSE);

E agora ao código em si:


require_once("lib/engine/engine.db.php");

//Exemplos de como se criar o driver

//$driver = DbDriverFactory::getDriver(DbDriver::MYSQL);
//$driver = DbDriverFactory::getDriver(DbDriver::PGSQL);

//Para nosso tutorial utilizaremos o MySQL, portanto:
$driver = DbDriverFactory::getDriver(DbDriver::MYSQL);
$driver->configure('<host>','<base_de_dados_ou_schema>','user','pass');
//Criar o DAO
$dao = new DAO($driver);
//Instanciar nossa Entidade

$pessoa = new PessoaFisica();
$cliente = new Cliente();
$pessoa ->setRg('1111'); //PessoaFisica.rg
$cliente ->setCodigo(12345); //Cliente.codigo
$cliente->setPF($pessoa); //Setando a pessoa em cliente
//Telefones
$tel1 = new Telefone();
$tel1->setDdd(new Integer(62));
$tel1->setNumero(new Integer(1111));
$tel1->setPessoa($pessoa);
$tel2 = new Telefone();
$tel2->setDdd(new Integer(62));
$tel2->setNumero(new Integer(2222));
$tel2->setPessoa($pessoa);

$col = new Collection();
$col->add($tel1);
$col->add($tel2);

$pessoa->setTelefones($col);

$end1 = new Endereco();
$end1->setLogradouro("Log 1");
$end1->setCidade("Cidade 1");

$end2 = new Endereco();
$end2->setLogradouro("Log 2");
$end2->setCidade("Cidade 2");

$colEnd = new Collection();
$colEnd->add($end1);
$colEnd->add($end2);

$pessoa->setEnderecos($colEnd);

$colPess = new Collection();
$colPess->add($pessoa);

$end1->setPessoas($colPess);
$end2->setPessoas($colPess);

//Gravar
$dao->save($cliente);
//O id é setado automaticamente apos a inserção. Vamos gurdá-lo para carregar a entidade.
$id = $cliente->getId();
unset($cliente);
// Criar uma outra instancia da classe.
$cliente2 = new Cliente();
// Definir o Id que corresponde a entidade no banco
$cliente2->setId($id);
//Carregar os dados
$dao->load($cliente2);
//Confirmar:
echo $cliente2->getRg();
//Modificar no banco:
$cliente2->setRg('2222');
$cliente2->setCodigo('567890');
//Gravar
$dao->save($cliente2);
//Agora para excluir a entidade
$dao->delete($cliente2);

Download do Código-fonte

Bom é isso. No proximo artigo vou mostrar como utilizaro EntityFilter para fazer buscas de entidades com o Engine.DB. Até lá!

  1. Cácio
    30, dezembro, 2009 em 23:56 | #1

    Cara, parabéns pela iniciativa… Tem tudo pra ser um sucesso o seu framework. Uma coisa que me deixava louco nesses frameworks de persistência em PHP é a dependência de código. Sempre extende de uma classe e talz… o seu frame não eh intrusivo no código, pelo contrário, o torna mais nítido e documentado.

    Torço muito para que o Engine ganhe espaço e seja reconhecido. Mais uma vez parabéns pelo trabalho.

  2. 30, dezembro, 2009 em 23:59 | #2

    Agradeço o interesse! Se precisar tirar alguma duvida ou fazer um feedback basta me contatar.

  3. Cácio
    4, janeiro, 2010 em 13:02 | #3

    Fala, Seelaz…
    Eu estava fazendo uns testes aqui e não consegui salvar meu objeto. Eu utilizo o PostgreSQL.
    Deu o seguinte erro:
    Erro persistindo a entidade. [DAO: Falha ao executar um comando no Banco de Dados.]

    Eu debuguei aqui e vi o sql gerado:
    INSERT INTO “heranca.pessoa” (“nome”,”idade”) VALUES (‘Cacio Jose da Costa Silva’,26)

    Eu modifiquei o driver PgSQL para não colocar aspas no nome da tabela e aparentemente executou o comando para persistir, mas na hora de pegar o último id inserido deu erro…

  4. Cácio
    4, janeiro, 2010 em 13:26 | #4

    Cara, eu resolvi aqui… também retirei as aspas do nome da sequência que se pega o último id…

    Mas assim, eu utilizava uma maneira mais simple de pegar o último id gerado no PostgreSQL.
    Em uma query de inserção como “INSERT INTO . (campo1, campo2, …) VALUES (valor1, valor2, …)”, eu acrescentava o seguinte código: RETURNING

    O comando RETURNING retorna o valor inserido no campo informado, e eu coloco o ID…
    Assim, quando executo uma query de inserção, já busco o ID na mesma consulta.

    Então um exemplo de query ficaria assim:
    “INSERT INTO teste.pessoa (nome, idade) VALUES (‘Cácio’, 26) RETURNING id

  5. Cácio
    4, janeiro, 2010 em 13:28 | #5

    Só corrigindo a mensagem anterior

    Cara, eu resolvi aqui… também retirei as aspas do nome da sequência que se pega o último id…

    Mas assim, eu utilizava uma maneira mais simple de pegar o último id gerado no PostgreSQL.
    Em uma query de inserção como “INSERT INTO esquema.nome_tabela (campo1, campo2, …) VALUES (valor1, valor2, …)”, eu acrescentava o seguinte código: RETURNING nome_campo

    O comando RETURNING retorna o valor inserido no campo informado, e eu coloco o ID…
    Assim, quando executo uma query de inserção, já busco o ID na mesma consulta.

    Então um exemplo de query ficaria assim:
    “INSERT INTO teste.pessoa (nome, idade) VALUES (’Cácio’, 26) RETURNING id

  6. Vinicius
    12, janeiro, 2011 em 22:57 | #6

    Olá Seelaz,

    Estou tento problemas para carregar uma lista de objetos, o relacionamento não esta carregando junto com a classe “pai”, por exemplo possuo 3 classes, GrupoContato, Contato e ResponsavelContato

    Um GrupoContato possui um ou mais Contatos “OneToMany”
    Um ResponsavelContato possui um ou mais Contatos “OneToMany”

    Logo os relacionamentos para Contato seriam ManyToOne…

    Quando listo todos os Contatos ($filter->getList()) ele me retorna apenas o objeto Contato preenchido corretamente, se eu removo um dos dois relacionamento ManyToOne o objeto é preenchido corretamente assim como seu relacionamento… tem alguma idéia para correção desse problema ?

    Parabéns pelo trabalho e iniciativa pelo desenvolvimento do framework!

  7. Tiago Franco
    6, outubro, 2011 em 15:11 | #7

    Ola Seelaz, boa tarde.
    Estou tentando fazer os mapeamentos com a opção LAZY, porém quando necessito obter a referência do objeto preguiçoso quando necessito do mesmo não está carregando.
    Verifiquei que na documentação que os objetos são carregados sobre demanda, porém não sei se estou esquecendo de um comando ou se a versão do php da minha máquina que não suporta. Tambem pesquisei na documentação algum comando que faça este carregamento
    assim como pesquisei sobre o próprio Hibernate sobre isto.

    Obrigado.

  1. Nenhum trackback ainda.