Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Estou estudando php e por consequência orientação objetos. Nesses últimos dias, me deparei com os princípios do SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) e tive algumas dúvidas. Como por exemplo, na tentativa de criar um "registration system", não consegui implementar os conceitos do SOLID, principalmente o SRP e OCP.
Com isso me deparei com algumas dúvidas, por exemplo:
Como escolher os nomes das classes e suas funções (objetivo, etc) ?
Como relacionar os objetos (banco de dados, usuario, etc) ?
Como deve ser a estrutura de classes(ex: DataBase, User, User Service) para um 'registration system' ou similar ?
Como saber se estou violando ou não os principios do SOLID (dicas, etc) ?
Como deveria ser implementada as classes abaixo, principalmente a DataBase ?
Segue a tentativa:
DataBase.class.php
<?php
require_once 'config.php';
class DataBase {
private $DB;
private $table = 'cadastro';
public function __construct() {
$this->DB = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
}
public function insertUser($name, $lastname, $email, $password) {
$sql = "INSERT INTO {$this->table} (name, lastname, email, password) VALUES (:name, :lastname, :email, :password)";
$stmt = $this->DB->prepare($sql);
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->bindParam(':lastname', $lastname, PDO::PARAM_STR);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->bindParam(':password', $password, PDO::PARAM_STR);
return $stmt->execute();
}
public function findUserByEmail($email) {
//...
}
public function findAll() {
//...
}
//...
}
?>
User.class.php
<?php
class User {
private $id;
private $name;
private $lastname;
private $email;
private $password;
public function __construct($name = null, $lastname = null, $email = null, $password = null) {
$this->name = $name;
$this->lastname = $lastname;
$this->email = $email;
$this->password = $password;
}
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 getLastname() {
return $this->lastname;
}
public function setLastname($lastname) {
$this->lastname = $lastname;
}
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
public function getPassword() {
return $this->password;
}
public function setPassword($password) {
$this->password = $password;
}
}
?>
UserAction.class.php
<?php
class UserAction {
public function register($user, $database) {
return $database->insertUser($user->getName(), $user->getLastname(), $user->getEmail(), $user->getPassword());
}
/*
public function login($user, $database) {
//...
} */
//...
}
?>
register.php
<?php
require_once 'autoload.php';
$name = (isset($_POST['name']))?$_POST['name']:null;
$lastname = (isset($_POST['lastname']))?$_POST['lastname']:null;
$email = (isset($_POST['email']))?$_POST['email']:null;
$password = (isset($_POST['password']))?$_POST['password']:null;
$user = new User($name, $lastname, $email, $password);
$database = new DataBase();
$action = new UserAction();
$action->register($user, $database);
//...
?>
Se puderem me indicar materias ou exemplos para prática ficarei grato.
Obrigado !
Eu tinha começado a escrever um textão ontem, mas fiquei com sono e não terminei. O Gabriel falou basicamente tudo que eu iria falar (e de forma bem mais resumida :lol:).
Uma última dica que eu dou é a seguinte:
Use isso como guia.
DataBase->insertUserjá é uma violação, responsabilidade no lugar errado.
A tua class User é como um VO (ValueObject) que eu considero um desperdício de memória, por estar indo para o lado do ActiveRecord.
Eu teria um DAO e pronto.
>
O TableDataGateway também é bem interessante. Ele é mais simples que o DataMapper, tem uma granularidade menor (o que é muito bom) e mantém a mesma finalidade, desacoplar o SGBD. O Henrique Barcelos escreveu um bom artigo sobre o padrão.
http://imasters.com.br/linguagens/php/padroes-tabledatagateway-e-tablerowgateway-teoria-e-pratica/
Uma última dica que eu dou é a seguinte:
Use isso como guia.
A tua class User é como um VO (ValueObject) que eu considero um desperdício de memória, por estar indo para o lado do ActiveRecord.
Eu teria um DAO e pronto.
index.php
<?php
require_once 'config.php';
require_once 'autoload.php';
$db = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
$userDAO = new UserDAO($db);
$user = new User('Fulano', 'Silva', 'fulanosilva@email.com', '123456');
$userDAO->insert($user);
?>
IDAO.class.php
<?php
interface IDAO {
public function findById($pk);
public function listAll();
public function insert($object);
public function update($object);
public function delete($pk);
}
?>
obs: A interface aqui é necessária ? Me pareceu correto ser implementada....
UserDAO.class.php
<?php
require_once 'IDAO.class.php';
class UserDAO implements IDAO {
private $db;
private $table = 'cadastro';
public function __construct($db) {
$this->db = $db;
}
public function findByPk($pk) {
//code
}
public function listAll() {
//code
}
public function insert($object) {
$sql = "INSERT INTO {$this->table} (name, lastname, email, password) VALUES (:name, :lastname, :email, :password)";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':name', $object->getName());
$stmt->bindParam(':lastname', $object->getLastname());
$stmt->bindParam(':email', $object->getEmail());
$stmt->bindParam(':password', sha1(md5($object->getPassword())));
return $stmt->execute();
}
public function update($object) {
//code
}
public function delete($pk) {
//code
}
}
?>
User.class.php
<?php
class User {
private $id;
private $name;
private $lastname;
private $email;
private $password;
public function __construct($name = null, $lastname = null, $email = null, $password = null) {
$this->name = $name;
$this->lastname = $lastname;
$this->email = $email;
$this->password = $password;
}
//gets e sets
//...
}
?>
Ainda sobre DAO, para cada classe (User, Client, etc, etc...) terei que ter um DAO referente ?
Obrigado a todos por enquanto, estou lendo e revendo todas as informações !!!
>
Pelo o que eu entendi, nesse caso, o Willian Bruno está se referindo a Data Transfer Object (DTO), a comunidade Java fala Value Object (VO). Mas tome cuidado quanto a isso, VO pode ter outro significado, explicado aqui: http://forum.imasters.com.br/topic/541651-entidade-x-value-object/
O ActiveRecord "knows to much", ou seja, sabe demais. Ele tem conhecimento sobre a sua própria persistência, o que não está de acordo com SRP. Ele deve possuir apenas uma responsabilidade, representar uma entidade Usuário, mas acaba conhecendo regras de persistência.
Mas acredito que ele seja o mais indicado para responder o questionamento.
obs: A interface aqui é necessária ? Me pareceu correto ser implementada....
Não é necessária, mas dá "confiança" no uso de uma implementação, além de permitir o polimorfismo.
Mas é interessante fazer uso de outros recursos do PHP, como o phpDoc, pois consegue definir questões que o PHP por si só não implementa (no PHP 7 type hint já são realidade).
Alguns exemplos aqui:http://forum.imasters.com.br/topic/538625-classe-para-gerenciar-postagens/?p=2151886
Já no seu caso, a implementação não ira funcionar, visto que toda a interface não foi implementada:
IDAO::findById($pk) -> UserDAO::findByPk($pk);
Apesar de fortemente atrelado a um SGDB SQL (do tipo relacional), PK e ID não necessariamente necessitam ser a mesma coisa. PK refere-se ao identificador da tabela (id da tabela), mas o ID é o identificador do objeto. Um exemplo pode ser o código de referência, como o código de barras. Outro exemplo, em uma chave composta (com mais de uma PK), vocês possui N PK's, mas apenas um ID (que são todas as PK's juntas).
Ainda sobre DAO, para cada classe (User, Client, etc, etc...) terei que ter um DAO referente ?
Sim, para cada objeto (ao nível do sistema) e necessário um DAO. O DAO é uma camada de abstração entre a sua aplicação e o seu Storage. Se ocorrer uma mudança, a nível de SGBD, você só precisa mudar o DAO. Sua aplicação se torna independente de um Storage em específico e fica dependente de uma abstração (o que é muito bom).
Diferente do DataMapper/TDG, um DAO não necessita ser 1:1 com o SGBD, ou seja, cada tabela ser representada por um objeto. Mas, normalmente é 1:1.
>
Mas é interessante fazer uso de outros recursos do PHP, como o phpDoc, pois consegue definir questões que o PHP por si só não implementa (no PHP 7 type hint já são realidade).
Alguns exemplos aqui:http://forum.imasters.com.br/topic/538625-classe-para-gerenciar-postagens/?p=2151886
Obrigado pela dica, nos meus próximos códigos vou implementar phpDoc.
Nesses últimos dias estou desenvolvendo classe UserService e a classe Session, queria opinião de vocês a respeito dos principios do S.O.L.I.D.
UserService.class.php
class UserService implements IUserService {
private $validator;
public function __construct(UserServiceValidator $validator) {
$this->validator = $validator;
}
public function login(IUserDAO $userDAO, $email, $password) {
$this->validator->validateUser('email', $email);
$this->validator->validateUser('password', $password);
if ($this->validator->getError() > 0) {
return false;
}
$result = $userDAO->checkLoginByCredentials($email, $password);
if ($result > 0) {
$this->setSession($userDAO->getUser($email));
return true;
}
return false;
}
public function register(IUserDAO $userDAO, User $user) {
$this->validator->validateUser('name', $user->getName());
$this->validator->validateUser('lastname', $user->getLastname());
$this->validator->validateUser('email', $user->getEmail());
$this->validator->validateUser('password', $user->getPassword());
if ($this->validator->getError() > 0) {
return false;
}
$result = $userDAO->insert($user);
return $result;
}
public function logoff() {
Session::destroy();
}
private function setSession($user) {
Session::create();
Session::set('id', $user->id);
Session::set('name', $user->name);
Session::set('lastname', $user->lastname);
Session::set('email', $user->email);
}
public function getCurrentUser() {
$id = Session::get('id');
$name = Session::get('name');
$lastname = Session::get('lastname');
$email = Session::get('email');
return new User($name, $lastname, $email, null, $id);
}
public function isLogged() {
Session::create();
if (!is_null(Session::get('email'))) {
return true;
}
return false;
}
}
-> Nessa classe, não sabia como validar os dados do usuário então criei a classe UserServiceValidator para fazer as consistência.
A parte do Session esta correto? Não sei se fere o principio de SRP.
UserServiceValidator.class.php
class UserServiceValidator {
private $error;
public function validateUser($option, $field) {
$this->error = 0;
// code
}
public function getError() {
return $this->error;
}
}
-> Aqui ainda não foi implementada, mas a ideia é cada opção ('username, email, password, etc...') fazer uma consistencia.
Session.class.php
class Session {
public static function create() {
if (!isset($_SESSION)) {
session_start();
}
}
public static function destroy() {
self::create();
$_SESSION = array();
session_destroy();
}
public static function set($key, $value) {
$_SESSION[$key] = $value;
}
public static function get($key) {
self::create();
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
}
return null;
}
public static function delete($key) {
$_SESSION[$key] = null;
}
}
-> Não sei se foi implementada corretamente essa classe na UserService, pois ela foi chamada estaticamente.
...
Caso vocês puderem me dizer o que esta errado...
Obrigado a todos...
Apesar do exemplo CRUD ser o mais demonstrado/estudado, é um dos mais difíceis para se iniciar no mundo da orientação à objetos. Isso pode ser visto/demonstrado através dos inúmeros modelos (vide Anemic Model) e outros patterns (DataMapper, DAO, TableDataGateway, etc..).
O problema fica no ponto que não consegue ser 100% abstrato, visto que cada SGBD possui suas particularidades e isso influencia muito no desenvolvimento. Dessa forma, cada proposta possui seus pontos positivos e negativos.
Nomear classes/bibliotecas é um dos pontos mais difíceis da programação. Entretanto, alguns pontos devem ser considerados, os principais são o que a classe/biblioteca se propõe a realizar e qual o domínio dela.
Robert Martin escreve um capítulo inteiro sobre isso no seu livro Clean Code.
É mais simples de responder do que entender. Na programação, existe dois tipos básicos de relacionamento: herança e associação.
No básico, se definem da seguinte forma:
Após o básico, vem os diferentes tipos de associação (simples, agregação e composição). No link abaixo há um bom exemplo sobre os tipos de associações:
http://forum.imasters.com.br/topic/458278-como-pensar-em-orientacao-a-objetos/page-2#entry1815097
Outro ponto importante, é estudar a modelagem através de alguns diagramas, como o Diagrama de Classes para criar/entender o seu relacionamento sem uso e o Diagrama de Sequência que demonstra a interação entre os participantes através de uma linha do tempo.
Depende da abordagem. Eu, particularmente, gosto do DataMapper. Entretanto, o DataMapper peca pela granularidade, ele exige muitas classes, pois cada uma define um propósito (Mapper, Factory, Collection, Entity). Caso você for usar o DataMapper completamente, existem mais participantes (UnitOfWork, IdentityMap). A parte interessante do DataMapper, fica no completo desacoplamento da base de dados.
No link abaixo existe um exemplo interessante sobre DataMapper:
http://blog.tekerson.com/2008/12/17/data-mapper-pattern-in-php/
O TableDataGateway também é bem interessante. Ele é mais simples que o DataMapper, tem uma granularidade menor (o que é muito bom) e mantém a mesma finalidade, desacoplar o SGBD. O Henrique Barcelos escreveu um bom artigo sobre o padrão.
http://imasters.com.br/linguagens/php/padroes-tabledatagateway-e-tablerowgateway-teoria-e-pratica/
Prática, somente a prática. Nos links abaixo, existem muitos exemplos sobre OO:
http://www.oodesign.com - http://www.oodesign.com/design-principles.html
https://sourcemaking.com - https://sourcemaking.com/refactoring
Apesar do SourceMaking não ter sobre Design Principles, ele possui sobre Refactoring, que é uma área aonde o SOLID se encaixa muito bem e vice-versa.
Depende da abordagem. Acredito que nos exemplos acima listados, você pode ter uma pequena noção de qual abordagem irá desejar.