Orchestra is a Naked Object implementation on top of Symfony2 Available as a Symfony2 Bundle
Naked Object is a powerful pattern first described by Richard Pawson.
It states that business objects, if correctly modelled, are sufficient to create an application, the technical code being generic enough to be automatically generated.
Orchestra aims to be such an admin generator: a library that helps you focusing on modelling the Domain right, and do all the "boring code" for you.
Maybe you will want to personalize it further, then it's ok, Orchestra is not about restraining you but helping you quickly create a starter application, that may be used as a prototype, a proof-of-concept, or for discussions with business experts... Note that Orchestra application are made to be production-ready, safe to use, and to offer decent performances. Everything is done by reflection (eventually cached), no direct code generation in here.
Roadmap:
Install bundle using composer: composer require romaricdrigon/orchestra-bundle
Register the bundle and the vendor we use in app/AppKernel.php
:
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new RomaricDrigon\OrchestraBundle\RomaricDrigonOrchestraBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
);
Import our routes (both the XML and our custom type):
# app/config/routing.yml
orchestra_routing:
resource: '@RomaricDrigonOrchestraBundle/Resources/config/routing.xml'
prefix: /admin
orchestra_generated:
prefix: /admin
resource: .
type: orchestra
With Orchestra admin generator you will have to focus only on 2 objects: Entities
and Repositories
.
All those objects must be placed within a valid Symfony2 bundle.
TL;DR: you can find a working example at Orchestra-example
Entities
are the basic Domain objects of your application.
Even if we will see later that they are persisted, they may be differ than Doctrine entities.
By convention, place those in your bundle Entity
folder.
They must implement RomaricDrigon\OrchestraBundle\Domain\Entity\EntityInterface
: Orchestra must be able to get a unique ID for each entity instance.
use RomaricDrigon\OrchestraBundle\Domain\Entity\EntityInterface;
class SomeEntity implements EntityInterface
{
public function getId()
{
return ...
Naked Object follow a DDD mindset. We strike not to have an Anemic Domain Model. A few guidelines and advices:
If you want your entity to be displayed in the default listing page, it must implement ListableEntityInterface
:
use RomaricDrigon\OrchestraBundle\Domain\Entity\EntityInterface;
use RomaricDrigon\OrchestraBundle\Domain\Entity\ListableEntityInterface;
class SomeEntity implements EntityInterface, ListableEntityInterface
{
public function viewListing()
{
return ['some data', 'some more', '...'];
}
The data returned by viewListing
will be displayed in the same order in each listing row.
It can be an array or a QueryInterface
object. You will learn more about queries in the next sections.
Entities apply the Command-Query Separation principle.
Any entity method can return an array (simpler, preferred) or an object implementing QueryInterface
.
Orchestra will generate from it an action, a web page displaying the data from the returned object.
Note: QueryInterface
extends \Traversable
.
It means that a Query will have to either extend \Iterator
or IteratorAggregate
Watch out, as of PHP 5.4.30, interface implementation order counts, you must implement one those 2 interfaces before implementing QueryInterface
.
Any entity method can accept an object implementing CommandInterface
.
Such method will be transformed into a web page with a Form.
A Command
is typically a simple data container, with public properties. Those public properties will be mapped to Orchestra-generated Form.
Note: A Command will be called either by calling its constructor (in that case it should receive no argument), either you can designate a factory method on the entity.
In that case, use the CommandFactory
annotation on your Command class.
Usually, they are persisted. Orchestra supports Doctrine ORM through its Symfony2 bridge. You will have to do their mapping, using Doctrine annotations, YAML (advised) or XML as you want. For this part, please refer to Symfony documentation.
Events are objects implementing EventInterface
. An Entity can emit Events.
A method emitting en Event must return either an Event or null
(in that case nothing will happen).
The Event
will be passed to the corresponding Repository receive
method (it must implement ReceiveEventInterface
). You can then decide what to do.
Those are NOT Doctrine repositories! We stick to the original Repository pattern (Fowler, 2002), "a collection-like interface for accessing domain objects".
Their name will be the name of the Entity
suffixed by Repository
but you're free to do otherwise.
Each Repository
must have a corresponding Entity
, with the same slug (lowercase name without suffix):
for a FooBar
entity, you must have a FoobarRepository
, or FooBarRepository
or FooBar
(though this last is less readable).
We advise you to place those, by convention, in your bundle within a Repository
folder.
They must implement RomaricDrigon\OrchestraBundle\Domain\Repository\RepositoryInterface
:
use RomaricDrigon\OrchestraBundle\Domain\Repository\RepositoryInterface;
use RomaricDrigon\OrchestraBundle\Annotation\Name;
class MyRepository implements RepositoryInterface
{
They must be declared as services, tagged with orchestra.repository
and indicating the corresponding entity class:
<!-- your bundle services.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="romaric_drigon_example.my_repository" class="My\Repository\Class\Path">
<tag name="orchestra.repository" entityClass="Acme\Demo\Entity\SomeEntity" />
</service>
...
An Orchestra Repository is a Symfony2 service, so you can inject it dependencies.
Orchestra can automatically resolve and inject to your service the corresponding Doctrine repository.
They will also receive a copy of Orchestra ObjectManager
.
To benefit from this, just implement RomaricDrigon\OrchestraBundle\Domain\Doctrine\DoctrineAwareInterface
.
For simplicity, you can extends the provided BaseRepository
class.
You will then have access to the corresponding Doctrine repository, and we provide a generic listing
method.
use RomaricDrigon\OrchestraBundle\Domain\Doctrine\BaseRepository;
use RomaricDrigon\OrchestraBundle\Annotation\Name;
class MyRepository implements BaseRepository
{
public function someMethod()
{
$objectManager = $this->objectManager;
$doctrineRepository = $this->doctrineRepository;
...
The name displayed for the Repository can be automatically generated, from the class name, or optionally personalized using the @Name
annotation.
use RomaricDrigon\OrchestraBundle\Domain\Repository\RepositoryInterface;
use RomaricDrigon\OrchestraBundle\Annotation as Orchestra;
/**
* @Orchestra\Name("CustomName")
*/
class MyRepository implements RepositoryInterface
{
You can add an Entity or Repository method from the menu by adding it a @Hidden
annotation.
use RomaricDrigon\OrchestraBundle\Domain\Repository\RepositoryInterface;
use RomaricDrigon\OrchestraBundle\Annotation as Orchestra;
class MyRepository implements RepositoryInterface
{
/**
* @Orchestra\Hidden
*/
public function hiddenMethod()
{
Note this does not work on interface, only child classes.
Orchestra fully integrates with Symfony2 Security component, and does not try to interfere with it.
As a convenience, you can add @Security
annotations to an Entity or Repository method to restrict access to the corresponding page.
The content of this annotation is a valid Symfony2 Expression, identical to those used by the SensioFrameworkExtraBundle
Moreover you can access Orchestra objects, such as repository
, entity
which an EntityReflection
or object
the eventual current EntityInterface
(but not the Request
object directly).
You can configure the Bundle by putting those settings in your config.yml
:
# app/config/config.yml
romaric_drigon_orchestra:
app_title: Orchestra # default. Will be used as title (prefix) for pages
IE8 is not supported by provided templates. Twitter Bootstrap is missing its JS polyfills, and we are using jQuery 2.0
Twitter Bootstrap integration have been realized using templates from Braincrafted Bootstrap bundle
Security annotation are supported in a very similar way to SensioFrameworkExtraBundle