PHP OOP Design for simple Models - php

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;
}

Related

Prevent Object recursion/loop by reference in PHP

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;
}
}

How to save protected properties via datamapper to database? [closed]

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.

Iterate over object properties and omit certain ones

I'm trying to figure out the best way to iterate over an object's properties so I can build a sql query for an insert or update. I also am looking to be able to omit certain fields in the iteration.
Below is an example object where I would like to grab name and age but omit employer because that is a join from another table.
class person
{
private $_name, $_age, $_employer;
public function get_name()
{
return $this->_name;
}
public function get_age()
{
return $this->_age;
}
public function get_employer()
{
return $this->_employer;
}
}
I could cast an object as an array to get the properties but I still don't have a good way to omit certain properties.
$personObj = new person();
foreach((array)$personObj as $k => $v)
{
$sql .= "...";
}
Hope this gives you a hint
class person
{
private $_name = 'dude';
private $_age = '27';
private $_employer = 'yes';
public function get_name()
{
return $this->_name;
}
public function get_age()
{
return $this->_age;
}
public function get_employer()
{
return $this->_employer;
}
}
$person = new person();
$required = array('name','age');
foreach($required as $req)
{
$func = "get_{$req}";
echo $person->$func();
}
https://3v4l.org/vLdAN

Dependency Injection and passing arguments

I have a question about Dependency Injection in PHP.
I currently have this 3 classes:
Staff.php
<?php
class Staff
{
public function name($id)
{
return 'returning staff with id ' . $id;
}
}
Projects.php
<?php
class Projects
{
..... projects related functions
}
ProjectsManager.php
<?php
class ProjectsManager
{
private $staff = null;
private $projects = null;
public function __construct(Staff $staff, Projects $projects)
{
$this->staff = $staff;
$this->projects = $projects;
}
public function staff()
{
return $this->staff;
}
public function projects()
{
return $this->projects;
}
}
Those classes are instantiated like this:
$staff = new Staff;
$projects = new Projects;
$app = new ProjectsManager($staff, $projects);
echo $app->staff()->name(5);
The above is working, but what I would like to do is something like this:
$employee = $app->staff(5);
echo $employee->name();
echo $employee->position();
echo $employee->email();
How can I handle the dependency to achieve this?
You can simply add the set function in Staff class and call it in ProjectsManager:
<?php
class Staff
{
private $id = null;
public function name()
{
return 'returning staff with id ' . $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
class Projects
{
//..... projects related functions
}
class ProjectsManager
{
private $staff = null;
private $projects = null;
public function __construct(Staff $staff, Projects $projects)
{
$this->staff = $staff;
$this->projects = $projects;
}
public function staff($id = null)
{
$this->staff->setId($id);
return $this->staff;
}
public function projects($val = null)
{
return $this->projects;
}
}
$staff = new Staff;
$projects = new Projects;
$app = new ProjectsManager($staff, $projects);
$employee = $app->staff(5);
echo $employee->name();
$employee = $app->staff()->name(5);
//$app is the ProjectsManager
//$app->staff() returns it's Staff object
//staff()->name(5) Invokes the Staff object's name function
//Returns 'Returning staff with id 5'
echo $employee->name();
echo $employee->position();
echo $employee->email();
To avoid confusion, I would also suggest prefix some of those functions with get (eg. $app->getStaff()->getFromId(#))
Also, be sure to modify staff()->name(#) to actually return an object and not a string.

Infinite loop with constructors due to m:n relation

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.

Categories