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.
Related
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
I want to be able to do something like:
objects = getAllInstances(ClassName);
where ClassName has a unique field, so that two instances can not have the exact same value of that field.
class ClassName {
protected $unique_field;
public function __construct($value)
{
$objects = getAllInstances(self);
foreach($objects as $object)
{
if($object->getUniqueField() === $value)
{
return $object;
}
}
}
public function getUniqueField()
{
return $this->unique_field;
}
};
Is there a design pattern, a built-in function in PHP for this purpose, or must I use a static array that holds all the created instances and then just loop over it?
You could create a factory that keeps a reference to all instances created with it:
class ClassNameFactory
{
private $instances = [];
public function create($value)
{
return $this->instances[] = new ClassName($value);
}
public function getInstances()
{
return $this->instances;
}
}
$f = new ClassNameFactory();
$o1 = $f->create('foo');
$o2 = $f->create('bar');
print_r($f->getInstances());
You can hold a static array with all the existing instances. Something similar to this...
static $instances;
public function __construct($name) {
$this->unique_field = $name;
if (empty($instances)) {
self::$instances = array();
}
foreach (self::$instances as $instance) {
if ($instance->getUniqueField() === $name)
return $instance;
}
self::$instances[] = $this;
}
What you need is the registry pattern:
class ClassNameRegistry {
private $instances = array();
public function set($name, InterfaceName $instance) {
$this->instances[$name] = $instance;
}
public function get($name) {
if (!$this->has($name)) {
throw new \LogicException(sprintf(
'No instance "%s" found for class "ClassName".',
$name
);
}
return $this->instances[$name];
}
public function has($name) {
return isset($this->instances[$name]);
}
public function getAll() {
return $this->instances;
}
}
This is certainly the best OOP architecture option because you isolate the behaviour in a standalone class as a service. If you do not have a dependency injection mechanism with services, I would suggest you to define the registry class as a singleton!
In my example, I used a InterfaceName to have a low coupling between Registry and its handled instances.
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.
If I have the following class example:
<?php
class Person
{
private $prefix;
private $givenName;
private $familyName;
private $suffix;
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
public function getPrefix()
{
return $this->prefix;
}
public function setGivenName($gn)
{
$this->givenName = $gn;
}
public function getGivenName()
{
return $this->givenName;
}
public function setFamilyName($fn)
{
$this->familyName = $fn;
}
public function getFamilyName()
{
return $this->familyName;
}
public function setSuffix($suffix)
{
$this->suffix = $suffix;
}
public function getSuffix()
{
return $suffix;
}
}
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?>
I there a way in PHP (5.4 preferably), to combine these return values into one function, this way it models a little bit more like the revealing module pattern in JavaScript?
UPDATE:
OK, I am now beginning to learn that within PHP, it is normative to return a single value from a function, but you "can" return an array of multiple values. This is the ultimate answer to my question and what I will dive into some practices with this understanding.
small example -
function fruit () {
return [
'a' => 'apple',
'b' => 'banana'
];
}
echo fruit()['b'];
Also an article I ran across on stackoverflow on the topic...
PHP: Is it possible to return multiple values from a function?
Good luck!
You sound like you want the __get() magic method.
class Thing {
private $property;
public function __get($name) {
if( isset( $this->$name ) {
return $this->$name;
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
In the case that you want all of the values returned at once, as in Marc B's example, I'd simplify the class design for it thusly:
class Thing {
private $properties = array();
public function getAll() {
return $properties;
}
public function __get($name) {
if( isset( $this->properties[$name] ) {
return $this->properties[$name];
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
$props = $athing-> getAll();
Perhaps
public function getAll() {
return(array('prefix' => $this->prefix, 'givenName' => $this->giveName, etc...));
}
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;
}