Reader and Writer Interface for PHP - php

I've created two very basic Interfaces named ReaderInterface and WriterInterface, but I have removed WriterInterface from this example since it is unnecessary to illustrate my conundrum.
ReaderInterface.php
interface ReaderInterface
{
public function read();
}
I have a concrete class called Datatable:
Datatable.php
class Datatable
{
protected $cols;
protected $rows;
protected $reader;
public function __construct()
{
$this->cols = array();
$this->rows = array();
$this->reader = null;
}
public function setReader(ReaderInterface $reader)
{
$this->reader = $reader;
}
public function load()
{
//Contents to follow below.
}
}
I instantiate a datatable instance as follows:
$db = new PDO("mysql:host=localhost;port=3306;dbname=test", "user", "pass"); //Let's pretend this is a good connection.
$datatable = new Datatable();
$datatable->setReader(new DatatableReader($db));
$datatable->load();
My question is about implementing DatatableReader so that it can read from the database I pass in, and write to $this->cols and $this->rows in my Datatable object.
I see two approaches right off.
1. Dependency Injection
class DatatableReader implements ReaderInterface
{
protected $db;
protected $datatable;
public function __construct(Datatable &$datatable, PDO &$db)
{
$this->datatable = $datatable;
$this->db = $db;
}
public function read()
{
//Execute prepared statement which returns 5 records.
//Get the columns, and place them into an array.
foreach ($columns as $col) {
$this->datatable->addColumn($col); //Add a column to the datatable.
}
}
}
Then, my Datatable::load() method would be implemented as:
public function load()
{
if ($this->reader != null)
$this->reader->read();
}
2. Weakly typed return from read().
class DatatableReader implements ReaderInterface
{
protected $db;
public function __construct(PDO &$db)
{
$this->db = $db;
}
public function read()
{
//Execute prepared statement which returns 5 records.
//Get the columns, and place them into an array.
return $columns;
}
}
I would then call my load() method as follows:
public function load()
{
if ($this->reader != null) {
$retval = $this->reader->read();
//Do stuff with the array of information returned from the reader.
}
}
Questions
Of these two options, which is the best design?
Is there a third option that I'm overlooking?

I would go with option 2. With option 1 you produce a recursion: DatatableReader contains object Datatable and vice versa.
The other bad thing with option 1 is that you abuse the read method to write to another object.
And only the concrete implementation (DatatableReader) knows about that object.
All objects that implements an interface should react in the same way.

Related

How to abstract php database object class to work with variable columns

I have a PHP class whose instances are rows in my database table. The class has methods that allow one to create, read, update, and delete rows corresponding to the given instance of the class. It works well as it stands, but I currently name each of the database columns explicitly in my code. As a result, when I add or delete columns (an occasional, but not trivially infrequent action), I must update the class to include those column names. I would like to abstract my code so that it queries the database first to determine what the columns it needs to fill in. (Incidentally, I'm working with PHP 7.2)
The code below is what I've got now, but I'm at a loss as to how to abstract it so that it is not tied to spelling out each specific column name.
<?php
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id','column1', 'column2', 'column3', 'column4'];
public $id;
public $column1;
public $column2;
public $column3;
public $column4;
public function __construct($args=[]) {
$this->column1 = $args['column1'] ?? '';
$this->column2 = $args['column2'] ?? '';
$this->column3 = $args['column3'] ?? '';
$this->column4 = $args['column4'] ?? '';
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $column) {
if ($column === "id") { continue; }
$attributes[$column] = $this->$column;
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
?>
I'm not sure how far you want to take this. This is one way that you can abstract the data for a row.
You will notice that I use set and get functions to access the data.
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id', 'column1', 'column2', 'column3', 'column4'];
public $id;
protected $data = [];
public function __construct($args=[]) {
foreach(self::$db_columns as $columnName) {
if ( isset($args[$columnName]) ) {
$this->data[$columnName] = $args[$columnName];
}
}
}
public function set($columnName, $value)
{
if ( in_array($columnName, $this->data) && isset($this->data[$columnName]) ) {
$this->data[$columnName] = $value;
}
}
public function get($columnName)
{
if ( isset($this->data[$columnName]) ) {
return $this->data[$columnName];
}
return null;
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $columnName) {
if ($column === "id") { continue; }
$attributes[$columnName] = $this->data[$columnName];
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
You may need to look at the magic methods __get() and __set(). Basically, they are class methods that are triggered when trying to access a property that doesn't exist in your class, so you can have a fallback for them.
Please take a look at the documentation: https://www.php.net/manual/en/language.oop5.overloading.php#object.set

Repository and Data Mapper Coupling

I have a controller that acquires data to pass to a view. Into this is injected (via a pimple container) a service which uses a number of domain models + business logic to create the data.
The service itself has a 'repository' class injected into it which has methods for creating data mappers and returning a domain model instance.
I'm aware that I might not have got my head around the repository concept as Martin Fowler puts it to "build another layer of abstraction over the mapping layer" & "A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection." So I may be using this term erroneously.
service:
class InputService
{
private $repos;
public function __construct($repo) {
$this->repos = $repo;
}
public function getInitialData()
{
$product = $this->repo->getProduct();
$country = $this->repo->getCountry();
$spinalPoint = $this->repo->getPoint();
/*business logic with model instances to produce data array*/
return //array of data
}
}
repository:
class InputRepository
{
private $db;
public function __construct($db) {
$this->db = $db;
}
public function getCountry()
{
$mapper = new CountryMapper($this->db);
$country = $mapper->fetch();
return $country; //returns country object
}
// lots of other methods for returning different model objects
}
mapper:
class CountryMapper
{
private $db;
public function __construct($db) {
$this->db = $db;
}
public function fetch()
{
$data = //code to grab data from db;
$obj = new Country($data);
return $obj;
}
}
As you can see, the mappers are tightly coupled to the repository class, however I can't see a way around it.
I was wondering if there is a way to implement this repository that provides looser coupling to the data mapper classes?
In the grand scheme of things this application is fairly small and so having to update code across both wouldn't be disastrous, but you never now when thing will grow!
The db operations should be performed through adapters (MySqliAdapter, PdoAdapter, etc). So, the db connections are injected into adapters, not into the mappers. And certainly not in the repositories, because then the abstraction purpose of the repositories would be pointless.
A mapper receives adapter(s) as dependencies and can receive other mappers too.
The mappers are passed as dependencies to the repositories.
A repository name is semantically related to the domain layer names, not really to the ones of the service layer. E.g: "InputService": ok. "InputRepository": wrong. "CountryRepository": correct.
A service can receive more repositories. Or mappers, if you don't want to apply the extra layer of repositories.
In the code, the only tightly coupled structure is the Country object (entity or domain object) - dynamically created for each fetched table row. Even this could be avoided through the use of a domain objects factory, but I, personally, don't see it really necessary.
P.S: Sorry for not providing a more documented code.
Service
class InputService {
private $countryRepository;
private $productRepository;
public function __construct(CountryRepositoryInterface $countryRepository, ProductRepositoryInterface $productRepository) {
$this->countryRepository = $countryRepository;
$this->productRepository = $productRepository;
}
public function getInitialData() {
$products = $this->productRepository->findAll();
$country = $this->countryRepository->findByName('England');
//...
return // resulted data
}
}
Repository
class CountryRepository implements CountryRepositoryInterface {
private $countryMapper;
public function __construct(CountryMapperInterface $countryMapper) {
$this->countryMapper = $countryMapper;
}
public function findByPrefix($prefix) {
return $this->countryMapper->find(['prefix' => $prefix]);
}
public function findByName($name) {
return $this->countryMapper->find(['name' => $name]);
}
public function findAll() {
return $this->countryMapper->find();
}
public function store(CountryInterface $country) {
return $this->countryMapper->save($country);
}
public function remove(CountryInterface $country) {
return $this->countryMapper->delete($country);
}
}
Data mapper
class CountryMapper implements CountryMapperInterface {
private $adapter;
private $countryCollection;
public function __construct(AdapterInterface $adapter, CountryCollectionInterface $countryCollection) {
$this->adapter = $adapter;
$this->countryCollection = $countryCollection;
}
public function find(array $filter = [], $one = FALSE) {
// If $one is TRUE then add limit to sql statement, or so...
$rows = $this->adapter->find($sql, $bindings);
// If $one is TRUE return a domain object, else a domain objects list.
if ($one) {
return $this->createCountry($row[0]);
}
return $this->createCountryCollection($rows);
}
public function save(CountryInterface $country) {
if (NULL === $country->id) {
// Build the INSERT statement and the bindings array...
$this->adapter->insert($sql, $bindings);
$lastInsertId = $this->adapter->getLastInsertId();
return $this->find(['id' => $lastInsertId], true);
}
// Build the UPDATE statement and the bindings array...
$this->adapter->update($sql, $bindings);
return $this->find(['id' => $country->id], true);
}
public function delete(CountryInterface $country) {
$sql = 'DELETE FROM countries WHERE id=:id';
$bindings = [':id' => $country->id];
$rowCount = $this->adapter->delete($sql, $bindings);
return $rowCount > 0;
}
// Create a Country (domain object) from row.
public function createCountry(array $row = []) {
$country = new Country();
/*
* Iterate through the row items.
* Assign a property to Country object for each item's name/value.
*/
return $country;
}
// Create a Country[] list from rows list.
public function createCountryCollection(array $rows) {
/*
* Iterate through rows.
* Create a Country object for each row, with column names/values as properties.
* Push Country object object to collection.
* Return collection's content.
*/
return $this->countryCollection->all();
}
}
Db adapter
class PdoAdapter implements AdapterInterface {
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
public function find(string $sql, array $bindings = [], int $fetchMode = PDO::FETCH_ASSOC, $fetchArgument = NULL, array $fetchConstructorArguments = []) {
$statement = $this->connection->prepare($sql);
$statement->execute($bindings);
return $statement->fetchAll($fetchMode, $fetchArgument, $fetchConstructorArguments);
}
//...
}
Domain objects collection
class CountryCollection implements CountryCollectionInterface {
private $countries = [];
public function push(CountryInterface $country) {
$this->countries[] = $country;
return $this;
}
public function all() {
return $this->countries;
}
public function getIterator() {
return new ArrayIterator($this->countries);
}
//...
}
Domain object
class Country implements CountryInterface {
// Business logic: properties and methods...
}
You could inject the class names OR instances in the constructor:
class InputRepository
{
private $db;
protected $mappers = array();
public function __construct($db, array $mappers) {
$this->db = $db;
$this->mappers = $mappers;
}
public function getMapper($key) {
if (!isset($this->mappers[$key]) {
throw new Exception('Invalid mapper "'. $key .'"');
}
if (!$this->mappers[$key] instanceof MapperInterface) {
$this->mappers[$key] = new $this->mappers[$key]($this->db);
}
return $this->mappers[$key];
}
public function getCountry()
{
$mapper = $this->getMapper('country');
$country = $mapper->fetch();
return $country; //returns country object
}
// lots of other methods for returning different model objects
}
You would probably want to make the interface checking a bit more robust, obviously.

PHP MVC Model Relations - MySQL

I'm building a small and simple PHP content management system and have chosen to adopt an MVC design pattern.
I'm struggling to grasp how my models should work in conjunction with the database.
I'd like to separate the database queries themselves, so that if we choose to change our database engine in the future, it is easy to do so.
As a basic concept, would the below proposed solution work, is there a better way of doing things what are the pitfalls with such an approach?
First, I'd have a database class to handle all MySQL specific pieces of code:
class Database
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
Second, I'd have a model, in this case to hold all my menu items:
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
Finally, I'd have a 'factory' to pull the two together:
class MenuItemModelFactory extends Database
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
parent::__construct();
}
public function loadById($id)
{
$data = parent::loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = parent::loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Your solution will work of course, but there are some flaws.
Class Database uses inside it's constructor class DatabaseFactory - it is not good. DatabaseFactory must create Database object by itself. However it okay here, because if we will look at class Database, we will see that is not a database, it is some kind of QueryObject pattern (see link for more details). So we can solve the problem here by just renaming class Database to a more suitable name.
Class MenuItemModelFactory is extending class Database - it is not good. Because we decided already, that Database is just a query object. So it must hold only methods for general querying database. And here you mixing knowledge of creating model with general database querying. Don't use inheritance. Just use instance of Database (query object) inside MenuItemModelFactory to query database. So now, you can change only instance of "Database", if you will decide to migrate to another database and will change SQL syntax. And class MenuItemModelFactory won't change because of migrating to a new relational database.
MenuItemModelFactory is not suitable naming, because factory purpose in DDD (domain-driven design) is to hide complexity of creating entities or aggregates, when they need many parameters or other objects. But here you are not hiding complexity of creating object. You don't even "creating" object, you are "loading" object from some collection.
So if we take into account all the shortcomings and correct them, we will come to this design:
class Query
{
protected $table_name;
protected $primary_key;
private $db;
public function __construct()
{
$this->db = DatabaseFactory::getFactory()->getConnection();
}
public function query($sql)
{
$query = $this->db->prepare($sql);
$query->execute();
return $query->fetchAll();
}
public function loadSingle($id)
{
$sql = "SELECT * FROM $this->table_name WHERE $this->primary_key = $id";
return $this->query($sql);
}
public function loadAll()
{
$sql = "SELECT * FROM $this->table_name";
return $this->query($sql);
}
}
class MenuItemModel
{
public $menu_name;
public $menu_url;
private $data;
public function __construct($data)
{
$this->data = $data;
$this->menu_name = $data['menu_name'];
$this->menu_url = $data['menu_url'];
}
}
class MenuItemModelDataMapper
{
public function __construct() {
$this->table_name = 'menus';
$this->primary_key = 'menu_id';
$this->query = new Query();
}
public function loadById($id)
{
$data = $this->query->loadSingle($this->table_name, $this->primary_key, $id);
return new MenuItemModel($data);
}
public function loadAll()
{
$list = array();
$data = $this->query->loadAll();
foreach ($data as $row) {
$list[] = new MenuItemModel($row);
}
return $list;
}
}
Also consider reading this:
DataMapper pattern
Repository pattern
DDD

MongoDB - PHP - Factory method implementation

There are lots of articles regarding factory method implementation in PHP.
I want to implement such a method for my MongoDB implementation in PHP.
I wrote the code something like below. Please Look at that code.
<?php
class Document {
public $value = array();
function __construct($doc = array()) {
$this->value = $doc;
}
/** User defined functions here **/
}
class Collection extends Document {
//initialize database
function __construct() {
global $mongo;
$this->db = Collection::$DB_NAME;
}
//select collection in database
public function changeCollection($name) {
$this->collection = $this->db->selectCollection($name);
}
//user defined method
public function findOne($query = array(), $projection = array()) {
$doc = $this->collection->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function find($query = array(), $projection = array()) {
$result = array();
$cur = $this->collection->find($query, $projection);
foreach($cur as $doc) {
array_push($result, new Document($doc));
}
return $result;
}
/* Other user defined methods will go here */
}
/* Factory class for collection */
class CollectionFactory {
private static $engine;
private function __construct($name) {}
private function __destruct() {}
private function __clone() {}
public static function invokeMethod($collection, $name, $params) {
static $initialized = false;
if (!$initialized) {
self::$engine = new Collection($collection);
$initialized = true;
}
self::$engine->changeCollection($collection);
return call_user_func_array(array(self::$engine, $name), $params);
}
}
/* books collection */
class Books extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('books', $name, $params);
}
}
/* authors collection */
class Authors extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('authors', $name, $params);
}
}
/* How to use */
$books = Books::findOne(array('name' => 'Google'));
$authors = Authors::findOne(array('name' => 'John'));
Authors::update(array('name' => 'John'), array('name' => 'John White'));
Authors::remove(array('name' => 'John'));
?>
My questions are:-
Is this correct PHP implementation of Factory method?
Does this implementation have any issues?
Are there any better methodologies over this for this scenario?
Thanks all for the answers.
Hmm no, because with your piece of code you make ALL methods on the collection class available for a static call. That's not the purpose of the (abstract) factory pattern.
(Magic) methods like __callStatic or call_user_func_array are very tricky because a developer can use it to call every method.
What would you really like to do? Implement the factory pattern OR use static one-liner methods for your MongoDB implementation?!
If the implementation of the book and author collection has different methods(lets say getName() etc..) I recommend something like this:
class BookCollection extends Collection {
protected $collection = 'book';
public function getName() {
return 'Book!';
}
}
class AuthorCollection extends Collection {
protected $collection = 'author';
public function getName() {
return 'Author!';
}
}
class Collection {
private $adapter = null;
public function __construct() {
$this->getAdapter()->selectCollection($this->collection);
}
public function findOne($query = array(), $projection = array()) {
$doc = $this->getAdapter()->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function getAdapter() {
// some get/set dep.injection for mongo
if(isset($this->adapter)) {
return $this->adapter;
}
return new Mongo();
}
}
class CollectionFactory {
public static function build($collection)
{
switch($collection) {
case 'book':
return new BookCollection();
break;
case 'author':
return new AuthorCollection();
break;
}
// or use reflection magic
}
}
$bookCollection = CollectionFactory::build('book');
$bookCollection->findOne(array('name' => 'Google'));
print $bookCollection->getName(); // Book!
Edit: An example with static one-liner methods
class BookCollection extends Collection {
protected static $name = 'book';
}
class AuthorCollection extends Collection {
protected static $name = 'author';
}
class Collection {
private static $adapter;
public static function setAdapter($adapter) {
self::$adapter = $adapter;
}
public static function getCollectionName() {
$self = new static();
return $self::$name;
}
public function findOne($query = array(), $projection = array()) {
self::$adapter->selectCollection(self::getCollectionName());
$doc = self::$adapter->findOne($query, $projection);
return $doc;
}
}
Collection::setAdapter(new Mongo()); //initiate mongo adapter (once)
BookCollection::findOne(array('name' => 'Google'));
AuthorCollection::findOne(array('name' => 'John'));
Does it make sense for Collection to extend Document? It seems to me like a Collection could have Document(s), but not be a Document... So I would say this code looks a bit tangled.
Also, with the factory method, you really want to use that to instantiate a different concrete subclass of either Document or Collection. Let's suppose you've only ever got one type of Collection for ease of conversation; then your factory class needs only focus on the different Document subclasses.
So you might have a Document class that expects a raw array representing a single document.
class Document
{
private $_aRawDoc;
public function __construct(array $aRawDoc)
{
$this->_aRawDoc = $aRawDoc;
}
// Common Document methods here..
}
Then specialized subclasses for given Document types
class Book extends Document
{
// Specialized Book functions ...
}
For the factory class you'll need something that will then wrap your raw results as they are read off the cursor. PDO let's you do this out of the box (see the $className parameter of PDOStatement::fetchObject for example), but we'll need to use a decorator since PHP doesn't let us get as fancy with the Mongo extension.
class MongoCursorDecorator implements MongoCursorInterface, Iterator
{
private $_sDocClass; // Document class to be used
private $_oCursor; // Underlying MongoCursor instance
private $_aDataObjects = []; // Concrete Document instances
// Decorate the MongoCursor, so we can wrap the results
public function __construct(MongoCursor $oCursor, $sDocClass)
{
$this->_oCursor = $oCursor;
$this->_sDocClass = $sDocClass;
}
// Delegate to most of the stock MongoCursor methods
public function __call($sMethod, array $aParams)
{
return call_user_func_array([$this->_oCursor, $sMethod], $aParams);
}
// Wrap the raw results by our Document classes
public function current()
{
$key = $this->key();
if(!isset($this->_aDataObjects[$key]))
$this->_aDataObjects[$key] =
new $this->sDocClass(parent::current());
return $this->_aDataObjects[$key];
}
}
Now a sample of how you would query mongo for books by a given author
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
// Wrap the native cursor by our Decorator
$cursor = new MongoCursorDecorator($cursor, 'Book');
foreach ($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
You could tighten it up a bit with a MongoCollection subclass and you may as well have it anyway, since you'll want the findOne method decorating those raw results too.
class MongoDocCollection extends MongoCollection
{
public function find(array $query=[], array $fields=[])
{
// The Document class name is based on the collection name
$sDocClass = ucfirst($this->getName());
$cursor = parent::find($query, $fields);
$cursor = new MongoCursorDecorator($cursor, $sDocClass);
return $cursor;
}
public function findOne(
array $query=[], array $fields=[], array $options=[]
) {
$sDocClass = ucfirst($this->getName());
return new $sDocClass(parent::findOne($query, $fields, $options));
}
}
Then our sample usage becomes
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoDocCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
foreach($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}

PHP OO - how do I establish class relationships between X has many Y

So I have 2 classes: Lets call them Person and Car
I need to access some attributes from my person object in my Car class before I can instantiate.
Do I simply say something to the order of: $car = new Car($person);
If yes, then how do I access those object attributes in my Car class? Would it be something like this:
class Car{
function __construct($person)
{
$this->person = $person;
}
}
If no, what would be a way to achieve this?
We have some confusion here. Real world OOP, consider and compare:
A car is given to or bought by a person:
$person->addCar($car);
A person is entering the car (to sit on the left front seat):
$car->addPerson($person, Car::SEAT_FRONT_LEFT);
Where Car::SEAT_FRONT_LEFT is a public const member of Car.
The relations are important to be kept semantically correct to be able to construct working objects.
--
To implement this, it might be helpful to lookup the meaning of (Aware-)Interface.
Example classes I'd probably define:
interface Person {
public function getHeight();
public function setHeight();
}
class CarOwner implements Person {
protected $height;
protected $cars = array();
public function getHeight();
public function setHeight();
public function getCars();
};
class Driver implements Person {
protected $height;
public function getHeight();
public function setHeight();
};
class Car {
protected $persons = array(); // #var array
protected $readyToDrive = false;
public const SEAT_FRONT_LEFT = 1;
public const SEAT_FRONT_RIGHT = 2;
// ...
public function addPerson(Person $person, $seat) {
if ($person instanceof Driver) {
$this->isReadyToDrive(true);
}
// I know this is stupid but you get the point:
if ($this->getDriver()->getHeight() < 140 && $this->getSeat()->getPosition() > 40) {
throw new Exception('please set the seat to fit your height!');
}
if (!in_array($person, $this->persons)) {
$this->persons[$seat] = $person;
}
else {
throw new Exception('person is already in the car!');
// Person first needs to "leave" the car! like:
// $car->removePerson($person)->addPerson($person, Car::SEAT_REAR_LEFT);
}
}
public function getDriver() {
if (isset($persons[self::SEAT_FRONT_LEFT]) && $persons[self::SEAT_FRONT_LEFT] instanceof Driver) {
return persons[self::SEAT_FRONT_LEFT];
}
else { //... }
}
};
If we want to see it from encapsulation side, we need to think about it. Is it ok, if a car knows a Person, which is inside? Maybe we need a Service/Controller which takes this params?
function processSomething(Person $person, Car $car) {}
But your example is not bad at all. It is depended on the usecase. Its the way it is done, if a car may know about his person.
If you can have multiple persons, you can have this constructor:
public function __construct(array $personList) {
//Check for a person
if (count($personList) < 1) {
throw new Exception('No person given');
}
//Validate list
foreach ($personList as $person) {
if (!$person instanceof Person) {
throw new Exception('This is not a person');
}
}
//Keep persons
$this->personList = $personList;
}
Also note, if we want to have multiple Objects in our class, we can make an ContainerObject:
class ContainerObject {
public $person;
public $moreInfo;
public $manyMoreInfo;
}
class Person {
public function sayHello() {};
}
class Car {
private $containerObj;
function __construct(ContainerObject $obj) {
$this->containerObj= $obj;
}
function foo() {
$this->containerObj->person->sayHello();
}
}

Categories