chriskite/phactory — pull request #19 by ramsey (2012-08-10 18:51:28+00:00)
Title: Feature namespaces

**NOTE:** This was originally submitted as Pull Request #17. I am submitting it as a new pull request in order to direct it into the `next` branch, rather than `master`.

**WARNING:** _Accepting this pull request will break backwards compatibility with all previous versions of Phactory, as well as what's currently in master. I am submitting this for review and discussion more than blind acceptance into the Phactory master branch. Following discussion, you may feel that these modifications aren't good for the Phactory project or that they are best-suited in a Phactory2 repository, creating a logical split from the current version of the project. That's up to you._

This pull request introduces namespaces to Phactory. It also removes all static calls and singleton behavior, so that instances of Phactory objects must be created and passed around (dependency injection). These changes were made to solve the following problems I encountered:

* Since Phactory.php and PhactoryMongo.php both contained classes named Phactory, it was impossible to include them both in the same tests
* Again, since both files defined classes with the same name, PSR-0 autoloaders had problems loading the PhactoryMongo class, since it was not named appropriately; this also led to problems with Composer autoloading from the vendor directory
* The static methods were difficult to write tests for in PHPUnit. Migrating them to instance methods has alleviated the problems caused by testing static methods and singletons

As a result of these changes, backwards compatibility is broken with current master and all previous tags of Phactory. However, what is gained is:

* Compliance with PSR-0 autoloaders
* Ability to use Phactory with Composer without autoloader conflicts
* Ability to test both SQL and Mongo databases within the same tests
* Easier to test Phactory methods with PHPUnit

Here is an example unit test using MongoDB and these updates to Phactory. This unit test will run as is, provided you have a local MongoDB database with a "testdb" database and a user with username "myuser" and password "mypass". It assumes you have this test in a file in the root of your project and that you have run `composer.phar install` so that there is a `vendor/` directory with your autoloader in it. Feel free to give it a try.

```php
mongo = new \Mongo('mongodb://localhost:27017', array(
'db' => 'testdb',
'username' => 'myuser',
'password' => 'mypass',
));

$this->phactory = new PhactoryMongo($this->mongo->testdb);
$this->phactory->reset();

$this->defineFixtures();
$this->buildFixtures();
}

protected function tearDown()
{
$this->phactory->recall();
}

protected function defineFixtures()
{
$this->phactory->define('book', array(
'_id' => null,
'name' => 'The Great Book $n',
'created' => new \MongoDate,
));

$this->phactory->define('author', array(
'_id' => null,
'name' => 'J.R.R. Authorname $n',
'description' => 'A widely respected test author.',
'created' => new \MongoDate,
), array(
'books' => $this->phactory->embedsMany('books'),
));

$this->phactory->define('publisher', array(
'_id' => null,
'name' => 'Acme Publishing $n, LLC',
'created' => new \MongoDate,
), array(
'authors' => $this->phactory->embedsMany('authors'),
));
}

protected function buildFixtures()
{
// Books for Publisher 1, Author 1 (embedded doc, use "build")
$book1 = $this->phactory->build('book', array('_id' => new \MongoId));
$book2 = $this->phactory->build('book', array('_id' => new \MongoId));

// Books for Publisher 1, Author 2 (embedded doc, use "build")
$book3 = $this->phactory->build('book', array('_id' => new \MongoId));
$book4 = $this->phactory->build('book', array('_id' => new \MongoId));

// Books for Publisher 2, Author 3 (embedded doc, use "build")
$book5 = $this->phactory->build('book', array('_id' => new \MongoId));
$book6 = $this->phactory->build('book', array('_id' => new \MongoId));

// Publisher 1, Author 1
// (embedded doc with embedded items, use "buildWithAssociations")
$author1 = $this->phactory->buildWithAssociations('author', array(
'books' => array($book1, $book2),
), array(
'_id' => new \MongoId,
));

// Publisher 1, Author 2
// (embedded doc with embedded items, use "buildWithAssociations")
$author2 = $this->phactory->buildWithAssociations('author', array(
'books' => array($book3, $book4),
), array(
'_id' => new \MongoId,
));

// Publisher 2, Author 3
// (embedded doc with embedded items, use "buildWithAssociations")
$author3 = $this->phactory->buildWithAssociations('author', array(
'books' => array($book5, $book6),
), array(
'_id' => new \MongoId,
));

// Publisher 1
// (parent doc with embedded items, use "createWithAssociations")
$publisher1 = $this->phactory->createWithAssociations('publisher', array(
'authors' => array($author1, $author2),
), array(
'_id' => new \MongoId,
));

// Publisher 2
// (parent doc with embedded items, use "createWithAssociations")
$publisher2 = $this->phactory->createWithAssociations('publisher', array(
'authors' => array($author3),
), array(
'_id' => new \MongoId,
));

// Set the fixtures so that we may access them from the tests
$this->fixtures['publisher1'] = $publisher1;
$this->fixtures['publisher2'] = $publisher2;
}

public function testSomething()
{
$publisher = $this->mongo->testdb->publishers->findOne(array(
'_id' => $this->fixtures['publisher1']['_id'],
));
$this->assertEquals($this->fixtures['publisher1']['name'], $publisher['name']);
}
}
```

This test will create structures in Mongo DB that look like this:

For Publisher 1:

```json
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000009"),
"name" : "Acme Publishing 0, LLC",
"created" : ISODate("2012-06-22T18:39:47.122Z"),
"authors" : [
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000006"),
"name" : "J.R.R. Authorname 0",
"description" : "A widely respected test author.",
"created" : ISODate("2012-06-22T18:39:47.121Z"),
"books" : [
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000000"),
"name" : "The Great Book 0",
"created" : ISODate("2012-06-22T18:39:47.121Z")
},
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000001"),
"name" : "The Great Book 1",
"created" : ISODate("2012-06-22T18:39:47.121Z")
}
]
},
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000007"),
"name" : "J.R.R. Authorname 1",
"description" : "A widely respected test author.",
"created" : ISODate("2012-06-22T18:39:47.121Z"),
"books" : [
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000002"),
"name" : "The Great Book 2",
"created" : ISODate("2012-06-22T18:39:47.121Z")
},
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000003"),
"name" : "The Great Book 3",
"created" : ISODate("2012-06-22T18:39:47.121Z")
}
]
}
]
}
```

For Publisher 2:

```json
{
"_id" : ObjectId("4fe4bbf3dfb5cde96800000a"),
"name" : "Acme Publishing 1, LLC",
"created" : ISODate("2012-06-22T18:39:47.122Z"),
"authors" : [
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000008"),
"name" : "J.R.R. Authorname 2",
"description" : "A widely respected test author.",
"created" : ISODate("2012-06-22T18:39:47.121Z"),
"books" : [
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000004"),
"name" : "The Great Book 4",
"created" : ISODate("2012-06-22T18:39:47.121Z")
},
{
"_id" : ObjectId("4fe4bbf3dfb5cde968000005"),
"name" : "The Great Book 5",
"created" : ISODate("2012-06-22T18:39:47.121Z")
}
]
}
]
}
```