├── .gitignore
├── README.markdown
├── autoload.php
├── database
├── tbl_article.sql
└── tbl_user.sql
├── phpunit.xml.dist
├── src
├── Repository.php
├── framework
│ ├── IdentityMap.php
│ ├── MapperException.php
│ └── RecursiveClassLoder.php
├── model
│ ├── Article.php
│ └── User.php
└── persistence
│ ├── AbstractMapper.php
│ ├── ArticleMapper.php
│ └── UserMapper.php
├── test-bootstrap.php
├── tests
├── RepositoryTest.php
├── model
│ ├── ArticleTest.php
│ └── UserTest.php
└── persistence
│ ├── ArticleMapperTest.php
│ ├── UserMapperTest.php
│ └── fixture
│ ├── article-seed.xml
│ └── user-seed.xml
└── uml-php-identity-map.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Use wildcards as well
2 | *~
3 | *.dist
4 | phpunit.xml.dist
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Building an Identity Map in PHP
2 | ===============================
3 |
4 | Sample application used for training.
5 |
6 | This example code is no production code and should be used for training
7 | purposes only.
8 |
9 | This example code requires:
10 | ---------------------------
11 | * PDO a lightweight, consistent interface for accessing databases in PHP.
12 | * PHPUnit a unit testing framework for PHP projects.
13 |
14 | This example code implements:
15 | -----------------------
16 | * Data-Mapper Pattern
17 | * Identity-Map Pattern
18 |
19 | Why identity mapping?
20 | ---------------------
21 | By using Data-Mapper pattern without an identity map, you can easily run
22 | into problems because you may have more than one object that references
23 | the same domain entity.
24 |
25 | Data-Mapper without identity map
26 | ----------------------------------
27 |
28 | $userMapper = new UserMapper($pdo);
29 |
30 | $user1 = $userMapper->find(1); // creates new object
31 | $user2 = $userMapper->find(1); // creates new object
32 |
33 | echo $user1->getNickname(); // joe123
34 | echo $user2->getNickname(); // joe123
35 |
36 | $user1->setNickname('bob78');
37 |
38 | echo $user1->getNickname(); // bob78
39 | echo $user2->getNickname(); // joe123 -> ?!?
40 |
41 | Data-Mapper with identity map
42 | ----------------------------------
43 | The identity map solves this problem by acting as a registry for all
44 | loaded domain instances.
45 |
46 | $userMapper = new UserMapper($pdo);
47 |
48 | $user1 = $userMapper->find(1); // creates new object
49 | $user2 = $userMapper->find(1); // returns same object
50 |
51 | echo $user1->getNickname(); // joe123
52 | echo $user2->getNickname(); // joe123
53 |
54 | $user1->setNickname('bob78');
55 |
56 | echo $user1->getNickname(); // bob78
57 | echo $user2->getNickname(); // bob78 -> yes, much better
58 |
59 | By using an identity map you can be confident that your domain entity is
60 | shared throughout your application for the duration of the request.
61 |
62 | Note that using an identity map is not the same as adding a cache layer
63 | to your mappers. Although caching is useful and encouraged, it can still
64 | produce duplicate objects for the same domain entity.
65 |
66 | Load the Data-Mappers with the Repository class
67 | -----------------------------------------------
68 |
69 |
70 | $repository = new Repository($this->db);
71 | $userMapper = $repository->load('User');
72 | $insertId = $userMapper->insert(new User('billy', 'gatter'));
73 |
74 |
--------------------------------------------------------------------------------
/autoload.php:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | tests
26 |
27 |
28 |
29 |
30 |
31 |
32 | src
33 |
34 | test-bootstrap.php
35 | autoload.php
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/Repository.php:
--------------------------------------------------------------------------------
1 | identityMap->hasId($entity)) {
13 | return $this->identityMap->getObject($entity);
14 | }
15 |
16 | $this->identityMap->set($entity, new $entity($this->db));
17 |
18 | return $this->identityMap->getObject($entity);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/framework/IdentityMap.php:
--------------------------------------------------------------------------------
1 | objectToId = new SplObjectStorage();
17 | $this->idToObject = new ArrayObject();
18 | }
19 |
20 | /**
21 | * @param integer $id
22 | * @param mixed $object
23 | */
24 | public function set($id, $object)
25 | {
26 | $this->idToObject[$id] = $object;
27 | $this->objectToId[$object] = $id;
28 | }
29 |
30 | /**
31 | * @param mixed $object
32 | * @throws OutOfBoundsException
33 | * @return integer
34 | */
35 | public function getId($object)
36 | {
37 | if (false === $this->hasObject($object)) {
38 | throw new OutOfBoundsException();
39 | }
40 |
41 | return $this->objectToId[$object];
42 | }
43 |
44 | /**
45 | * @param integer $id
46 | * @return boolean
47 | */
48 | public function hasId($id)
49 | {
50 | return isset($this->idToObject[$id]);
51 | }
52 |
53 | /**
54 | * @param mixed $object
55 | * @return boolean
56 | */
57 | public function hasObject($object)
58 | {
59 | return isset($this->objectToId[$object]);
60 | }
61 |
62 | /**
63 | * @param integer $id
64 | * @throws OutOfBoundsException
65 | * @return object
66 | */
67 | public function getObject($id)
68 | {
69 | if (false === $this->hasId($id)) {
70 | throw new OutOfBoundsException();
71 | }
72 |
73 | return $this->idToObject[$id];
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/framework/MapperException.php:
--------------------------------------------------------------------------------
1 | directoryToBeLoaded = $directoryToBeLoaded;
21 | $this->projectRoot = $projectRoot;
22 | }
23 |
24 | /**
25 | * @param string $className Name of the class, will be invoked by de SPL autoloader.
26 | */
27 | public function load($className)
28 | {
29 | static $classes;
30 |
31 | if ($classes === null) {
32 |
33 | $regexIterator = new RegexIterator(
34 | new RecursiveIteratorIterator(
35 | new RecursiveDirectoryIterator(
36 | $this->directoryToBeLoaded
37 | )
38 | ),
39 | '/^.+\.php$/i',
40 | RecursiveRegexIterator::GET_MATCH
41 | );
42 |
43 | foreach (iterator_to_array($regexIterator, false) as $file) {
44 |
45 | $path = current($file);
46 | $name = explode('/', $path);
47 | $name = str_replace('.php', '', end($name));
48 |
49 | $classes[$name] = '/'.$path;
50 | }
51 | }
52 |
53 | if (isset($classes[$className])) {
54 | require $this->projectRoot . $classes[$className];
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/model/Article.php:
--------------------------------------------------------------------------------
1 | title = $title;
27 | $this->content = $content;
28 | }
29 |
30 | /**
31 | * @return string
32 | */
33 | public function getTitle()
34 | {
35 | return $this->title;
36 | }
37 |
38 | /**
39 | * @param string $title
40 | * @return Article
41 | */
42 | public function setTitle($title)
43 | {
44 | $this->title = $title;
45 |
46 | return $this;
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function getContent()
53 | {
54 | return $this->content;
55 | }
56 |
57 | /**
58 | * @param string $content
59 | * @return Article
60 | */
61 | public function setContent($content)
62 | {
63 | $this->content = $content;
64 |
65 | return $this;
66 | }
67 |
68 | /**
69 | * @return User
70 | */
71 | public function getUser()
72 | {
73 | return $this->user;
74 | }
75 |
76 | /**
77 | * @param User $user
78 | * @return Article
79 | */
80 | public function setUser(User $user)
81 | {
82 | $this->user = $user;
83 |
84 | return $this;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/model/User.php:
--------------------------------------------------------------------------------
1 | nickname = $nickname;
32 | $this->password = $password;
33 | }
34 |
35 | /**
36 | * @return string
37 | */
38 | public function getNickname()
39 | {
40 | return $this->nickname;
41 | }
42 |
43 | /**
44 | * @param string $nickname
45 | * @return User
46 | */
47 | public function setNickname($nickname)
48 | {
49 | $this->nickname = $nickname;
50 |
51 | return $this;
52 | }
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getPassword()
58 | {
59 | return $this->password;
60 | }
61 |
62 | /**
63 | * @param string $password
64 | * @return User
65 | */
66 | public function setPassword($password)
67 | {
68 | $this->password = $password;
69 |
70 | return $this;
71 | }
72 |
73 | /**
74 | * @return integer
75 | */
76 | public function getId()
77 | {
78 | return $this->id;
79 | }
80 |
81 | /**
82 | * @param string $title
83 | * @param string $content
84 | * @return User
85 | */
86 | public function addArticle($title, $content)
87 | {
88 | $article = new Article($title, $content);
89 | $this->articles[] = $article->setUser($this);
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * @param array $article List of Article objects.
96 | * @return User
97 | */
98 | public function setArticles(array $article)
99 | {
100 | $this->articles = $article;
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * @return array A list of Article instances.
107 | */
108 | public function getArticles()
109 | {
110 | return $this->articles;
111 | }
112 |
113 | /**
114 | * @return boolean
115 | */
116 | public function hasArticles()
117 | {
118 | return (true === is_array($this->articles) && false === empty($this->articles));
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/persistence/AbstractMapper.php:
--------------------------------------------------------------------------------
1 | db = $db;
26 | $this->identityMap = new IdentityMap();
27 | }
28 |
29 | public function __destruct()
30 | {
31 | unset($this->identityMap, $this->db);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/persistence/ArticleMapper.php:
--------------------------------------------------------------------------------
1 | identityMap->hasId($id)) {
12 | return $this->identityMap->getObject($id);
13 | }
14 |
15 | $sth = $this->db->prepare(
16 | 'SELECT * FROM tbl_article WHERE id = :id'
17 | );
18 |
19 | $sth->bindValue(':id', $id, PDO::PARAM_INT);
20 | $sth->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Article', array('title', 'content'));
21 | $sth->execute();
22 |
23 | if ($sth->rowCount() == 0) {
24 | throw new OutOfBoundsException(
25 | sprintf('No article with id #%d exists.', $id)
26 | );
27 | }
28 |
29 | // let pdo fetch the Article instance for you.
30 | $article = $sth->fetch();
31 |
32 | $this->identityMap->set($id, $article);
33 |
34 | return $article;
35 | }
36 |
37 | /**
38 | * @param integer $id
39 | * @throws OutOfBoundsException
40 | * @return array A list of Article objects.
41 | */
42 | public function findByUserId($id)
43 | {
44 | $sth = $this->db->prepare(
45 | "SELECT * FROM tbl_article WHERE userId = :userId"
46 | );
47 |
48 | $sth->bindValue(':userId', $id, PDO::PARAM_INT);
49 | $sth->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Article', array('title', 'content'));
50 | $sth->execute();
51 |
52 | if ($sth->rowCount() == 0) {
53 | throw new OutOfBoundsException(
54 | sprintf('No article with userId #%d exists.', $id)
55 | );
56 | }
57 |
58 | $articles = $sth->fetchAll();
59 |
60 | return $articles;
61 | }
62 |
63 | /**
64 | * @param Article $article
65 | * @throws MapperException
66 | * @return integer A lastInsertId.
67 | */
68 | public function insert(Article $article)
69 | {
70 | if (true === $this->identityMap->hasObject($article)) {
71 | throw new MapperException('Object has an ID, cannot insert.');
72 | }
73 |
74 | $sth = $this->db->prepare(
75 | "INSERT INTO tbl_article (title, content, userId) " .
76 | "VALUES (:title, :content, :userId)"
77 | );
78 |
79 | $sth->bindValue(':title', $article->getTitle(), PDO::PARAM_STR);
80 | $sth->bindValue(':content', $article->getContent(), PDO::PARAM_STR);
81 | $sth->bindValue(':userId', $article->getUser()->getId(), PDO::PARAM_INT);
82 | $sth->execute();
83 |
84 | $this->identityMap->set((int)$this->db->lastInsertId(), $article);
85 |
86 | return (int)$this->db->lastInsertId();
87 | }
88 |
89 | /**
90 | * @param Article $article
91 | * @throws MapperException
92 | * @return boolean
93 | */
94 | public function update(Article $article)
95 | {
96 | if (false === $this->identityMap->hasObject($article)) {
97 | throw new MapperException('Object has no ID, cannot update.');
98 | }
99 |
100 | $sth = $this->db->prepare(
101 | "UPDATE tbl_article " .
102 | "SET title = :title, content = :content WHERE id = :id"
103 | );
104 |
105 | $sth->bindValue(':title', $article->getTitle(), PDO::PARAM_STR);
106 | $sth->bindValue(':content', $article->getContent(), PDO::PARAM_STR);
107 | $sth->bindValue(':id', $this->identityMap->getId($article), PDO::PARAM_INT);
108 | $sth->execute();
109 |
110 | if ($sth->rowCount() == 1) {
111 | return true;
112 | }
113 |
114 | return false;
115 | }
116 |
117 | /**
118 | * @param Article $article
119 | * @throws MapperException
120 | * @return boolean
121 | */
122 | public function delete(Article $article)
123 | {
124 | if (false === $this->identityMap->hasObject($article)) {
125 | throw new MapperException('Object has no ID, cannot delete.');
126 | }
127 |
128 | $sth = $this->db->prepare(
129 | "DELETE FROM tbl_article WHERE id = :id LIMIT 1"
130 | );
131 |
132 | $sth->bindValue(':id', $this->identityMap->getId($article), PDO::PARAM_INT);
133 | $sth->execute();
134 |
135 | if ($sth->rowCount() == 0) {
136 | return false;
137 | }
138 |
139 | return true;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/persistence/UserMapper.php:
--------------------------------------------------------------------------------
1 | identityMap->hasId($id)) {
12 | return $this->identityMap->getObject($id);
13 | }
14 |
15 | $sth = $this->db->prepare(
16 | 'SELECT * FROM tbl_user WHERE id = :id'
17 | );
18 |
19 | $sth->bindValue(':id', $id, PDO::PARAM_INT);
20 | $sth->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'User', array('nickname', 'password'));
21 | $sth->execute();
22 |
23 | if ($sth->rowCount() == 0) {
24 | throw new OutOfBoundsException(
25 | sprintf('No user with id #%d exists.', $id)
26 | );
27 | }
28 |
29 | // let pdo fetch the User instance for you.
30 | $user = $sth->fetch();
31 |
32 | // set the protected id of user via reflection.
33 | $attribute = new ReflectionProperty($user, 'id');
34 | $attribute->setAccessible(true);
35 | $attribute->setValue($user, $id);
36 |
37 | // load all user's articles
38 | $articleMapper = new ArticleMapper($this->db);
39 |
40 | try {
41 |
42 | $user->setArticles($articleMapper->findByUserId($id));
43 |
44 | } catch (OutOfBoundsException $e) {
45 | // no articles at the database.
46 | }
47 |
48 | $this->identityMap->set($id, $user);
49 |
50 | return $user;
51 | }
52 |
53 | /**
54 | * @param User $user
55 | * @throws MapperException
56 | * @return integer A lastInsertId.
57 | */
58 | public function insert(User $user)
59 | {
60 | if (true === $this->identityMap->hasObject($user)) {
61 | throw new MapperException('Object has an ID, cannot insert.');
62 | }
63 |
64 | $sth = $this->db->prepare(
65 | "INSERT INTO tbl_user (nickname, `password`) " .
66 | "VALUES (:nick, :passwd)"
67 | );
68 |
69 | $sth->bindValue(':nick', $user->getNickname(), PDO::PARAM_STR);
70 | $sth->bindValue(':passwd', $user->getPassword(), PDO::PARAM_STR);
71 | $sth->execute();
72 |
73 | $id = (int)$this->db->lastInsertId();
74 |
75 | $attribute = new ReflectionProperty($user, 'id');
76 | $attribute->setAccessible(true);
77 | $attribute->setValue($user, $id);
78 |
79 | // if user has assosiated articles.
80 | if (true === $user->hasArticles()) {
81 | $articleMapper = new ArticleMapper($this->db);
82 |
83 | // than insert the articles too.
84 | foreach ($user->getArticles() as $article) {
85 | $article->setUser($user);
86 | $articleMapper->insert($article);
87 | }
88 | }
89 |
90 | $this->identityMap->set($id, $user);
91 |
92 | return $id;
93 | }
94 |
95 | /**
96 | * @param User $user
97 | * @throws MapperException
98 | * @return boolean
99 | */
100 | public function update(User $user)
101 | {
102 | if (false === $this->identityMap->hasObject($user)) {
103 | throw new MapperException('Object has no ID, cannot update.');
104 | }
105 |
106 | $sth = $this->db->prepare(
107 | "UPDATE tbl_user " .
108 | "SET nickname = :nick, `password` = :passwd WHERE id = :id"
109 | );
110 |
111 | $sth->bindValue(':nick', $user->getNickname(), PDO::PARAM_STR);
112 | $sth->bindValue(':passwd', $user->getPassword(), PDO::PARAM_STR);
113 | $sth->bindValue(':id', $this->identityMap->getId($user), PDO::PARAM_INT);
114 |
115 | $sth->execute();
116 |
117 | if ($sth->rowCount() == 1) {
118 | return true;
119 | }
120 |
121 | return false;
122 | }
123 |
124 | /**
125 | * @param User $user
126 | * @throws MapperException
127 | * @return boolean
128 | */
129 | public function delete(User $user)
130 | {
131 | if (false === $this->identityMap->hasObject($user)) {
132 | throw new MapperException('Object has no ID, cannot delete.');
133 | }
134 |
135 | $sth = $this->db->prepare(
136 | "DELETE FROM tbl_user WHERE id = :id;"
137 | );
138 |
139 | $sth->bindValue(':id', $this->identityMap->getId($user), PDO::PARAM_INT);
140 | $sth->execute();
141 |
142 | if ($sth->rowCount() == 0) {
143 | return false;
144 | }
145 |
146 | return true;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/test-bootstrap.php:
--------------------------------------------------------------------------------
1 | db === null) {
25 | $this->db = new PDO(
26 | $GLOBALS['DB_DSN'],
27 | $GLOBALS['DB_USER'],
28 | $GLOBALS['DB_PASSWD'],
29 | array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
30 | );
31 | }
32 | }
33 |
34 | /**
35 | * @test
36 | */
37 | public function CreatingNewInstance()
38 | {
39 | new Repository($this->db);
40 | }
41 |
42 | /**
43 | * @test
44 | */
45 | public function LoadingEntity()
46 | {
47 | $repository = new Repository($this->db);
48 |
49 | $this->assertInstanceOf('UserMapper', $repository->load('user'));
50 | }
51 |
52 | /**
53 | * @test
54 | */
55 | public function LoadingEntityTwiceTimeExpectingTheSameObject()
56 | {
57 | $repository = new Repository($this->db);
58 |
59 | $this->assertSame($repository->load('user'), $repository->load('user'));
60 | }
61 |
62 | /**
63 | * @test
64 | */
65 | public function LoadingEntityAnFindOneEntry()
66 | {
67 | $repository = new Repository($this->db);
68 | $user = $repository->load('user');
69 |
70 | $this->assertInstanceOf('User', $user->find(1));
71 | }
72 |
73 | /**
74 | * @test
75 | */
76 | public function InsertingNewUserAndCompareObjectsThanDelete()
77 | {
78 | $user = new User('billy', 'gatter');
79 | $repository = new Repository($this->db);
80 | $userMapper = $repository->load('User');
81 | $insertId = $userMapper->insert($user);
82 | $user2 = $userMapper->find($insertId);
83 |
84 | $this->assertTrue($user === $user2);
85 | $this->assertTrue($userMapper->delete($user2));
86 | }
87 |
88 | /**
89 | * @test
90 | */
91 | public function CreateUserWithArticlesAndFindArticlesByUserIdThanDeleteUser()
92 | {
93 | $repository = new Repository($this->db);
94 | $userMapper = $repository->load('User');
95 |
96 | // create data.
97 | $newUser = new User('Conan', 'He rocks!');
98 | $newUser->addArticle('Conan I', 'Some content about Conan')
99 | ->addArticle('Conan II', 'Some content about Conan')
100 | ->addArticle('Rambo III', 'Some content about Rambo');
101 |
102 | // insert it.
103 | $lastInsertInd = $userMapper->insert($newUser);
104 |
105 | // retrieve the data from the articles.
106 | $articleMapper = $repository->load('Article');
107 | $articles1 = $articleMapper->findByUserId($lastInsertInd);
108 | $articles2 = $articleMapper->findByUserId($lastInsertInd);
109 |
110 | $this->assertNotEmpty($articles1);
111 | $this->assertNotEmpty($articles2);
112 |
113 | foreach ($articles1 as $article) {
114 | $this->assertInstanceOf('Article', $article);
115 | }
116 |
117 | foreach ($articles2 as $article) {
118 | $this->assertInstanceOf('Article', $article);
119 | }
120 |
121 | $this->assertTrue($userMapper->delete($newUser));
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/tests/model/ArticleTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($reflectedArticle->hasProperty('title'));
14 | $this->assertEquals(null, $article->getTitle());
15 |
16 | $this->assertTrue($reflectedArticle->hasProperty('content'));
17 | $this->assertEquals(null, $article->getContent());
18 |
19 | $this->assertTrue($reflectedArticle->hasProperty('user'));
20 | $this->assertEquals(null, $article->getUser());
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function InstanceNoException()
27 | {
28 | new Article('the title', 'the content');
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/tests/model/UserTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($reflectedUser->hasProperty('nickname'));
14 | $this->assertEquals(null, $user->getNickname());
15 |
16 | $this->assertTrue($reflectedUser->hasProperty('password'));
17 | $this->assertEquals(null, $user->getPassword());
18 | }
19 |
20 | /**
21 | * @test
22 | */
23 | public function InstanceNoException()
24 | {
25 | $newUser = new User('maxf', 'love123');
26 |
27 | $this->assertEquals('love123', $newUser->getPassword());
28 | }
29 |
30 | /**
31 | * @test
32 | */
33 | public function AddSomeArticles()
34 | {
35 | $newUser = new User('Conan', 'He rocks!');
36 |
37 | $this->assertFalse($newUser->hasArticles());
38 |
39 | $newUser->addArticle('Conan I', 'Some content about conan')
40 | ->addArticle('Conan I', 'Some content about conan');
41 |
42 | $this->assertTrue($newUser->hasArticles());
43 | $this->assertInstanceOf('Article', current($newUser->getArticles()));
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/tests/persistence/ArticleMapperTest.php:
--------------------------------------------------------------------------------
1 | db = new PDO(
14 | $GLOBALS['DB_DSN'],
15 | $GLOBALS['DB_USER'],
16 | $GLOBALS['DB_PASSWD'],
17 | array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
18 | );
19 |
20 | $this->db->exec(
21 | file_get_contents(
22 | dirname(dirname(dirname(__FILE__))) . '/database/tbl_article.sql'
23 | )
24 | );
25 |
26 | $this->mapper = new ArticleMapper($this->db);
27 |
28 | parent::setUp();
29 | }
30 |
31 | public function getConnection()
32 | {
33 | return $this->createDefaultDBConnection($this->db, $GLOBALS['DB_DBNAME']);
34 | }
35 |
36 | public function getDataSet()
37 | {
38 | return $this->createFlatXMLDataSet(
39 | __DIR__ . '/fixture/article-seed.xml'
40 | );
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function FindByUserId()
47 | {
48 | $articles1 = $this->mapper->findByUserId(1);
49 | $articles2 = $this->mapper->findByUserId(1);
50 |
51 | $this->assertNotEmpty($articles1);
52 | $this->assertNotEmpty($articles2);
53 |
54 | foreach ($articles1 as $article) {
55 | $this->assertInstanceOf('Article', $article);
56 | }
57 |
58 | foreach ($articles2 as $article) {
59 | $this->assertInstanceOf('Article', $article);
60 | }
61 | }
62 |
63 | /**
64 | * @test
65 | * @expectedException OutOfBoundsException
66 | */
67 | public function deleteUserExpectingDeletingAllArticlesFromUser()
68 | {
69 | // create data.
70 | $newUser = new User('Conan', 'He rocks!');
71 | $newUser->addArticle('Conan I', 'Some content about Conan')
72 | ->addArticle('Conan II', 'Some content about Conan')
73 | ->addArticle('Rambo III', 'Some content about Rambo');
74 |
75 | // use user-mapper to insert data.
76 | $userMapper = new UserMapper($this->db);
77 |
78 | $lastUserId = $userMapper->insert($newUser);
79 |
80 | // than delete user with all his articles.
81 | $userMapper->delete($newUser);
82 |
83 | // this one throws the expected OutOfBoundsException.
84 | $this->mapper->findByUserId($lastUserId);
85 | }
86 |
87 | /**
88 | * @test
89 | */
90 | public function UpdateArticle()
91 | {
92 | $article1 = $this->mapper->find(2);
93 |
94 | $article1->setTitle('conan')->setContent('the barbar');
95 |
96 | $res = $this->mapper->update($article1);
97 |
98 | $this->assertTrue($res);
99 | }
100 |
101 | /**
102 | * @test
103 | */
104 | public function DeleteArticle()
105 | {
106 | // create new user and insert.
107 | $user = new User('ZZ', 'TOP');
108 | $userMapper = new UserMapper($this->db);
109 | $userMapper->insert($user);
110 |
111 | // create an article and assosiate it to the user.
112 | $article = new Article('Make Love', 'Some content about love');
113 | $article->setUser($user);
114 | $lastArticleId = $this->mapper->insert($article);
115 |
116 | unset($this->mapper);
117 |
118 | $this->mapper = new ArticleMapper($this->db);
119 |
120 | $this->mapper->delete($this->mapper->find($lastArticleId));
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/tests/persistence/UserMapperTest.php:
--------------------------------------------------------------------------------
1 | db = new PDO(
14 | $GLOBALS['DB_DSN'],
15 | $GLOBALS['DB_USER'],
16 | $GLOBALS['DB_PASSWD'],
17 | array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
18 | );
19 |
20 | $this->db->exec(
21 | file_get_contents(
22 | dirname(dirname(dirname(__FILE__))) . '/database/tbl_user.sql'
23 | )
24 | );
25 |
26 | $this->mapper = new UserMapper($this->db);
27 |
28 | parent::setUp();
29 | }
30 |
31 | public function getConnection()
32 | {
33 | return $this->createDefaultDBConnection($this->db, $GLOBALS['DB_DBNAME']);
34 | }
35 |
36 | public function getDataSet()
37 | {
38 | return $this->createFlatXMLDataSet(
39 | __DIR__ . '/fixture/user-seed.xml'
40 | );
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function UserCanBeFoundById()
47 | {
48 | $user = $this->mapper->find(1);
49 |
50 | $this->assertEquals('joe123', $user->getNickname());
51 | }
52 |
53 | /**
54 | * @test
55 | */
56 | public function InsertingNewUserAndCompareObjectsThanDelete()
57 | {
58 | $user = new User('billy', 'gatter');
59 |
60 | $insertId = $this->mapper->insert($user);
61 |
62 | $user2 = $this->mapper->find($insertId);
63 |
64 | $this->assertTrue($user === $user2);
65 | $this->assertTrue($this->mapper->delete($user2));
66 | }
67 |
68 | /**
69 | * @test
70 | * @expectedException OutOfBoundsException
71 | */
72 | public function UserCanNotBeFoundById()
73 | {
74 | $this->mapper->find(123);
75 | }
76 |
77 | /**
78 | * @test
79 | */
80 | public function UserCanBeInserted()
81 | {
82 | $newUser = new User('maxf', 'love123');
83 |
84 | $lastinsertId = $this->mapper->insert($newUser);
85 |
86 | $this->assertEquals(3, $lastinsertId);
87 |
88 | $user = $this->mapper->find($lastinsertId);
89 |
90 | $this->assertEquals('maxf', $user->getNickname());
91 | }
92 |
93 | /**
94 | * @test
95 | */
96 | public function IdentityMapInteractionAndConsistency()
97 | {
98 | $user1 = $this->mapper->find(1);
99 | $user2 = $this->mapper->find(1);
100 |
101 | // expects same nickname in each object.
102 | $this->assertEquals($user2->getNickname(), $user1->getNickname());
103 |
104 | // update the nickname on user1.
105 | $user2->setNickname('tucker');
106 |
107 | // expects same nickname in each object.
108 | $this->assertEquals($user2->getNickname(), $user1->getNickname());
109 |
110 | // than update into the database.
111 | $this->mapper->update($user2);
112 | }
113 |
114 | /**
115 | * @test
116 | */
117 | public function PersistUserWithSomeArticles()
118 | {
119 | $newUser = new User('Conan', 'He rocks!');
120 | $newUser->addArticle('Conan I', 'Some content about Conan')
121 | ->addArticle('Conan II', 'Some content about Conan')
122 | ->addArticle('Rambo III', 'Some content about Rambo');
123 |
124 | $lastUserId = $this->mapper->insert($newUser);
125 |
126 | // unset the user-mapper and the identity-map - force db connection.
127 | unset($this->mapper);
128 |
129 | // create new user-mapper with new identity-map.
130 | $this->mapper = new UserMapper($this->db);
131 |
132 | $user = $this->mapper->find($lastUserId);
133 |
134 | foreach ($user->getArticles() as $article) {
135 | $this->assertInstanceOf('Article', $article);
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/tests/persistence/fixture/article-seed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
15 |
--------------------------------------------------------------------------------
/tests/persistence/fixture/user-seed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/uml-php-identity-map.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gjerokrsteski/php-identity-map/5c22834e4c3ae2e4ab7fa8a565f25ba39878c89a/uml-php-identity-map.gif
--------------------------------------------------------------------------------