MongoDB - PHP - Factory method implementation - php

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
}

Related

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.

Get all the instances of a particular class in PHP

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.

Inheritance in PHP - Creating child instance and calling parent method

I have something like this:
class MyParent {
protected static $object;
protected static $db_fields;
public function delete() {
// delete stuff
}
public static function find_by_id($id = 0) {
global $database;
$result_array = self::find_by_sql("SELECT * FROM " . static::$table_name . " WHERE id=" . $database -> escape_value($id) . " LIMIT 1");
return !empty($result_array) ? array_shift($result_array) : false;
}
public static function find_by_sql($sql = "") {
global $database;
// Do Query
$result_set = $database -> query($sql);
// Get Results
$object_array = array();
while ($row = $database -> fetch_array($result_set)) {
$object_array[] = self::instantiate($row);
}
return $object_array;
}
private static function instantiate($record) {
$object = self::$object;
foreach ($record as $attribute => $value) {
if (self::has_attribute($attribute)) {
$object -> $attribute = $value;
}
}
return $object;
}
}
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = new TheChild;
}
}
$child= TheChild::find_by_id($_GET['id']);
$child->delete();
I get this: Call to undefined method stdClass::delete() referring to the last line above. What step am I missing for proper inheritance?
You never actually instanciate the TheChild class, which should be done by
$var = new TheChild();
except in TheChild constructor itself.
So, the static $object field is never affected (at least in your example), so affecting a field to it (the line $object -> $attribute = $value; ) causes the creation of an stdClass object, as demonstrated in this interactive PHP shell session:
php > class Z { public static $object; }
php > Z::$object->toto = 5;
PHP Warning: Creating default object from empty value in php shell code on line 1
php > var_dump(Z::$object);
object(stdClass)#1 (1) {
["toto"]=>
int(5)
}
This object does not have a delete method.
And as said before, actually creating a TheChild instance will result in an infinite recursion.
What you want to do is this, probably:
class TheChild extends MyParent {
protected static $db_fields = array('id', 'name');
protected static $table_name = "my_table";
function __construct() {
self::$object = $this;
}
}
Edit: Your updated code shows a COMPLETE different Example:
class MyParent {
protected static $object;
public function delete() {
// delete stuff
}
}
class TheChild extends MyParent {
function __construct() {
self::$object = new TheChild;
}
}
$child = new TheChild;
$child->delete();
Calling "Child's" Constructor from within "Child's" Constructor will result in an infinite loop:
function __construct() {
self::$object = new TheChild; // will trigger __construct on the child, which in turn will create a new child, and so on.
}
Maybe - i dont know what you try to achieve - you are looking for:
function __construct() {
self::$object = new MyParent;
}
ALSO note, that the :: Notation is not just a different Version for -> - it is completely different. One is a Static access, the other is a access on an actual object instance!

What's wrong in this singleton class

I've 3 classes. [1]Singleton [2]Load [3]Dashboard . In Load class there is one method called 'model()'. Where i'm initializing data for singleton object by using this code.
$obj = Singleton::getInstance();
$obj->insertData('email', 'mail#domain.com');
Again, from Dashboard class there is one method called 'show()' from where i'm trying to print the Singleton object data. But, here i can see all the data of Singleton object except the data which has been initialized by 'model' method of 'Load' class.
Here is my full code...
<?php
//---Singletone Class---
class Singleton
{
// A static property to hold the single instance of the class
private static $instance;
// The constructor is private so that outside code cannot instantiate
public function __construct() {
if(isset(self::$instance))
foreach(self::$instance as $key => &$val)
{
$this->{$key} = &$val;
}
}
// All code that needs to get and instance of the class should call
// this function like so: $db = Database::getInstance();
public static function getInstance()
{
// If there is no instance, create one
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
// Block the clone method
private function __clone() {}
// Function for inserting data to object
public function insertData($param, $element)
{
$this->{$param} = $element;
}
}
//---LOAD class---
class Load
{
function __construct()
{
$obj = Singleton::getInstance();
$obj->insertData('country', 'INDIA');
}
function model()
{
$this->name = 'Suresh';
$obj = Singleton::getInstance();
$obj->insertData('email', 'mail#domain.com');
}
function msg()
{
return('<br><br>This message is from LOAD class');
}
}
$obj = Singleton::getInstance();
$load = new load();
$obj->load = $load;
//---Dashboard Class---
class Dashboard extends Singleton
{
function __construct()
{
parent::__construct();
}
function show()
{
echo "Default data in current Object";
echo "<br>";
print_r($this);
echo $this->load->msg();
$this->load->model();
echo "<br><br>Data in current Object after post intialization";
echo "<br>";
print_r($this);
}
}
$dashboard = new dashboard();
$dashboard->show();
If your singleton was truly a singleton then the update would have worked. I'm suspecting that you may have multiple instances of the singleton class that is initialized.
Edit:
Also its not a good idea to inherit from a true singleton class.
You need to remove the inheritance that Dashboard has on Singleton
Edit:
Best practice on PHP singleton classes
I don't like your direct access to an object like an array. This one is a better approach [see here]:
You should call it like this:
$obj = Singleton::getInstance();
$load = new Load();
$obj->insertData( 'load', $load );
Implementation of Singleton:
class Singleton
{
// A static property to hold the single instance of the class
private static $instance;
// my local data
protected $_properties;
// You might want to move setter/getter to the end of the class file
public function __set( $name, $value )
{
$this->_properties[ $name ] = $value;
}
public function __get( $name )
{
if ( ! isset( $this->_properties[ $name ] )) {
return null;
}
return $this->_properties[ $name ];
}
// No need to check, if single instance exists!
// __construct can only be called, if an instance of Singleton actually exists
private function __construct() {
$this->_properties = array();
foreach(self::$instance as $key => &$val)
{
$this->_properties{$key} = &$val;
}
}
public static function getInstance()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
// Function for inserting data to object
public function insertData($param, $element)
{
$this->_properties{$param} = $element;
}
// Block the clone method
private function __clone() {}
}

Understanding desing patterns and OOP in PHP

i'm quite new to php and trying to learn. I have 2 similiar classes. I want to create that objects when i pass count and object type. I read some about factory pattern. Here is my factory class:
class AssetFactory
{
private static $table;
public static $objects = array();
public static function Create($asset,$count)
{
switch ($asset) {
case "Item":
self::$table = "items";
break;
case "Job":
self::$table = "jobs";
break;
}
$db = new Database();
$rows = $db->query("SELECT * FROM ".self::$table." LIMIT ".$count);
foreach($rows as $row)
{
self::$objects[] = new $asset($row);
}
return self::$objects;
}
}
and when i need 5 items i use:
$myItems = AssetFactory::Create('Item',5);
when i need some jobs i use:
$myJobs= AssetFactory::Create('Job',5);
item and job are that similiar classes. My question is here, as i said im trying to learn. Am i doing this right? Did i understand factory pattern right? Have any good documents about this(i read everything on php.net, got anything else).
There are few thing that seem wrong in this case.
First of all , there are two similar structures which are used for object creation:
factories: if object requires some sort of initialization before released for 'consumption'
builders: if before creating object you have to create bunch of other objects
Usually people do not distinguish between the two, and just call them "Factories". So these would be two case where you use a factory.
What you have right now does not fit the description. You are creating some sort of database connection, then getting some data, and then using it for creating a list of objects. This is not a reusable code.
It would be much better if the usage of factory would be something like this :
$connection = new PDO( .. blah.. );
$stmt = $connection->query( 'SELECT * FROM '.$type.' LIMIT '.$count );
$factory = new Factory;
$collection = $factory->buildCollection( $type, $stmt->fetchALL(PDO::FETCH_ASSOC) );
Of course, with factory class which implements this behavior.
Additionally , you might want to watch (assuming, that you haven't seen already) two videos on the subject:
Don't Look For Things!
Unit Testing
Global State and Singletons
I am far from an authority on the factory design pattern, but I usually delegate the instantiation to a subclass.
<?php
class AssetFactory
{
public static function Create($asset, $count)
{
$objects = false;
switch ( strtolower($asset) ) {
case 'item':
case 'job':
$class_name = 'Asset'.$asset;
$asset_obj = $class_name::getInstance();
$objects = $asset_obj->Create($count);
break;
default:
// Invalid asset
break;
}
return $objects;
}
}
class Asset
{
var $name = null;
var $table = null;
private static $instance = null;
private function __construct() {}
private function __clone() {}
public function Create($count)
{
$objects = array();
$db = new Database();
$rows = $db->query("SELECT * FROM ".$this->table." LIMIT ".$count);
if ( is_array($rows) ) {
foreach($rows as $row)
{
$objects[] = new Item($row);
}
}
return $objects;
}
public static function getInstance()
{
if ( empty($self::$instance) ) {
$class_name = __CLASS__;
self::$instance = new $class_name();
}
return self::$instance;
}
}
class AssetItem extends Asset
{
private function __construct()
{
$this->name = 'Item';
$this->table = 'item';
parent::__construct();
}
}
class AssetJob extends Asset
{
private function __construct()
{
$this->name = 'Job';
$this->table = 'job';
parent::__construct();
}
}

Categories