PHP STARTER
APPLICATION
For Complex Business Logic
http://github.com/KimPrince/zf2-starter
Dealing with business complexity
General Requirements
• Means of organizing logic
• Flexible database interaction
• A clear API for client code
Solution
• MVC skeleton with a Model
• Model
• The Domain, with:
• Domain object factories
• Collection handling and logic
• Proxies
• Single-valued properties
• Multi-valued properties
• Identity map
• The Data Mapper
• The Service Layer
• View – OOTB
• Controller – OOTB
The code
• Zend Framework 2
• Easily ported to others
• Actively maintained
• Fork it on github
• Two modules
• Core
• Mapper (implementation)
• http://kimprince.com
• Detailed usage discussion
• Code examples
THE MVC ‘MODEL’
- An umbrella for a whole other structure
- The Domain, The Data Mapper, The Service Layer
The Domain: Domain objects
• Represent business objects
• Typically nouns such as ‘customer’, ‘product’
• Include data and behaviour
• Each implements its own interface
• Easily proxied or mocked
• Abstract parent
• Provides common features
• Encapsulation
• $allowed array
• Keys are property names
• Boolean values describe required/optional behaviour
• $data array stores real values
The Domain: Domain objects (cont)
• Construction
• Object factories do the heavy lifting
• Constructor expects an array of filtered values
• Checks that mandatory values have been supplied
• Sets any factories and finders which may have been injected
• Access
• Properties are ‘open for reading, closed for writing’
• __get() – assumes client code can read properties
• __set() – assumes client code can NOT write properties
• Override these with getPropertyName(), setPropertyName($value)
• Identity
• Abstract accessors: getId(), setId()
• For globally unique id: getShortType($object) . $object->getId()
• Value objects are handled differently
The Domain: Object proxies
• Most Domain objects have a proxy
• Factories may substitute proxies for real properties
• A proxy has a real object id and finder (Mapper)
• A proxy is realised if/when it is accessed
• Unless accessing the id only
The Domain: Collections
• One per Domain class (usually)
• Each implements own interface
• Easily proxied or mocked
• Iterable and countable
• Commonly returned by Mappers
• Contain a factory and an array of raw data
• May contain custom logic for the given type
• For sorting, filtering, adding members, removing, comparing, …
• Pass out a clone when filtering or sorting
The Domain: Collection proxies
• Most collections have a proxy
• Class diagram (over)
• Collections and their proxies inherit from a common hierarchy
• Key difference:
• A collection becomes an iterator
• A collection proxy becomes an iterator aggregate
• Iterator aggregate references the real iterator
• On construction, proxies receive
• A finder – for retrieving the collection
• A method name – which exists on the finder
• An array of arguments – which are passed to the finder method
• Collection proxies are used extensively by factories
• As a substitute for multi-valued properties
The Domain: Collection proxies (cont)
The Domain: Object factories
• Often long and complex classes
• Loaded via an abstract factory
• Use the factory method pattern
• Factories are service locator aware
• Include features related to N+1 selects handling (more later)
Method Responsibilities
New Object Defaults • Add defaults for new objects
• Override inputs where necessary
Type Conversion • Cast inputs to required types or proxies
Add Relations • Add collection proxies for multi-valued properties
Instantiation • Inject to constructor: $data, $finders, $factories
The Domain: Identity map
• A safety net
• Performance
• Model integrity
• Main clients: Object factories
• Check for existing before instantiating a Domain object
• Add newly instantiated Domain objects to the map
• Other clients: Data Mappers
• Check for existing before executing a ‘find’ query
• Add newly created entities to the map
• Following a db insert, since that’s when the id is assigned
The Mapper
• Translates between object world and the db
• Converts between under_score and camelCase
• Usually one Mapper class per Domain object
• A typical Mapper has:
• find($id) – returns a single Domain object
• Other single-object finders, such as findByName($name)
• Numerous collection finders, such as findByFoo($id)
• insert($object) – executes an sql insert
• update($object) – executes an sql update
• Mappers are loaded via an abstract factory
The Service Layer
• Highest layer of the Model
• Depends on Domain and Mapper
• Service-locator-aware
• Composes an event manager
• Loaded via an abstract factory
• Tasks
• “Whatever clients need…”
• Persisting changes
• Including database transactions
EVERYTHING ELSE
Non-Model considerations
Views, controllers, helpers
• Views
• OOTB
• Extensive use of helpers
• Forms – Consider hand-crafted for more control
• Confidence in performance, based on:
• Identity map means no fetching duplicates
• Object factories use collection proxies to avoid N+1 selects
• Controllers
• OOTB
• Service locator aware
• Helper trait
• Inserted into most supertypes
• getShortType() identifies a family of objects
• TheObject, TheObjectMapper, TheObjectFactory, TheObjectCollection
A note on filtering and validation
• May be located in Service Layer or Domain
• Option 1: Service Layer
• Easy to manage – i.e. audit, fix gaps
• Option 2: Domain (and Mapper)
• More difficult to manage, but a better fit with OO
• Three data contexts:
• Newly created entities – filter and validate in the object’s factory
• Updates to object properties – filter and validate in custom setters
• Finder parameters – filter and validate in Mappers themselves
The N+1 selects problem
• Occurs where a Domain object has a multi-valued property
• ‘First degree’ problems solved when factory used correctly, e.g.:
• If bars is multi-valued property of Foo…
• The Foo factory sets Foo->bars to a Bar collection proxy
• ‘Second degree’ problems require new factory ‘flavours’
• Flavours are stored in factories as class constants, e.g. FOO::BARS_WITH_BAZ
• Use setFlavour() and addFlavour() to configure factory
• In flavoured factories, collection proxies eagerly load navigable associations
• This affects Mappers too, e.g. Bar Mapper needs findByFooWithBaz($foo)
• See detailed usage discussion for more examples
Using the Starter Application
• See detailed usage discussion for notes on:
• Defining the business problem
• Building the Domain
• Building the Data Mapper
• Building the Service Layer
• Building the presentation layer (views, controllers)
• Iterating to completion
• Also in the usage discussion:
• Create your own Starter Application from Zend’s ZF2 Skeleton App.
www.kimprince.com
@Kim_Prince

PHP Starter Application

  • 1.
    PHP STARTER APPLICATION For ComplexBusiness Logic http://github.com/KimPrince/zf2-starter
  • 2.
    Dealing with businesscomplexity General Requirements • Means of organizing logic • Flexible database interaction • A clear API for client code Solution • MVC skeleton with a Model • Model • The Domain, with: • Domain object factories • Collection handling and logic • Proxies • Single-valued properties • Multi-valued properties • Identity map • The Data Mapper • The Service Layer • View – OOTB • Controller – OOTB
  • 3.
    The code • ZendFramework 2 • Easily ported to others • Actively maintained • Fork it on github • Two modules • Core • Mapper (implementation) • http://kimprince.com • Detailed usage discussion • Code examples
  • 4.
    THE MVC ‘MODEL’ -An umbrella for a whole other structure - The Domain, The Data Mapper, The Service Layer
  • 5.
    The Domain: Domainobjects • Represent business objects • Typically nouns such as ‘customer’, ‘product’ • Include data and behaviour • Each implements its own interface • Easily proxied or mocked • Abstract parent • Provides common features • Encapsulation • $allowed array • Keys are property names • Boolean values describe required/optional behaviour • $data array stores real values
  • 6.
    The Domain: Domainobjects (cont) • Construction • Object factories do the heavy lifting • Constructor expects an array of filtered values • Checks that mandatory values have been supplied • Sets any factories and finders which may have been injected • Access • Properties are ‘open for reading, closed for writing’ • __get() – assumes client code can read properties • __set() – assumes client code can NOT write properties • Override these with getPropertyName(), setPropertyName($value) • Identity • Abstract accessors: getId(), setId() • For globally unique id: getShortType($object) . $object->getId() • Value objects are handled differently
  • 7.
    The Domain: Objectproxies • Most Domain objects have a proxy • Factories may substitute proxies for real properties • A proxy has a real object id and finder (Mapper) • A proxy is realised if/when it is accessed • Unless accessing the id only
  • 8.
    The Domain: Collections •One per Domain class (usually) • Each implements own interface • Easily proxied or mocked • Iterable and countable • Commonly returned by Mappers • Contain a factory and an array of raw data • May contain custom logic for the given type • For sorting, filtering, adding members, removing, comparing, … • Pass out a clone when filtering or sorting
  • 9.
    The Domain: Collectionproxies • Most collections have a proxy • Class diagram (over) • Collections and their proxies inherit from a common hierarchy • Key difference: • A collection becomes an iterator • A collection proxy becomes an iterator aggregate • Iterator aggregate references the real iterator • On construction, proxies receive • A finder – for retrieving the collection • A method name – which exists on the finder • An array of arguments – which are passed to the finder method • Collection proxies are used extensively by factories • As a substitute for multi-valued properties
  • 10.
    The Domain: Collectionproxies (cont)
  • 11.
    The Domain: Objectfactories • Often long and complex classes • Loaded via an abstract factory • Use the factory method pattern • Factories are service locator aware • Include features related to N+1 selects handling (more later) Method Responsibilities New Object Defaults • Add defaults for new objects • Override inputs where necessary Type Conversion • Cast inputs to required types or proxies Add Relations • Add collection proxies for multi-valued properties Instantiation • Inject to constructor: $data, $finders, $factories
  • 12.
    The Domain: Identitymap • A safety net • Performance • Model integrity • Main clients: Object factories • Check for existing before instantiating a Domain object • Add newly instantiated Domain objects to the map • Other clients: Data Mappers • Check for existing before executing a ‘find’ query • Add newly created entities to the map • Following a db insert, since that’s when the id is assigned
  • 13.
    The Mapper • Translatesbetween object world and the db • Converts between under_score and camelCase • Usually one Mapper class per Domain object • A typical Mapper has: • find($id) – returns a single Domain object • Other single-object finders, such as findByName($name) • Numerous collection finders, such as findByFoo($id) • insert($object) – executes an sql insert • update($object) – executes an sql update • Mappers are loaded via an abstract factory
  • 14.
    The Service Layer •Highest layer of the Model • Depends on Domain and Mapper • Service-locator-aware • Composes an event manager • Loaded via an abstract factory • Tasks • “Whatever clients need…” • Persisting changes • Including database transactions
  • 15.
  • 16.
    Views, controllers, helpers •Views • OOTB • Extensive use of helpers • Forms – Consider hand-crafted for more control • Confidence in performance, based on: • Identity map means no fetching duplicates • Object factories use collection proxies to avoid N+1 selects • Controllers • OOTB • Service locator aware • Helper trait • Inserted into most supertypes • getShortType() identifies a family of objects • TheObject, TheObjectMapper, TheObjectFactory, TheObjectCollection
  • 17.
    A note onfiltering and validation • May be located in Service Layer or Domain • Option 1: Service Layer • Easy to manage – i.e. audit, fix gaps • Option 2: Domain (and Mapper) • More difficult to manage, but a better fit with OO • Three data contexts: • Newly created entities – filter and validate in the object’s factory • Updates to object properties – filter and validate in custom setters • Finder parameters – filter and validate in Mappers themselves
  • 18.
    The N+1 selectsproblem • Occurs where a Domain object has a multi-valued property • ‘First degree’ problems solved when factory used correctly, e.g.: • If bars is multi-valued property of Foo… • The Foo factory sets Foo->bars to a Bar collection proxy • ‘Second degree’ problems require new factory ‘flavours’ • Flavours are stored in factories as class constants, e.g. FOO::BARS_WITH_BAZ • Use setFlavour() and addFlavour() to configure factory • In flavoured factories, collection proxies eagerly load navigable associations • This affects Mappers too, e.g. Bar Mapper needs findByFooWithBaz($foo) • See detailed usage discussion for more examples
  • 19.
    Using the StarterApplication • See detailed usage discussion for notes on: • Defining the business problem • Building the Domain • Building the Data Mapper • Building the Service Layer • Building the presentation layer (views, controllers) • Iterating to completion • Also in the usage discussion: • Create your own Starter Application from Zend’s ZF2 Skeleton App.
  • 20.