Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Olá pessoal,
Eu estou criando um sistema novo de e-commerce e neste sistema eu terei várias categorias e subcategorias (categorias pais e filhas).
Eu ainda não trabalhei desta maneira e por isso estou com algumas dúvidas. No meu sistema antigo de e-commerce eu tinha departamentos, seções, categorias e subcategorias, ou seja, não era apenas categorias pais e filhas. Porém, deste modo a forma de trabalhar com a aplicação ficava muito "engessada" e então resolvi fazer diferente, mas estou com algumas dúvidas principalmente com relação a OOP.
Vou postar minha classe Application_Model_Category pra ficar mais fácil de entender a minha dúvida, ela tem este prefixo "Application_Model" porque estou trabalhando com o ZF.
class Application_Model_Category
{
protected $id;
protected $name;
protected $slug;
protected $parent;
protected $dateAdded;
protected $dateModified;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
public function getSlug() {
return $this->slug;
}
public function setSlug($slug) {
$this->slug = $slug;
}
public function getParent() {
return $this->parent;
}
public function setParent(Application_Model_Category $parent) {
$this->parent = $parent;
}
public function getDateAdded() {
return $this->dateAdded;
}
public function setDateAdded($dateAdded) {
$this->dateAdded = $dateAdded;
}
public function getDateModified() {
return $this->dateModified;
}
public function setDateModified($dateModified) {
$this->dateModified = $dateModified;
}
}
Como podem observar, a propriedade "parent" irá receber um objeto desta mesma classe, que seria o objeto da categoria pai.
O problema é que podem existir N categorias filhas, um exemplo: "Eletrônicos -> Televisores -> LED -> 40 polegadas", a categoria "Eletrônicos" é a principal, a "Televisores" filha da "Eletrônicos" e assim por diante.
A principal dúvida que estou tendo no momento é de qual seria a melhor maneira de recuperar estes 4 objetos pra então conseguir pegar os IDs de cada categoria e salvar o relacionamento com os produtos por exemplo no banco de dados.
Espero ter sido claro e agradeço qualquer sugestão. :)
Eu estava relendo o post anterior e acho que não fui bem claro com a minha dúvida, vou tentar explicar melhor.
Vamos supor que eu cadastre um produto na categoria "40 polegadas" (do exemplo que eu citei acima), no meu controller eu vou recuperar o objeto desta categoria, ficaria mais ou menos assim:
// Este ID é recebido via POST
$categoryId = 1234;
// Crio um objeto da classe Category
$category = new Application_Model_Category();
// Chamo a classe mapper pra recuperar os dados do objeto no banco de dados
$categoryMapper = new Application_Model_CategoryMapper();
// Passo o ID da categoria e o objeto Category
$categoryMapper->find($categoryId, $category);
Ok, agora tenho o objeto $category que possui o ID 1234, nome "40 polegadas" e a classe pai "LED".
Eu tenho aí 4 objetos, "40 polegadas", a categoria pai "LED", a categoria pai "Televisores" e por último a categoria pai "Eletrônicos".
No banco de dados eu preciso salvar o ID destas 4 categorias na tabela "categories_products", qual seria a melhor maneira de eu recuperar estes 4 objetos?
PS: Não sei ao certo se isso é mesmo um caso de composição (como eu postei no título) ou se seria agregação.
EDIT:
Fala João, beleza?
Eu acho que é isso que você postou mesmo, eu ainda estou estudando UML e não entendo muito desses diagramas. Mas eu expliquei melhor a dúvida agora, vê se você consegue entender. :)
Se eu soubesse o número exato de categorias que estão sendo usadas eu faria assim para recuperar o ID de cada uma delas:
$category->getId(); // ID da categoria 40 polegadas
$category->getParent()->getId(); // ID da categoria LED
$category->getParent()->getParent()->getId(); // ID da categoria Televisores
$category->getParent()->getParent()->getParent()->getId(); // ID da categoria Eletrônicos
O problema é que podem ser N categorias e subcategorias, daí não sei como pegar todos os objetos.
PS: Eu já tinha visto a mensagem por e-mail e agora vi de novo aqui no fórum, mas não sei se vai dar pra eu participar do evento. :(
>
Eu tenho aí 4 objetos, "40 polegadas", a categoria pai "LED", a categoria pai "Televisores" e por último a categoria pai "Eletrônicos".
No banco de dados eu preciso salvar o ID destas 4 categorias na tabela "categories_products", qual seria a melhor maneira de eu recuperar estes 4 objetos?
Ok, vamos lá:
Sua classe Application_Model_Category pode comportar-se como uma lista encadeada, como você fez:
<?php
class Category {
private $child;
private $name;
public function __construct( $name = null ) {
$this->setName( $name );
}
public function child() {
return $this->child;
}
public function getName() {
return $this->name;
}
public function setChild( Category $child ) {
$this->child = $child;
}
public function setName( $name ) {
$this->name = $name;
}
}
Com isso, sua View poderia Iterar os filhos da categoria:
<?php
class CategoryView {
public function show( Category $category ) {
echo '<ul>' , PHP_EOL;
do {
echo '<li>' , $category->getName() , '</li>' , PHP_EOL;
} while ( ( $category = $category->child() ) != null );
echo '</ul>';
}
}
<?php
$eletronicos = new Category( 'Eletrônicos' );
$televisores = new Category( 'Televisores' );
$led = new Category( 'LED' );
$eletronicos->setChild( $televisores );
$televisores->setChild( $led );
$view = new CategoryView();
$view->show( $eletronicos );
Saída:
<ul>
<li>Eletrônicos</li>
<li>Televisores</li>
<li>LED</li>
</ul>
Porém, uma categoria pode ter N filhos, então uma simples lista encadeada não resolveria seu problema. Ela teria que ser:
<?php
class Category implements IteratorAggregate {
private $children = array();
private $name;
public function __construct( $name = null ) {
$this->setName( $name );
}
public function addChild( Category $child ) {
$this->children[] = $child;
}
public function getIterator() {
return new ArrayIterator( $this->children );
}
public function getName() {
return $this->name;
}
public function hasChildren() {
return count( $this->children ) > 0;
}
public function setName( $name ) {
$this->name = $name;
}
}
Com isso, a View ficaria:
<?php
class CategoryView {
public function show( Category $category ) {
echo $category->getName();
if ( $category->hasChildren() ) {
echo '<ul>';
foreach ( $category as $child ) {
echo '<li>';
$this->show( $child );
echo '</li>';
}
echo '</ul>';
}
}
}
<?php
$eletronicos = new Category( 'Eletrônicos' );
$eletronicos->addChild( $televisores = new Category( 'Televisores' ) );
$eletronicos->addChild( $computadores = new Category( 'Computadores' ) );
$eletronicos->addChild( $mobile = new Category( 'Dispositivos Móveis' ) );
$televisores->addChild( new Category( 'LED' ) );
$televisores->addChild( new Category( 'LCD' ) );
$computadores->addChild( new Category( 'Notebook' ) );
$computadores->addChild( new Category( 'Desktop' ) );
$computadores->addChild( new Category( 'Workstation' ) );
$mobile->addChild( new Category( 'Smartphone' ) );
$mobile->addChild( new Category( 'Tablet' ) );
$view = new CategoryView();
$view->show( $eletronicos );
Saída:
Eletrônicos
<ul>
<li>Televisores
<ul>
<li>LED</li>
<li>LCD</li>
</ul>
</li>
<li>Computadores
<ul>
<li>Notebook</li>
<li>Desktop</li>
<li>Workstation</li>
</ul>
</li>
<li>Dispositivos Móveis
<ul>
<li>Smartphone</li>
<li>Tablet</li>
</ul>
</li>
</ul>
Ok, a parte fácil já foi, o problema agora é recuperar isso do banco. Se você estivesse trabalhando com um banco orientado a documentos, tudo seria mais simples. Mas no banco relacional você pode fazer assim:
CREATE TABLE `Category` (
`idCategory` int(10) unsigned NOT NULL AUTO_INCREMENT,
`categoryName` varchar(45) NOT NULL,
`categoryParent` int(10) unsigned NOT NULL,
PRIMARY KEY (`idCategory`)
);
A entidade Category ficaria assim:
<?php
/**
* Entidade que representa uma categoria
*/
class Category implements IteratorAggregate {
/**
* @var string
*/
private $categoryName;
/**
* @var integer
*/
private $categoryParent;
/**
* @var array
*/
private $children = array();
/**
* @var integer
*/
private $idCategory;
/**
* Adiciona uma categoria filha à essa categoria
* @param Category $child
*/
public function addChild( Category $child ) {
$this->children[] = $child;
}
/**
* Recupera o ID da categoria.
* @return integer
*/
public function getId() {
return (int) $this->idCategory;
}
/**
* Recupera um Iterator com as categorias filhas.
* @return Iterator
* @see IteratorAggregate::getIterator()
*/
public function getIterator() {
return new ArrayIterator( $this->children );
}
/**
* Recupera o nome da categoria.
* @return string
*/
public function getName() {
return $this->categoryName;
}
/**
* Recupera o ID da categoria pai.
* @return integer
*/
public function getParent() {
return (int) $this->categoryParent;
}
/**
* Verifica se a categoria possui filhos.
* @return boolean
*/
public function hasChildren() {
return count( $this->children ) > 0;
}
}
A model que recuperará essas categorias ficaria:
<?php
/**
* Model de categorias
*/
class CategoryModel {
/**
* @var PDO
*/
private $pdo;
/**
* Constroi o objeto da Model de categorias. Por motivo de simplificação do
* exemplo, a CategoryModel depende de um objeto PDO.
* @param PDO $pdo
*/
public function __construct( PDO $pdo ) {
$this->pdo = $pdo;
}
/**
* Recupera a hierarquia de categorias.
* @return Category
*/
public function getCategoryHierarchy() {
$nullCategory = new Category();
$categories = array( $nullCategory );
$stm = $this->pdo->prepare( 'SELECT * FROM `Category`;' );
$stm->setFetchMode( PDO::FETCH_CLASS , 'Category' );
$stm->execute();
foreach ( $stm->fetchAll() as $category ) {
$parent = $category->getParent();
$categories[ $category->getId() ] = $category;
if ( isset( $categories[ $parent ] ) ) {
$categories[ $parent ]->addChild( $category );
}
}
return $nullCategory;
}
}
E a View é exatamente a mesma coisa:
<?php
/**
* View de categorias.
*/
class CategoryView {
/**
* Mostra a hierarquia de categorias.
* @param Category $category A categoria de nível mais alto.
*/
public function show( Category $category ) {
echo $category->getName();
if ( $category->hasChildren() ) {
echo '<ul>';
foreach ( $category as $child ) {
echo '<li>';
$this->show( $child );
echo '</li>';
}
echo '</ul>';
}
}
}
Inserimos alguns dados na base:
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (1,'Eletrônicos',0);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (2,'Televisores',1);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (3,'Computadores',1);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (4,'Dispositivos Móveis',1);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (5,'LED',2);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (6,'LCD',2);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (7,'Notebook',3);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (8,'Desktop',3);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (9,'Workstation',3);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (10,'Smartphone',4);
INSERT INTO `Category` (idCategory,categoryName,categoryParent) VALUES (11,'Tablet',4);
E testamos:
<?php
$model = new CategoryModel(
new PDO( 'mysql:host=127.0.0.1;dbname=test' , 'user' , 'pswd' )
);
$view = new CategoryView();
$view->show( $model->getCategoryHierarchy() );
Saída:
<ul>
<li>Eletrônicos
<ul>
<li>Televisores
<ul>
<li>LED</li>
<li>LCD</li>
</ul>
</li>
<li>Computadores
<ul>
<li>Notebook</li>
<li>Desktop</li>
<li>Workstation</li>
</ul>
</li>
<li>Dispositivos Móveis
<ul>
<li>Smartphone</li>
<li>Tablet</li>
</ul>
</li>
</ul>
</li>
</ul>
;)
>
PS: Não sei ao certo se isso é mesmo um caso de composição (como eu postei no título) ou se seria agregação.
A diferença entre agregação e composição é a força da associação:
Roda agrega um carro :seta: Se você remover a roda do carro, o carro não vai explodir.
Cabeça compõe a pessoa :seta: Se você cortar a cabeça, a pessoa vai morrer.
Nossa João, nem sei como te agradecer, como sempre dando uma verdadeira aula. Fico imaginando o tempo que você perde se dispondo a entender o problema dos outros e depois pra explicar de forma tão didática com a única intenção de ajudar o próximo. Parabéns brother! :clap:
Eu estava dando uma olhada lá no manual do PHP nestas classes de iteração mas confesso que não sabia nem por onde começar (só tinha usado RecursiveDirectoryIterator pra trabalhar com arquivos e diretórios até agora), mas agora vou estudar aqui o que você passou.
Depois posto o resultado aqui.
Um abraço!
>
Nossa João, nem sei como te agradecer, como sempre dando uma verdadeira aula.
^_^
>
Fico imaginando o tempo que você perde se dispondo a entender o problema dos outros e depois pra explicar de forma tão didática com a única intenção de ajudar o próximo. Parabéns brother! :clap:
tkz,
Na verdade, eu gostaria de poder responder em TODOS os tópicos, mas como, infelizmente, isso não é possível devido ao tempo, escolho algum interessante e procuro responder da melhor forma possível.
>
Eu estava dando uma olhada lá no manual do PHP nestas classes de iteração mas confesso que não sabia nem por onde começar
O melhor começo é compreender o design pattern. Na área de cursos eu cheguei a falar um pouquinho sobre Iterator e Aggregate.
>
Depois posto o resultado aqui.
Um abraço!
ok,
;)
Cara, acho que tem um erro conceitual na sua nomenclatura aqui:
Application_Model_Category deveria se referir ao Modelo da Aplicação, ou seja, a lógica de negócios da sua aplicação, operações, algoritmos, etc.
Ao invés disso, você esta usando para representar os dados existentes na aplicação, o que é chamado de Domain Model, ou Modelo de Domínio.
Dentro da camada ApplicationModel você pode ter um DataMapper para operações de CRUD entre outras coisas condizentes com a sua aplicação.
Dentro da camada DomainModel, você pode usar um DomainObject para representar seus dados.
Não vejo nenhum problema na nomenclatura da classe, pois como eu disse estou trabalhando com o ZF e ela está nomeada dessa forma para que o autoload funcione sem nenhum problema no framework.
E sim, esta classe é um Domain Model e além dela eu possuo mais duas classes relacionadas diretamente com categoria, a Application_Model_DbTable_Categories (Data Table) e a Application_Model_CategoryMapper que é responsável por fazer o mapemento entre os objetos da classe Application_Model_Category e a tabela no banco de dados (Data Table). ;)
Leozitho,
Apenas para confirmar, é isso que você quer fazer?
/applications/core/interface/imageproxy/imageproxy.php?img=http://forum.imasters.com.br/uploads//1314458547/gallery_94216_5_5553.png&key=d2d82d83e25141c30aaf75ec94243a4e07fc30e86c9c9996ff58546e07195e21" alt="gallery_94216_5_5553.png">
PS: Lhe enviei uma MP outro dia, você chegou a vê-la?