PageRenderTime 24ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Repository.php

https://gitlab.com/frontycore/yetorm
PHP | 493 lines | 246 code | 88 blank | 159 comment | 26 complexity | 5c3feff52d33fabfa926a91d477116c0 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the YetORM library.
  4. *
  5. * Copyright (c) 2014 Jaroslav Hranička
  6. * Copyright (c) 2013, 2014 Petr Kessler (http://kesspess.1991.cz)
  7. *
  8. * @license MIT
  9. * @link https://bitbucket.org/hranicka/yetorm
  10. * @link https://github.com/uestla/YetORM
  11. */
  12. namespace YetORM;
  13. use Nette;
  14. use Nette\Database as NDatabase;
  15. use Nette\Reflection\AnnotationsParser;
  16. use Nette\Utils\Strings as NStrings;
  17. /**
  18. * @property-read NDatabase\Table\Selection $table
  19. */
  20. abstract class Repository extends Nette\Object
  21. {
  22. /** @deprecated */
  23. const EVENT_BEFORE_PERSIST = 'beforePersist';
  24. /** @deprecated */
  25. const EVENT_AFTER_PERSIST = 'afterPersist';
  26. /** @deprecated */
  27. const EVENT_BEFORE_DELETE = 'beforeDelete';
  28. /** @deprecated */
  29. const EVENT_AFTER_DELETE = 'afterDelete';
  30. const BEFORE_PERSIST = 'beforePersist';
  31. const BEFORE_PERSIST_NEW = 'beforePersistNEW';
  32. const AFTER_PERSIST = 'afterPersist';
  33. const AFTER_PERSIST_NEW = 'afterPersistNew';
  34. const BEFORE_DELETE = 'beforeDelete';
  35. const AFTER_DELETE = 'afterDelete';
  36. /** @var array */
  37. public static $transactionCounter = [];
  38. /** @var array */
  39. private static $reflections = [];
  40. /**
  41. * @var callable[]
  42. * @deprecated
  43. */
  44. public $onBeforePersist = [];
  45. /**
  46. * @var callable[]
  47. * @deprecated
  48. */
  49. public $onAfterPersist = [];
  50. /**
  51. * @var callable[]
  52. * @deprecated
  53. */
  54. public $onBeforeDelete = [];
  55. /**
  56. * @var callable[]
  57. * @deprecated
  58. */
  59. public $onAfterDelete = [];
  60. /**
  61. * @var NDatabase\Context
  62. * @deprecated Use $database instead.
  63. */
  64. protected $dbContext;
  65. /** @var NDatabase\Context */
  66. protected $database;
  67. /** @var string */
  68. protected $tableName = NULL;
  69. /** @var string */
  70. protected $entity = NULL;
  71. /**
  72. * @var array
  73. * @deprecated
  74. */
  75. private $eventHandlers = [];
  76. /** @var array */
  77. private $handlers = [];
  78. /** @param NDatabase\Context $database */
  79. public function __construct(NDatabase\Context $database)
  80. {
  81. $this->dbContext = $database;
  82. $this->database = $database;
  83. if (!isset(self::$transactionCounter[$dsn = $database->getConnection()->getDsn()])) {
  84. self::$transactionCounter[$dsn] = 0;
  85. }
  86. }
  87. /**
  88. * @param string $event
  89. * @param callable $callback
  90. * @return $this
  91. * @deprecated
  92. */
  93. public function attachEventHandler($event, callable $callback)
  94. {
  95. $this->eventHandlers[$event][] = $callback;
  96. return $this;
  97. }
  98. /**
  99. * @param string $event
  100. * @param callable $callback
  101. * @return $this
  102. */
  103. public function handle($event, callable $callback)
  104. {
  105. $this->handlers[$event][] = $callback;
  106. return $this;
  107. }
  108. /**
  109. * @param NDatabase\Table\ActiveRow|array|null $row
  110. * @return Entity
  111. */
  112. public function create(NDatabase\Table\ActiveRow $row = NULL)
  113. {
  114. $class = $this->getEntityClass();
  115. return new $class($row);
  116. }
  117. /**
  118. * @param mixed $id
  119. * @return Entity|null
  120. */
  121. public function find($id)
  122. {
  123. $row = $this->getTable()->get($id);
  124. return ($row) ? $this->create($row) : NULL;
  125. }
  126. /**
  127. * @param array
  128. * @return Entity[]|Collection
  129. */
  130. public function findBy(array $criteria)
  131. {
  132. $selection = $this->getTable();
  133. if ($criteria) {
  134. $selection->where($criteria);
  135. }
  136. return $this->createCollection($selection);
  137. }
  138. /**
  139. * @return Entity[]|Collection
  140. */
  141. public function findAll()
  142. {
  143. return $this->findBy([]);
  144. }
  145. /**
  146. * @param array
  147. * @return Entity|null
  148. */
  149. public function findOneBy(array $criteria)
  150. {
  151. $row = $this->getTable()->where($criteria)->limit(1)->fetch();
  152. return ($row) ? $this->create($row) : NULL;
  153. }
  154. /**
  155. * @param Entity $entity
  156. * @throws \Exception
  157. * @return bool
  158. */
  159. public function persist(Entity $entity)
  160. {
  161. $this->checkEntity($entity);
  162. return $this->transaction(function () use ($entity) {
  163. $row = $entity->toRow();
  164. $isNew = !$row->hasNative();
  165. $this->runEvent(self::EVENT_BEFORE_PERSIST, [$this, $entity]);
  166. if ($isNew) {
  167. $this->invokeEvent(self::BEFORE_PERSIST_NEW, [$entity, $this]);
  168. }
  169. $this->invokeEvent(self::BEFORE_PERSIST, [$entity, $this]);
  170. if ($isNew) {
  171. $inserted = $this->getTable()->insert($row->getModified());
  172. // prevents bug in NDTB if inserting own primary non-AUTO_INCREMENT key
  173. if ($inserted && is_int($inserted)) {
  174. $inserted = $this->getTable()->where('id', $this->database->getInsertId())->fetch();
  175. }
  176. if ($inserted instanceof Nette\Database\Table\ActiveRow) {
  177. $row->setNative($inserted);
  178. }
  179. $return = ($inserted instanceof Nette\Database\IRow || $inserted > 0);
  180. } else {
  181. $return = $row->update();
  182. }
  183. $entity->refresh();
  184. $this->runEvent(self::EVENT_AFTER_PERSIST, [$this, $entity]);
  185. if ($isNew) {
  186. $this->invokeEvent(self::AFTER_PERSIST_NEW, [$entity, $this]);
  187. }
  188. $this->invokeEvent(self::AFTER_PERSIST, [$entity, $this]);
  189. $this->callHandlers($entity->onPersist, [$this, $entity]);
  190. $entity->onPersist = [];
  191. return $return;
  192. });
  193. }
  194. /**
  195. * @param Entity $entity
  196. * @throws \Exception
  197. * @return bool
  198. */
  199. public function delete(Entity $entity)
  200. {
  201. $this->checkEntity($entity);
  202. return $this->transaction(function () use ($entity) {
  203. $entity->loadReferences();
  204. $this->runEvent(self::EVENT_BEFORE_DELETE, [$this, $entity]);
  205. $this->invokeEvent(self::BEFORE_DELETE, [$entity, $this]);
  206. $return = TRUE;
  207. $row = $entity->toRow();
  208. if ($row->hasNative()) {
  209. $return = ($row->getNative()->delete() > 0);
  210. }
  211. $this->runEvent(self::EVENT_AFTER_DELETE, [$this, $entity]);
  212. $this->invokeEvent(self::AFTER_DELETE, [$entity, $this]);
  213. $this->callHandlers($entity->onDelete, [$this, $entity]);
  214. $entity->onDelete = [];
  215. return $return;
  216. });
  217. }
  218. /**
  219. * @param string $name
  220. * @param array $parameters
  221. * @deprecated
  222. */
  223. protected function runEvent($name, array $parameters)
  224. {
  225. $handlers = (isset($this->eventHandlers[$name])) ?
  226. $this->eventHandlers[$name] :
  227. [];
  228. // // Back compatibility
  229. // switch ($name) {
  230. // case self::EVENT_BEFORE_PERSIST:
  231. // $handlers = array_merge($handlers, $this->onBeforePersist);
  232. // break;
  233. // case self::EVENT_AFTER_PERSIST:
  234. // $handlers = array_merge($handlers, $this->onAfterPersist);
  235. // break;
  236. // case self::EVENT_BEFORE_DELETE:
  237. // $handlers = array_merge($handlers, $this->onBeforeDelete);
  238. // break;
  239. // case self::EVENT_AFTER_DELETE:
  240. // $handlers = array_merge($handlers, $this->onAfterDelete);
  241. // break;
  242. // }
  243. $this->callHandlers($handlers, $parameters);
  244. }
  245. /**
  246. * @param string $name
  247. * @param array $parameters
  248. */
  249. protected function invokeEvent($name, array $parameters)
  250. {
  251. if (isset($this->handlers[$name])) {
  252. $this->callHandlers($this->handlers[$name], $parameters);
  253. }
  254. }
  255. /**
  256. * @param NDatabase\Table\Selection $selection
  257. * @param string $entity
  258. * @param string $refTable
  259. * @param string $refColumn
  260. * @return Collection
  261. */
  262. protected function createCollection($selection, $entity = NULL, $refTable = NULL, $refColumn = NULL)
  263. {
  264. return new EntityCollection($selection, $this->getEntityClass($entity), $refTable, $refColumn);
  265. }
  266. /**
  267. * @param string $table
  268. * @return NDatabase\Table\Selection
  269. */
  270. protected function getTable($table = NULL)
  271. {
  272. return $this->database->table($this->getTableName($table));
  273. }
  274. /**
  275. * @param string|null $table
  276. * @throws Exception\InvalidStateException
  277. * @return string
  278. */
  279. protected function getTableName($table = NULL)
  280. {
  281. if ($table === NULL) {
  282. if ($this->tableName === NULL) {
  283. if (($name = static::getReflection()->getAnnotation('table')) !== NULL) {
  284. $this->tableName = $name;
  285. } elseif (!$this->parseName($name)) {
  286. throw new Exception\InvalidStateException("Table name not set.");
  287. }
  288. $this->tableName = strtolower($name);
  289. }
  290. $table = $this->tableName;
  291. }
  292. return $table;
  293. }
  294. /**
  295. * @param string $entity
  296. * @throws Exception\InvalidStateException
  297. * @return string
  298. */
  299. protected function getEntityClass($entity = NULL)
  300. {
  301. if ($entity === NULL) {
  302. if ($this->entity === NULL) {
  303. $ref = static::getReflection();
  304. if (($name = $ref->getAnnotation('entity')) !== NULL) {
  305. $this->entity = AnnotationsParser::expandClassName($name, $ref);
  306. } elseif ($this->parseName($name)) {
  307. $this->entity = AnnotationsParser::expandClassName($name, $ref);
  308. } else {
  309. throw new Exception\InvalidStateException('Entity class not set.');
  310. }
  311. }
  312. $entity = $this->entity;
  313. }
  314. return $entity;
  315. }
  316. /**
  317. * @param callable[] $handlers
  318. * @param array $parameters
  319. */
  320. private function callHandlers(array $handlers, array $parameters)
  321. {
  322. foreach ($handlers as $callback) {
  323. call_user_func_array($callback, $parameters);
  324. }
  325. }
  326. /**
  327. * @param string $name
  328. * @return bool
  329. */
  330. private function parseName(& $name)
  331. {
  332. if (!($m = NStrings::match(static::getReflection()->name, '#([a-z0-9]+)repository$#i'))) {
  333. return FALSE;
  334. }
  335. $name = ucfirst($m[1]);
  336. return TRUE;
  337. }
  338. /**
  339. * @param Entity $entity
  340. * @throws Exception\InvalidArgumentException
  341. */
  342. private function checkEntity(Entity $entity)
  343. {
  344. $class = $this->getEntityClass();
  345. if (!($entity instanceof $class)) {
  346. throw new Exception\InvalidArgumentException("Instance of '$class' expected, '"
  347. . get_class($entity) . "' given.");
  348. }
  349. }
  350. /** @return Nette\Reflection\ClassType|\ReflectionClass */
  351. final static function getReflection()
  352. {
  353. $class = get_called_class();
  354. if (!isset(self::$reflections[$class])) {
  355. self::$reflections[$class] = parent::getReflection();
  356. }
  357. return self::$reflections[$class];
  358. }
  359. // === TRANSACTIONS ====================================================
  360. /**
  361. * @param \Closure $callback
  362. * @throws \Exception
  363. * @return mixed
  364. */
  365. final public function transaction(\Closure $callback)
  366. {
  367. try {
  368. $this->begin();
  369. $return = $callback();
  370. $this->commit();
  371. return $return;
  372. } catch (\Exception $e) {
  373. $this->rollback();
  374. throw $e;
  375. }
  376. }
  377. /** @return void */
  378. final protected function begin()
  379. {
  380. if (self::$transactionCounter[$this->database->getConnection()->getDsn()]++ === 0) {
  381. $this->database->beginTransaction();
  382. }
  383. }
  384. /**
  385. * @throws Exception\InvalidStateException
  386. * @return void
  387. */
  388. final protected function commit()
  389. {
  390. if (self::$transactionCounter[$dsn = $this->database->getConnection()->getDsn()] === 0) {
  391. throw new Exception\InvalidStateException('No transaction started.');
  392. }
  393. if (--self::$transactionCounter[$dsn] === 0) {
  394. $this->database->commit();
  395. }
  396. }
  397. /** @return void */
  398. final protected function rollback()
  399. {
  400. if (self::$transactionCounter[$dsn = $this->database->getConnection()->getDsn()] !== 0) {
  401. $this->database->rollBack();
  402. }
  403. self::$transactionCounter[$dsn] = 0;
  404. }
  405. }