Page 1 of 1

Value Object with a set of possible values from a db

Posted: Mon Sep 17, 2007 8:32 am
by Begby
I have been working on creating some value objects for the system I am working on and have a datetime one, a money object, and a few others, but now I have one that is called ShipType and I don't know how to handle it.

I was thinking methods something like this

Code: Select all

interface FCP_ShipType
{

	const FROM_ID = 1 ;	
	const FROM_ABBRV = 2 ;

	/**
	 * Set the ship type
	 *
	 * @param mixed $shipType Either a ship type ID or abbreviation
	 */
	public function set($shipType) ;

	/**
	 * Constructor
	 *
	 * @param mixed $shipType Either a ship type ID or abbreviation
	 */
	public function __construct($shipType) ;


	/**
	 * Get the primary key of the ship type
	 *
	 * @return int
	 */
	public function getID() ;


	/**
	 * Get the description for ship type
	 *
	 * @return string
	 */
	public function getDescription() ;

	/**
	 * Get the abbreviation for this ship type
	 *
	 * @return string
	 */
	public function getAbbrv() ;

	/**
	 * Get the carrier for this ship type
	 *
	 * @return string
	 */
	public function getCarrier() ;

	/**
	 * Check if this object contains a valid ship type
	 *
	 * @return bool
	 */
	public function isValid() ;
}


// use

$shipType = new FCP_ShipType('PMD') ;

$otherShipType = new FCP_ShipType(3) ; // use the database ID


echo $shipType->getCarrier() ; // USPS
echo $shipType->getDescription() ; // Priority mail w/delivery confirmation

echo $otherShipType->getDescription() ; // DHL ground @home

// use as a member of another object
$order->getShipType()->getAbbrv() ;
I set it up so you could set the type with either the shipping abbreviation (DHL2, DHLR, PMD) or by ID. The reason for that is if we have orders imported from a CSV file or from XML the ship abbreviation will be sent, but when the order is stored in the DB then the ID is saved. Then when the order is pulled I can pull the ID and set the value with this object. As I write this I suppose that making the abbreviation be the primary key is probably the way to go, so I think I will change that.

Now here is the puzzle. In order to do validation and mapping between IDs and abbreviations, I need to do a query to pull the ship types from the database and store them in a static array within this class or some other method.

Is there a clean way to do this without having to inject the db connection or a registry into this value object? I really don't want value objects to have dependencies such as that.

Posted: Mon Sep 17, 2007 12:33 pm
by Christopher
First, this is not a Value Object. It is just a plain old object.

My concerns are:

1. that you are trying to do everything in one object when there are a couple of different responsibilities going on here

2. that you are overloading this ID/type when there really should be either a single interface where the internals deal with it, or an adapter of some sort to connect this object with the data source of the information it provides.

It really looks more like a Gateway that abstracts multiple order data sources. You could also look into creating a Factory to returns the correct gateway based on the type of order file. The above just does not seem to slice and dice the problem in the best way.

Posted: Mon Sep 17, 2007 2:37 pm
by Begby
Consider this

Code: Select all

$money = new FCP_Money(10.00) ;
$money->add(new Money(12.00)) ;
echo $money->toInt() ;
echo $money->formattedString() ;
Or this

Code: Select all

$date = new FCP_DateTime(now()) ;

echo $date->toXMLDateTime() ;
echo $date->toSQLDateTime() ;
Both of those objects hold a single value, and can then transform that object, do addition, or display it in different ways.

Code: Select all

$shipType = new ShipType('DHL') ;
echo $shipType->getDesciption() ;
echo $shipType->getCarrier() ;
These are used like this

Code: Select all

$order = new FCP_Order() ;
$order->setCost(new FCP_Money(12.50)) ;
$order->setOrderDate(new FCP_DateTime('12-14-2008 10:10pm') ;
$order->setShipType(new FCP_ShipType('DHL')) ;
This is supposed to be an object that holds a single value, the ship method, then can display it in different ways. I supposed I could use other names besides 'get' to make it clearer. But even if it is not a value object, or whatever, this how I would like to use the object.


I am not trying to do everything in one object. Right now I have a mapper (adapter, gateway, or whatever you want to call it) that looks like this

Code: Select all

$mapper = new FCP_ShipType_Mapper($this->db) ;
$mapper->getAllShipTypes() ; // Get all the ship types as an array
Then I have this in the constructor of FCP_ShipType

Code: Select all

if (!isset(self::$types))
{
  $mapper = new FCP_ShipType_Mapper() ;
  self::$types = $mapper->getAllShipTypes() ;
}
This solution sucks. I don't want to call the mapper from within FCP_ShipType if possible. Right now the mapper pulls the db connection from a registry.

Are you saying I should implement a factory like this?

Code: Select all

$order = new FCP_Order() ;
$order->setShipType( FCP_ShipType_Mapper::factory('DHLR') ) ;

Posted: Mon Sep 17, 2007 2:56 pm
by Christopher
I think you are trying to make your ShipType object like your Money and DateTime object -- but they are not the same. Those two object format a value you give it (which is different than a ValueObject). The ShipType shows the fields of a record. It is more Value Object-y, but it is really giving acces to a row in a database. That's why I thought a Gateway would be better:

Code: Select all

$order = new FCP_Order() ;
$shiptype = FCP_ShipTypeGateway($this->db)
$order->setShipType($shiptype->find('DHLR') );
setShipType just takes an array. All the database stuff is in the Gateway which gives you good separation.

Posted: Mon Sep 17, 2007 7:10 pm
by Begby
Thank you for your help, I am setting it up similar to how you suggest but instead of the gateway returning an array its going to return a shipType object, then within the gateway I am going to cache the result set so it doesn't need to do a query every time find() is called (I will be working with several hundred orders at at time in some situations).

Posted: Mon Sep 17, 2007 7:31 pm
by Christopher
That sounds like a better solution than I proposed. I find that when I have objects returning objects and accepting objects as parameters -- I am usually heading in the right direction. Not because I am using lots of object, but because I am creating clear dependencies on specific interfaces (rather than general types or array element naming conventions).

Your code actually looks interesting. Are the basic classes generic enough that you could release them for others to look at and use?

Posted: Mon Sep 17, 2007 8:06 pm
by Begby
Most of the code is not very generic, but I am sure that some of the base classes can be reused. This is all for one very large project that I am committed to working on for probably around another year. I am writing it to be robust and maintainable at the cost of reuseability. So far I have well over 100 classes written and I am guessing I am about 33% done with the coding needing to go to beta.

The source is also propietary and not owned by me according to my contractual agreements.

However, I am more than happy to share the source of whatever specific parts you want to look at as well as the patterns I am using to create it as I don't think that is going to upset anyone, I just can't zip the whole thing up and email it to you. I can go into more detail if you like.

Posted: Mon Sep 17, 2007 8:17 pm
by Christopher
I did not mean everything, but I think things like the Money, ShipType, Order, etc. classes would me of interest to people.