Haven't found an answer yet but I'm sure there must be one: how do I prevent an object recursion/loop when objects reference each other? An example:
class Patient {
private $Issues = array();
[...]
public function __construct($id) {
[ Get data from DB ]
while ($row = $result->fetch_assoc()) {
$this->Issues[$row['idIssue']] = new Issue($row['idIssue']);
}
[...]
}
}
class Issue {
private $Patient;
[...]
public function __construct($id) {
[ Get data from DB ]
$this->Patient = new Patient($row['idPatient']); <-- Leads to recursion as the patient will load all it's Issues() etc. etc.
[...]
}
}
How do I prevent this? I could use the id of the Patient() instead of the real object but that feels like a hack. Is there a way to use the real object?
Do not recreate object. Just pass the instance of the master object to the detail constructor. E.g.:
class Patient {
private $Issues = array();
[...]
public function __construct($id) {
[ Get data from DB ]
while ($row = $result->fetch_assoc()) {
$this->Issues[$row['idIssue']] = new Issue($row['idIssue'], $this);
}
[...]
}
}
class Issue {
private $Patient;
[...]
public function __construct($id, Patient $patient) {
[ Get data from DB ]
$this->Patient = $patient
[...]
}
}
You can (should !) separate the DB connection/queries from the entities definitions and pass references to relations, otherwise, you can't mock entities, plus mixing DB connection and entities definition goes against the separation of concerns :
// somewhere in your code
$idPatient = 42;
$patient = new Patient();
$patient->setId($idPatient);
// get datas from DB
while ($row = $result->fetch_assoc())
{
$issue = new Issue();
$issue->setId($row['idIssue'])
->setPatient($patient);
$patient->addIssue($issue);
// or, shorter way :
// $patient->addIssues((new Issue())->setId($row['idIssue'])
// ->setPatient($patient));
}
class Patient {
private $Issues = array();
private $Id;
public function addIssue(Issue $issue): self
{
$this->Issues[] = $issue;
return $this;
}
public function setId(int $id): self
{
$this->Id = $id;
return $this;
}
}
class Issue {
private $Patient;
private $Id;
public function addPatient(Patient $patient): self
{
$this->Patient = $patient;
return $this;
}
public function setId(int $id): self
{
$this->Id = $id;
return $this;
}
}
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I'm trying to grasp the concept of a datamapper (I hope this is the right terminology) in conjunction with protected properties.
I am building an authentication system. There I have a User class
class User {
protected $id;
public $first_name;
public $mail;
protected $password;
As you can see, I chose to make $id and $password protected. Actually I'm not quite sure if that's right, but I did read, that one should try to keep the scope of properties as closed as possible.
I also build a datamapper to save my user object to my database. The mapper is injected to the user class via constructor dependency injection. I call the mappers save-method from inside my user class this way
public function save () {
return $this->dep['mapper']->saveUser($this);
}
Inside my mappers saveUser()-method I am building an array of values to pass along to my database class.
public function saveUser($obj) {
$insert_array;
foreach ( $obj as $key => $value ) {
$insert_array[$key] = $obj->get($key);
}
This does not work the way it's intended, because my mapper is not able to iterate over the protected properties. Therefore these properties are not passed on to the database. If the said properties are public it works just fine.
So my question is: How do I have to setup my classes and methods so that my mapper is able to get all the values it needs, without exposing all my properties?
Extra: I already made use of __get() to circumvent the problem, but is that good coding practice?
There is no single right answer for this, but IMO you don't want to have different visibility for fields in a data object. Here are some ideas.
If you're set on having different visibility for fields on you User class, you can change things up like this to allow your Mapper to save the data using an array you build in the save method of your user class.
<?php
class User
{
protected $id;
public $first_name;
public $mail;
protected $password;
private $dep = [];
public function __construct()
{
$this->dep['mapper'] = new Mapper();
}
public function save()
{
$data = [
'id' => $this->id,
'first_name' => $this->first_name,
'mail' => $this->mail,
'password' => $this->password
];
return $this->dep['mapper']->saveUser($data);
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
$myUser = new User();
$myUser->first_name = 'Lando';
$myUser->mail = 'lando#cloudcity.gov';
$myUser->save();
A more formal option is to use a Data Transfer Object (DTO), which is a dead-simple class that just encapsulates the data. Then you can control access to the fields in your business object.
<?php
class User
{
private $dto;
private $dep = [];
public function __construct(UserDto $dto)
{
$this->dto = $dto;
$this->dep['mapper'] = new Mapper();
}
public function __get($propName)
{
if($propName=='password')
{
throw new Exception('No password for you');
}
elseif(property_exists($this->dto, $propName))
{
return $this->dto->$propName;
}
throw new InvalidArgumentException('No property '.$propName.' found in object');
}
public function __set($propName, $value)
{
if($propName=='id')
{
throw new Exception('ID may not be changed');
}
elseif($propName=='password')
{
throw new Exception('Password may not be changed');
}
elseif(property_exists($this->dto, $propName))
{
$this->dto->$propName = $value;
}
else
{
$this->$propName = $value;
}
}
public function __isset($propName)
{
return (property_exists($this->dto, $propName));
}
public function save()
{
return $this->dep['mapper']->saveUser($this->dto);
}
}
class UserDto
{
public $id;
public $first_name;
public $mail;
public $password;
}
class Mapper
{
public function saveUser(UserDto $dto)
{
foreach ($dto as $key => $value)
{
$insert_array[$key] = $dto->$key;
echo $key.': '.$value.PHP_EOL;
}
}
}
try
{
$dto = new UserDto();
$myUser = new User($dto);
$myUser->first_name = 'Lando';
$myUser->mail = 'lando#cloudcity.gov';
echo $myUser->password;
$myUser->password = 'foobar';
$myUser->save();
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
A better option to control access to properties is by using get/set/has methods. This is verbose, but has the benefit of adding logic or transforms to the data as you get and set it. One of the major benefits of this approach is that full-featured code editors will code-complete all of these getters and setters, you don't get that with magic methods. You can of course use this in combination with DTOs.
<?php
class User
{
private $data = [
'id'=>null,
'first_name'=>null,
'mail'=>null,
'password'=>null
];
private $dep = [];
public function __construct($data)
{
$validData = array_intersect_key($data, $this->data);
foreach($validData as $currKey=>$currValue)
{
$this->data[$currKey] = $currValue;
}
$this->dep['mapper'] = new Mapper();
}
public function getId()
{
return $this->data['id'];
}
//Notice there is no setter for ID!
public function hasId()
{
return (!empty($this->data['id']));
}
public function getFirstName()
{
return $this->data['first_name'];
}
public function setFirstName($val)
{
$this->data['first_name'] = $val;
}
public function hasFirstName()
{
return (!empty($this->data['first_name']));
}
public function getMail()
{
return $this->data['mail'];
}
public function setMail($val)
{
$this->data['mail'] = $val;
}
public function hasMail()
{
return (!empty($this->data['mail']));
}
//Notice there is no getter for ID!
public function setPassword($val)
{
$hashed = md5($val); //Just an example, don't do this
$this->data['password'] = $hashed;
}
public function hasPassword()
{
return (!empty($this->data['password']));
}
public function save()
{
return $this->dep['mapper']->saveUser($this->data);
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
try
{
$dataFromDb = [
'id'=>123,
'first_name'=>'Lando',
'mail'=>'lando#cloudcity.gov',
];
$myUser = new User($dataFromDb);
$myUser->setFirstName('Chewie');
$myUser->setMail('wookie#kashyyyk.net');
if(!$myUser->hasPassword())
{
$myUser->setPassword('AAAAAARRRRRRGHHHH');
}
$myUser->save();
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
I prefer to do something like this, where all of the verbose boilerplate is relegated to data access objects that encapsulate the data and handle loading and saving individual records, and the app logic for individual records is contained in the main business object. They can be superclasses or traits, whatever floats your boat. Personally, I have code that writes all of my DAO and business object classes for me based on database schemas, so all I have to worry about is app logic.
<?php
trait UserDao
{
private $data = [
'id'=>null,
'first_name'=>null,
'mail'=>null,
'password'=>null
];
private $deps;
public function getId()
{
return $this->data['id'];
}
//Notice there is no setter for ID!
public function hasId()
{
return (!empty($this->data['id']));
}
public function getFirstName()
{
return $this->data['first_name'];
}
public function setFirstName($val)
{
$this->data['first_name'] = $val;
}
public function hasFirstName()
{
return (!empty($this->data['first_name']));
}
public function getMail()
{
return $this->data['mail'];
}
public function setMail($val)
{
$this->data['mail'] = $val;
}
public function hasMail()
{
return (!empty($this->data['mail']));
}
private function _getPassword()
{
return $this->data['password'];
}
private function _setPassword($val)
{
$this->data['password'] = $val;
}
public function hasPassword()
{
return (!empty($this->data['password']));
}
public function load($data)
{
$validData = array_intersect_key($data, $this->data);
foreach($validData as $currKey=>$currValue)
{
$this->data[$currKey] = $currValue;
}
}
private function _save()
{
return $this->dep['mapper']->saveUser($this->data);
}
}
class User
{
use UserDao;
public function __construct()
{
$this->dep['mapper'] = new Mapper();
}
public function setPassword($val)
{
$hashed = str_rot13($val); //Just an example, don't do this
$this->_setPassword($hashed);
}
public function getPassword()
{
return str_rot13($this->_getPassword()); //Just an example, don't do this
}
public function save()
{
echo 'Do some complex validation here...'.PHP_EOL;
$this->_save();
}
}
class Mapper
{
public function saveUser($data)
{
foreach($data as $field=>$value)
{
echo $field.': '.$value.PHP_EOL;
}
}
}
try
{
$dataFromDb = [
'id'=>123,
'first_name'=>'Lando',
'mail'=>'lando#cloudcity.gov',
];
$myUser = new User();
$myUser->load($dataFromDb);
$myUser->setFirstName('Chewie');
$myUser->setMail('wookie#kashyyyk.net');
if(!$myUser->hasPassword())
{
$myUser->setPassword('AAAAAARRRRRRGHHHH');
}
$myUser->save();
echo 'Unfutzed Password: '.$myUser->getPassword().PHP_EOL;
}
catch(Exception $e)
{
echo $e->getMessage().PHP_EOL;
}
I recommend doing some research on this subject, there are a lot of patterns, and everyone has different opinions.
im work with php and mysql, sometimes i need instantiate my php class in data access layer for return objects, load list etc... but sometimes I use the class constructor and others do not.
Can i create doble constructor in a class?
example:
class Student {
private $id;
private $name;
private $course;
function __construct() {
}
//get set id and name
function setCourse($course) {
$this->course = $course;
}
function getCourse() {
$this->course = $course;
}
}
class Course {
private $id;
private $description;
function __construct($id) {
this->id = $id;
}
//get set, id, description
}
In my access layer sometime I use the constructor in different ways
for example:
$result = $stmt->fetchAll();
$listStudent = new ArrayObject();
if($result != null) {
foreach($result as $row) {
$student = new Student();
$student->setId($row['id']);
$student->setName($row['name']);
$student->setCourse(new Course($row['idcourse'])); //this works
$listStudent ->append($sol);
}
}
But sometimes I need to use the constructor in another way, for example
$result = $stmt->fetchAll();
$listCourses = new ArrayObject();
if($result != null) {
foreach($result as $row) {
$course = new Course(); //but here not work, becouse Class course receives a id
$course->setId($row['idcourse']);
$course->setDescription($row['description']);
$listCourses->append($sol);
}
}
My english is very bad,
i hope you understand me
Use default arguments:
class Course {
private $id;
private $description;
function __construct($id = 0) {
this->id = $id;
}
// getters and setters for id and description
}
Now, you can use it like that:
$course = new Course(12); // works with argument
or:
$course = new Course(); // works without argument
$course->setId(12);
class Course {
private $id;
private $description;
public function __construct() {
// allocate your stuff
}
public static function constructWithID( $id ) {
$instance = new self();
//do your stuffs here
return $instance;
}
call like Course:: constructWithID(..id) when you have to pass id otherwise make object (new Course()).
I'm trying to persist a domain model to a DB without using an ORM just for fun.
It's pretty easy to persist properties, but I'm having hard time persisting a collection.
Let's say I have the following two objects.
class aModel
{
private $items = [];
public function __construct($id, $name, array $items = [])
{
$this->id = $id;
$this->name = $name;
$this->items = $items;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function addItem(Item $item)
{
$this->items[] = $item;
}
}
class aDBRepository
{
public function persist(aModel $aModel)
{
$attributes = [
'id' => $aModel->getId(),
'name' => $aModel->getName()
];
$this->table->insert($attributes);
}
}
// Code
$aModel = new aModel("test id", "a name");
$aModel->addItem(new Item("id", "name"));
When I create a new aModel and add a new item to it, how do I detect 'unsaved' items and persist them?
I can only think of adding isSaved method in the Item class and loop through $items variable in aModel.
Without using reflection, what's the best way?
I have a database with authors and books, m:n
authors (a_id, ...)
authors_books (a_id, b_id)
books (b_id, ...)
My problem is, that I can't use the constructors to fetch the author/book-data into an array, because I would get an infinite loop.
class Book
{
public $name;
public $authors;
public function __construct($name)
{
$this->name=$name;
$this->authors=$this->Get_Authors();
}
public function Get_Authors()
{
$authors=array();
/* ... (database) */
$authors[]=new Author($name_from_db);
return $authors;
}
}
class Author
{
public $name;
public $books;
public function __construct($name)
{
$this->name=$name;
$this->books=$this->Get_Books();
}
public function Get_Books()
{
$books=array();
/* ... (database) */
$books[]=new Book($name_from_db);
return $books;
}
}
Example:
new Book('book_1');
-> is going to fetch 'author_1' and uses __constructor of Author class
new Author('author_1');
-> is going to fetch 'book_1 and uses __constructor of Book class
...
What is the "best practice" to resolve a m:n relation in PHP classes?
You can use lazy loading here:
class Book {
public $name;
private $_authors = null;
public function __constructor($name) {
$this->name = $name;
}
public function getAuthors() {
if ($this->_authors === null) {
$this->_authors = array();
/* database */
$this->_authors[] = new Author(/**/);
}
return $this->_authors;
}
// You can add some magic getter if you want to access authors as property
public function __get($key) {
if ($key === 'authors') {
return $this->getAuthors();
}
throw new Exception('Unknown property '.$key);
}
}
class Authors {
public $name;
private $_books = null;
public function __constructor($name) {
$this->name = $name;
}
public function getBooks() {
if ($this->_books === null) {
$this->_books = array();
/* database */
$this->_books[] = new Book(/**/);
}
return $this->_books;
}
// You can add some magic getter if you want to access books as property
public function __get($key) {
if ($key === 'books') {
return $this->getBooks();
}
throw new Exception('Unknown property '.$key);
}
}
This will cause that authors/books will be loaded only if you'll need it and won't loop infinitely, but you can reach another problem here:
$author = new Author("Jon Doe");
$book = $author->books[0];
// assuming that book has one author
// $book->authors[0] will not be same object as $author
Solution for that would be to use some third object for loading books and authors, that will store already loaded objects and inject them in proper places
class Library {
private $_books = array();
private $_authors = array();
public function getBooksForAuthor($authorId) {
/* db query... */
$books = array();
while ($row = $stmt->fetch()) {
if (isset($this->_books[$row['id']]) {
$books[] = $this->_books[$row['id']];
} else {
$book = new Book($row);
$this->_books[$row['id']] = $book;
$books[] = $book;
}
}
return $books;
}
/* and similar authorsForBook() method */
}
class Author {
private $_data;
private $_library;
private $_books = null;
public function __constructor($data, $library) {
$this->_data = $data;
$this->_library = $library;
}
public function getBooks() {
if ($this->_books === null) {
$this->_books = $this->_library->getBooksForAuthor($this->_data['id']);
}
return $this->_books;
}
public function __get($key) {
if ($key === 'books') {
return $this->getBooks();
}
if (isset($this->_data[$key]) {
return $this->_data[$key];
}
throw new Exception('Unknown property '.$key);
}
}
/* and similar for book */
Why do you want to load all related object? Use "lazy loading"- don't load all related objects until you need them. In your case it would mean getting related object through their getter method. If you need to get them through property you can implement getters through magic methods.
i've a little problem with the proper design for some simple database models. Lets say i have an User Object with getter/setters and an read method. Read querys the database and sets the properties.
class User extends MyDbBaseClass
{
protected $_id;
protected $_name;
public function setId($id)
{
$this->_id = $id;
}
public function setName($name)
{
$this->_name = $name;
}
public function getId()
{
return (int) $this->_id;
}
public function getName()
{
return (string) $this->_name;
}
public function read($id)
{
// fetch ONE record from Database
$this->_id = $this->setId($sqlResult['id');
$this->_name = $this->setName($sqlResult['name']);
}
public function save()
{
// do some sql stuff to save user to database
}
}
My Problem is, how to return multiple users?
public function getCollection()
{
// fetch all user records from database
forearch ($sqlResult as $result) {
// ... no idea..
}
}
Goal:
// works
$u = new User();
$u->read(1);
echo $u->getName();
// dont know the best way
$u = new User();
$uC = $u->getCollection();
foreach ($uC as $u)
{
echo $u->getName();
}
Any best practices for this?
You could just return an array with users
public function getCollection()
{
// fetch all user records from database
$users = array();
forearch ($sqlResult as $result) {
// ... no idea..
$user = new User();
$user->_name = $result->name; // just an example
$user[] = $users;
}
return $users;
}