Persistence / ORM
n2n Persistence bildet die Schnittstelle zur Datenbank und bietet eine umfangreiche ORM (object-relational mapping) API, welche die Spezifikation 2.1 der JPA (Java Persistence API) nahezu vollständig übernimmt. Ein ORM bietet dir eine objektorientierte Abstraktionsebene der Datenbank. Die Basis aller Datenbank-Verbindungen in n2n bildet PDO (PHP Data Objects).
Persistence Unit
In der
app.ini
[database]
{persistence_unit_name}.{property}
default.dsn_uri = "mysql:server=localhost;dbname=testdb" default.user = "root" default.password = "" default.transaction_isolation_level = "SERIALIZABLE" default.dialect = "n2n\\persistence\\meta\\dialect\\mysql\\MysqlDialect"
[database : live]
default.dsn_uri = "mysql:server=localhost;dbname=livedb" default.user = "liveuser" default.password = "livepw" default.transaction_isolation_level = "SERIALIZABLE" default.dialect = "n2n\\persistence\\meta\\dialect\\mysql\\MysqlDialect"
In diesem Beispiel registrieren wir eine Persistence Unit mit Namen "default" für den Development- und Live-Modus. Die Eigenschaft
{persistence_unit_name}.dsn_uri
$dsn
PDO::__construct()
{persistence_unit_name}.transaction_isolation_level
"READ UNCOMMITTED"
"READ COMMITTED"
"REPEATABLE READ"
"SERIALIZABLE"
"SERIALIZABLE"
Mit
{persistence_unit_name}.dialect
Dialect
Dialect
n2n bietet standardmäßig folgende Implementationen von
Dialect
| Datenbank | Dialect Klasse |
|---|---|
| MySQL | |
| PostgreSQL | |
| SQLite | |
| Microsoft SQL | |
| Oracle | |
PDO-Objekt anfordern
Wie bereits erwähnt, werden Datenbank-Verbindungen in n2n über PDO realisiert. In magischen Methoden kannst du dir ein
n2n\\persistence\\Pdo
private function _init(Pdo $pdo) {
$this->pdo = $pdo;
}
n2n\\persistence\\Pdo
n2n\\persistence\\PdoPool
private function _init(DbhPool $dbhPool) {
$this->pdo = $dbhPool->getPdo('db2');
}
In diesem Beispiel fordern wir ein
n2n\\persistence\\Pdo
n2n\\persistence\\Pdo
PDO
Entity
Eine Entity ist eine PHP-Klasse, die auf eine einzelne Tabelle in einer relationalen Datenbank abgebildet wird. Instanzen dieser Klasse entsprechen hierbei den Zeilen der Tabelle und die Eigenschaften den Spalten. Entities sind der Kern des ORM's und bilden normalerweise die Business-Logik deiner Applikation.
namespace atusch\\bo;
use n2n\\reflection\\ObjectAdapter;
class Article extends ObjectAdapter {
const TYPE_NEWS = 'news';
const TYPE_COMMENT = 'comment';
private $id;
private $title;
private $text;
private $type = self::TYPE_NEWS;
private $online = false;
public function __construct($title) {
$this->title = $title;
}
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getTitle() {
return $this->title;
}
public function setTitle($title) {
$this->title = $title;
}
public function getText() {
return $this->text;
}
public function setText($text) {
$this->text = $text;
}
public function getType() {
return $this->type;
}
public function setType($type) {
$this->type = $type;
}
public function isOnline() {
return $this->online;
}
public function setOnline($online) {
$this->online = $online;
}
}
Es werden alle Eigenschaften unabhängig von ihrer Sichtbarkeit erkannt. Du kannst aber einzelne Eigenschaften ignorieren lassen, indem du sie mit
n2n\\persistence\\orm\\annotation\\AnnoTransient
n2n\\reflection\\ObjectAdapter
ObjectAdapter
ObjectAdapter::getClass()
Für die Entity
Article
CREATE TABLE `article` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(50) DEFAULT NULL,
`text` TEXT,
`type` ENUM('news','comment') DEFAULT 'news',
`online` TINYINT(4) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Wie du siehst, nimmt das ORM automatisch an, dass die Eigenschaft mit Namen "id" den Primary Key repräsentiert und die Werte von der Datenbank generiert werden. Wie du dies ändern kannst, erfährst du im Abschnitt Id des Artikels Entity konfigurieren.
Alle Entity-Klassen müssen in der
app.ini
[orm]
[orm] entities[] = "atusch\\bo\\Article" entities[] = "atusch\\bo\\User"
EntityManager
Über den
EntityManager
EntityManager
ArticleDao
EntityManager
_init()
Article
class ArticleDao implements RequestScoped {
private $em;
private function _init(EntityManager $em) {
$this->em = $em;
}
/**
* @param int $id
* @return atusch\\bo\\Article
*/
public function getArticleById($id) {
return $this->em->find(Article::getClass(), $id);
}
public function saveArticle(Article $article) {
$this->em->persist($article);
}
}
Dieses
ArticleDao
ArticleController
public function doArticle(ArticleDao $articleDao, $id) {
$article = $articleDao->getArticleById($id);
if ($article === null) {
throw new PageNotFoundException();
}
$this->forward('view\\\\article.html', array('article' => $article));
}
EntityManager
Pdo
PdoPool
private function _init(PdoPool $pdoPool) {
$this->em = $pdoPool->getEntityManagerFactory("db2")->getExtended();
}
In diesem Beispiel holen wir zuerst die
EntityManagerFactory
EntityManagerFactory::getExtended()
EntityManager
Mit
EntityManagerFactory::getExtended()
EntityManager
EntityManagerFactory::create()
EntityManagerFactory::getTransactional()
EntityManager
Abfragen
Wie im oben stehenden Beispiel ersichtlich, kannst du über
EntityManager::find()
/**
* @param int $id
* @return atusch\\bo\\Article
*/
public function getArticleById($id) {
return $this->em->find(Article::getClass(), $id);
}
find()
ReflectionClass
n2n\\persistence\\orm\\ObjectAdapter
ObjectAdapter::getClass()
Möchtest du komplexere Abfragen ausführen, kannst du wahlweise auf die Criteria API oder NQL (n2n Query Language) zurückgreifen.
Criteria API
Ein
Criteria
Criteria::toQuery()
Query
Um ein einfaches
Criteria
EntityManager::createSimpleCriteria()
/**
* @param string $type
* @param int $limit
* @param int $num
* @return Article[]
*/
public function getArticlesByType($type, $limit, $num) {
$criteria = $this->em->createSimpleCriteria(Article::getClass(),
array('online' => true, 'type' => $type),
array('id' => 'DESC'), $limit, $num);
return $criteria->toQuery()->fetchArray();
}
createSimpleCriteria()
ReflectionClass
SELECT
FROM
Über den zweiten Parameter kannst du einfache Vergleichsbedingungen in der Form Eigenschaft = Wert bestimmen, wobei als Array-Schlüssel der Name der Eigenschaft und als Array-Wert der Vergleichswert erwartet (beeinflusst den
WHERE
Über den dritten Parameter definierst du die Sortierreihenfolge (beeinflusst den
ORDER BY
LIMIT
Sobald du das Criteria in ein
Query
Query::fetchArray()
array
Query::fetchArray()
array
Article
ArticleDao::getArticlesByType()
$articleDao->getArticlesByType('news', 0, 30)
Article::$id
Article::$online
true
Article::$type
Erwartest du als Resultat nur eine einzelne Zeile, kannst du die Abfrage auch mit
Query::fetchSingle()
Query::fetchSingle()
Query::fetchArray()
array
/**
* @param string $userName
* @return User
*/
public function getUserByUserName($userName) {
return $this->em->createSimpleCriteria(User::getClass(), array('userName' => $userName))
->toQuery()->fetchSingle();
}
In diesem Beispiel gibt
fetchSingle()
null
QueryConflictException
Mit
EntityManager::createCriteria()
/**
* @param array $types
* @return Article[]
*/
public function getArticlesByTypes(array $types) {
$criteria = $this->em->createCriteria();
$criteria->select('a')
->from(Article::getClass(), 'a')
->where()->match('a.online', '=', true)->andMatch('a.type', 'IN', $types);
return $criteria->toQuery()->fetchArray();
}
ArticleDao::getArticleDao()
$articleDao->getArticlesByTypes(array('news', 'comment'))
Article::$online
true
Article::$type
Dieser Artikel bietet nur eine kurze Einführung in die Criteria API. Ausführliche Informationen findest du im Artikel Criteria API / NQL.
NQL (n2n Query Language)
Die n2n Query Language ähnelt syntaktisch SQL-Statements, beziehen sich aber, wie die Criteria API, auf Entities und dessen Eigenschaften statt auf Datenbanktabellen und Spalten. Mit
EntityManager::createNqlCriteria()
Criteria
/**
* @param string $type
* @param int $limit
* @param int $num
* @return Article[]
*/
public function getArticlesByType($type, $limit, $num) {
$criteria = $this->em->createNqlCriteria(
'SELECT a FROM Article a WHERE a.online = :online AND a.type = :type',
array('online' => true, 'type' => $type));
return $criteria->limit(0, 30)->toQuery()->fetchAll();
}
In diesem Beispiel haben wir die Methode
ArticleDao::getArticlesByType()
EntityManager::createNqlCriteria()
Placeholder-Werte kannst du auch über
Query::setParameter()
Schreiben
Über den
EntityManager::persist()
EntityManager::remove()
ControllerAdapter::beginTransaction()
ControllerAdapter::commit()
public function doCreateArticle(ArticleDao $articleDao) {
$this->beginTransaction();
$article = new Article('Lorem ipsum');
$article->setText('Lorem ipsum dolor...');
$article->setType(Article::TYPE_COMMENT);
$articleDao->saveArticle($article);
$this->commit();
}
ArticleDao::saveArticle()
public function saveArticle(Article $article) {
$this->em->persist($article);
}
Die
INSERT
DELETE
UPDATE
public function doExample(ArticleDao $articleDao) {
$this->beginTransaction();
$article = $articleDao->getArticleById(1);
$article->setTitle('New title');
$this->commit();
}
In diesem Beispiel wird der Titel in der Datenbank automatisch aktualisiert, sobald
ControllerAdapter::commit()
UPDATE
Logger
Interessiert es dich, welche SQL-Statements der EntityManager generiert und absendet, kannst du sie über den
n2n\\persistence\\PdoLogger
PdoLogger
Pdo
PdoLogger
Pdo::getLogger()
public function saveArticle(Article $article) {
$this->em->persist($article);
var_dump($this->em->getPdo()->getLogger()->getEntries());
}
In diesem Beispiel holen wir über
EntityManager::getPdo()
EntityManager
Pdo
PdoLogger::getEntries()