potfur / stash
Stash: Mongo ODM
Requires
- php: >=5.5.9|~7.0
- ext-mongo: *
- ocramius/proxy-manager: 1.0.*
- symfony/event-dispatcher: ^2.7
Requires (Dev)
- phpmd/phpmd: ~2.2
- phpunit/phpunit: ~4.6
- squizlabs/php_codesniffer: ~2.3
This package is not auto-updated.
Last update: 2024-12-21 18:48:28 UTC
README
Stash is an object-document mapper for MongoDB written in PHP. It adds a fully transparent persistence layer while still preserving MongoDB's ease of use and way of handling data.
This means that MongoDB can be used almost in the exact same way as it would be used with arrays. The small, but important, difference here is that instead of returning plain arrays, Stash will return objects (entities). And of course, Stash not only returns entities, but it also stores them.
Example
Model definitions:
$models = new \Stash\ModelCollection(); $models->register( new \Stash\Model\Model( '\Order', [ new \Stash\Converter\Type\Id(), new \Stash\Converter\Type\Document('customer'), new \Stash\Converter\Type\ArrayOf('items', Fields::TYPE_DOCUMENT) ] ), 'order' ); $models->register( new \Stash\Model\ '\OrderItem', [ new \Stash\Converter\Type\Scalar('name', Fields::TYPE_STRING), new \Stash\Converter\Type\Scalar('amount', Fields::TYPE_INTEGER), new \Stash\Converter\Type\Scalar('cost', Fields::TYPE_INTEGER) ] ) ); $models->register( new \Stash\Model\ '\Voucher', [ new \Stash\Converter\Type\Scalar('name', Fields::TYPE_STRING), new \Stash\Converter\Type\Scalar('cost', Fields::TYPE_INTEGER) ] ) ); $models->register( new \Stash\Model\ '\Customer', [ new \Stash\Converter\Type\Scalar('name', Fields::TYPE_STRING), new \Stash\Converter\Type\Document('address') ] ) ); $models->register( new \Stash\Model\ '\CustomerAddress', [ new \Stash\Converter\Type\Scalar('address', Fields::TYPE_STRING), new \Stash\Converter\Type\Scalar('city', Fields::TYPE_STRING), new \Stash\Converter\Type\Scalar('zip', Fields::TYPE_STRING) ] ) );
Database connection:
$types = [ new \Stash\Converter\Type\IdType(), new \Stash\Converter\Type\BooleanType(), new \Stash\Converter\Type\IntegerType(), new \Stash\Converter\Type\DecimalType(), new \Stash\Converter\Type\StringType(), new \Stash\Converter\Type\DateType(), new \Stash\Converter\Type\ArrayType(), new \Stash\Converter\Type\DocumentType() ]; $proxyAdapter = new \Stash\ProxyAdapter(new \ProxyManager\Factory\LazyLoadingValueHolderFactory()); $converter = new \Stash\Converter\Converter($types); $referencer = new \Stash\ReferenceResolver($models); $documentConverter = new \Stash\DocumentConverter($converter, $referencer, $models, $proxyAdapter); $eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); $connection = new \Stash\Connection(new \MongoClient(), $documentConverter, $eventDispatcher); $connection->selectDB('test');
Entity creation and storage:
class Order { private $id; private $customer; private $items; public function __construct($customer, $items) { $this->customer = $customer; $this->items = $items; } } class OrderItem { private $name; private $amount; private $cost; public function __construct($name, $amount, $cost) { $this->name = $name; $this->amount = $amount; $this->cost = $cost; } } class Voucher { private $name; private $discount; public function __construct($name, $discount) { $this->name = $name; $this->discount = $discount; } } class Customer { private $name; private $address; public function __construct($name, CustomerAddress $address) { $this->$name = $name; $this->address = $address; } } class CustomerAddress { private $address; private $city; private $zip; public function __construct($address, $city, $zip) { $this->address = $address; $this->city = $city; $this->zip = $zip; } } $order = new Order( new Customer('Joe Doe', new CustomerAddress('Mongo alley', 'Somewhere', '12345')), [ new OrderItem('Foos', 10, 1000), new Voucher('Voucher', 250) ] ); $connection->getCollection('order')->save($order);
And this is the stored MongoDB's semi-JSON representation.
When saving objects (entities), Stash adds the _class
field, where it stores the class name
{
"_id" : ObjectId("55746f4f87dee7bc0b000033"),
"_class" : "Order",
"customer" : {
"_class" : "Customer",
"address" : {
"_class" : "CustomerAddress",
"address" : "Mongo alley",
"city" : "Somewhere",
"zip" : "12345"
}
},
"items" : [
{
"_class" : "OrderItem",
"name" : "Foos",
"amount" : 10,
"cost" : 1000
},
{
"_class" : "Voucher",
"name" : "Voucher",
"discount" : 250
}
]
}
Event subscription
Stash uses Symfonys Event Dispatcher for dispatching events.
find.after
triggered after reading document from database and converting it to entity instancepersist.before
is triggered before converting entity to array documentpersist.after
after document was saved (and updated with new_id
if needed)remove.before
triggered before entity is removed from database
Each event is represented with Event
entity, similar to Symfony's Event
but with two methods getPayload
and setPayload
to manage subject entity.
$subscriber = new \Fake\EventSubscriber( [ \Stash\Events::FIND_AFTER, \Stash\Events::PERSIST_BEFORE, \Stash\Events::PERSIST_AFTER, \Stash\Events::REMOVE_BEFORE ] ); $eventDispatcher->addSubscriber($subscriber);
Configuring proxy
By default, all required proxy classes are generated at runtime. Generation uses a lot of reflection and it may cause poor performance. To prevent this, proxy generator needs to be configured to reuse generated proxies.
$config = new \ProxyManager\Configuration(); $config->setProxiesTargetDir(__DIR__ . '/generated/proxy/); spl_autoload_register($config->getProxyAutoloader()); $proxyAdapter = new \Stash\ProxyAdapter($config);