code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Dbal\StorageReflection;

use Nextras\Orm;
use Nextras\Orm\Mapper\IMapper;

interface IStorageReflection extends Orm\StorageReflection\IStorageReflection
{
	/**
	 * Returns primary sequence name. If not supported nor present, returns null.
	 * @return string|null
	 */
	public function getPrimarySequenceName();

	/**
	 * Returns storage name for m:n relationship.
	 * @param  IMapper  $target
	 * @return string
	 */
	public function getManyHasManyStorageName(IMapper $target);

	/**
	 * Returns storage primary keys for m:n storage.
	 * @param  IMapper  $target
	 * @return array
	 */
	public function getManyHasManyStoragePrimaryKeys(IMapper $target);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Dbal\StorageReflection;

use Nette\Caching\Cache;
use Nette\Object;
use Nextras\Dbal\Connection;
use Nextras\Dbal\Platforms\CachedPlatform;
use Nextras\Dbal\Platforms\IPlatform;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\NotSupportedException;

abstract class StorageReflection extends Object implements IStorageReflection
{
	/** @const keys for mapping cache */
	const TO_STORAGE = 0;
	const TO_ENTITY = 1;

	/** @var string */
	public $manyHasManyStorageNamePattern = '%s_x_%s';

	/** @var string */
	protected $storageName;

	/** @var array */
	protected $mappings;

	/** @var array */
	protected $modifiers;

	/** @var array */
	protected $entityPrimaryKey = [];

	/** @var array */
	protected $storagePrimaryKey = [];

	/** @var IPlatform */
	protected $platform;

	public function __construct(Connection $connection, $storageName, array $entityPrimaryKey, Cache $cache)
	{
		$this->platform = new CachedPlatform($connection, $cache->derive('db_reflection'));
		$this->storageName = $storageName;
		$this->entityPrimaryKey = $entityPrimaryKey;

		$cache = $cache->derive('storage_reflection');
		$this->mappings = $cache->load('nextras.orm.storage_reflection.' . md5($this->storageName) . '.mappings', function () {
			return $this->getDefaultMappings();
		});
		$this->modifiers = $cache->load('nextras.orm.storage_reflection.' . md5($this->storageName) . '.modifiers', function () {
			return $this->getDefaultModifiers();
		});
	}

	public function getStorageName()
	{
		return $this->storageName;
	}

	public function getStoragePrimaryKey()
	{
		if (!$this->storagePrimaryKey) {
			$primaryKeys = [];
			foreach ($this->platform->getColumns($this->storageName) as $column => $meta) {
				if ($meta['is_primary']) {
					$primaryKeys[] = $column;
				}
			}
			if (count($primaryKeys) === 0) {
				throw new InvalidArgumentException("Storage '$this->storageName' has not defined any primary key.");
			}
			$this->storagePrimaryKey = $primaryKeys;
		}

		return $this->storagePrimaryKey;
	}

	public function convertEntityToStorage($in)
	{
		$out = [];
		foreach ($in as $key => $val) {
			if (isset($this->mappings[self::TO_STORAGE][$key][0])) {
				$newKey = $this->mappings[self::TO_STORAGE][$key][0];
			} else {
				$newKey = $this->convertEntityToStorageKey($key);
			}
			if (isset($this->modifiers[$newKey])) {
				$newKey .= $this->modifiers[$newKey];
			}
			if (isset($this->mappings[self::TO_STORAGE][$key][1])) {
				$converter = $this->mappings[self::TO_STORAGE][$key][1];
				$out[$newKey] = $converter($val, $newKey);
			} else {
				$out[$newKey] = $val;
			}
		}

		return $out;
	}

	public function convertStorageToEntity($in)
	{
		$out = [];
		foreach ($in as $key => $val) {
			if (isset($this->mappings[self::TO_ENTITY][$key][0])) {
				$newKey = $this->mappings[self::TO_ENTITY][$key][0];
			} else {
				$newKey = $this->convertStorageToEntityKey($key);
			}
			if (isset($this->mappings[self::TO_ENTITY][$key][1])) {
				$converter = $this->mappings[self::TO_ENTITY][$key][1];
				$out[$newKey] = $converter($val, $newKey);
			} else {
				$out[$newKey] = $val;
			}
		}

		return $out;
	}

	public function convertStorageToEntityKey($key)
	{
		if (!isset($this->mappings[self::TO_ENTITY][$key][0])) {
			$this->mappings[self::TO_ENTITY][$key] = [$this->formatEntityKey($key)];
		}

		return $this->mappings[self::TO_ENTITY][$key][0];
	}

	public function convertEntityToStorageKey($key)
	{
		if (!isset($this->mappings[self::TO_STORAGE][$key][0])) {
			$this->mappings[self::TO_STORAGE][$key] = [$this->formatStorageKey($key)];
		}

		return $this->mappings[self::TO_STORAGE][$key][0];
	}

	public function getPrimarySequenceName()
	{
		return $this->platform->getPrimarySequenceName($this->storageName);
	}

	public function getManyHasManyStorageName(IMapper $target)
	{
		return sprintf(
			$this->manyHasManyStorageNamePattern,
			$this->storageName,
			preg_replace('#^(.*\.)?(.*)$#', '$2', $target->getStorageReflection()->getStorageName())
		);
	}

	public function getManyHasManyStoragePrimaryKeys(IMapper $target)
	{
		$targetStorageRefleciton = $target->getStorageReflection();

		$one = $this->getStoragePrimaryKey()[0];
		$two = $targetStorageRefleciton->getStoragePrimaryKey()[0];
		if ($one !== $two) {
			return [$one, $two];
		}

		return $this->findManyHasManyPrimaryColumns(
			$this->getManyHasManyStorageName($target),
			$this->storageName,
			$targetStorageRefleciton->getStorageName()
		);
	}

	/**
	 * Adds mapping.
	 * @param  string   $entity
	 * @param  string   $storage
	 * @param  callable $toEntityCb
	 * @param  callable $toStorageCb
	 * @return StorageReflection
	 */
	public function addMapping($entity, $storage, callable $toEntityCb = null, callable $toStorageCb = null)
	{
		$this->mappings[self::TO_ENTITY][$storage] = [$entity, $toEntityCb];
		$this->mappings[self::TO_STORAGE][$entity] = [$storage, $toStorageCb];
		return $this;
	}

	/**
	 * Adds parameter modifier for data-trasform to Nextras Dbal layer.
	 * @param  string $storageKey
	 * @param  string $saveModifier
	 * @return StorageReflection
	 */
	public function addModifier($storageKey, $saveModifier)
	{
		$this->modifiers[$storageKey] = $saveModifier;
		return $this;
	}

	protected function findManyHasManyPrimaryColumns($joinTable, $sourceTable, $targetTable)
	{
		$useFQN = strpos($sourceTable, '.') !== false;
		$keys = $this->platform->getForeignKeys($joinTable);
		foreach ($keys as $column => $meta) {
			$table = $useFQN
				? $meta['ref_table']
				: preg_replace('#^(.*\.)?(.*)$#', '$2', $meta['ref_table']);

			if ($table === $sourceTable) {
				$sourceId = $column;
			} elseif ($table === $targetTable) {
				$targetId = $column;
			}
		}

		if (!isset($sourceId, $targetId)) {
			throw new InvalidStateException("No primary keys detected for many has many '{$joinTable}' join table.");
		}

		return [$sourceId, $targetId];
	}

	protected function getDefaultMappings()
	{
		$mappings = [self::TO_STORAGE => [], self::TO_ENTITY => []];

		$columns = array_keys($this->platform->getForeignKeys($this->storageName));
		foreach ($columns as $storageKey) {
			$entityKey = $this->formatEntityForeignKey($storageKey);
			$mappings[self::TO_ENTITY][$storageKey] = [$entityKey, null];
			$mappings[self::TO_STORAGE][$entityKey] = [$storageKey, null];
		}

		$storagePrimaryKey = $this->getStoragePrimaryKey();
		if (count($this->entityPrimaryKey) !== count($storagePrimaryKey)) {
			throw new InvalidStateException(
				'Mismatch count of entity primary key (' . implode(', ', $this->entityPrimaryKey)
				. ') with storage primary key (' . implode(', ', $storagePrimaryKey) . ').'
			);
		}

		if (count($storagePrimaryKey) === 1) {
			$entityKey = $this->entityPrimaryKey[0];
			$storageKey = $storagePrimaryKey[0];
			$mappings[self::TO_ENTITY][$storageKey] = [$entityKey, null];
			$mappings[self::TO_STORAGE][$entityKey] = [$storageKey, null];
		}

		return $mappings;
	}

	protected function getDefaultModifiers()
	{
		$modifiers = [];

		switch ($this->platform->getName()) {
			case 'postgresql':
				$types = ['TIMESTAMP' => true];
				break;

			case 'mysql':
				$types = ['DATETIME' => true];
				break;

			default:
				throw new NotSupportedException();
		}

		foreach ($this->platform->getColumns($this->storageName) as $column) {
			if (isset($types[$column['type']])) {
				$modifiers[$column['name']] = $column['is_nullable'] ? '%?dts' : '%dts';
			}
		}

		return $modifiers;
	}

	abstract protected function formatStorageKey($key);

	abstract protected function formatEntityKey($key);

	abstract protected function formatEntityForeignKey($key);
}

repo_name: Cerx/ToDo

license: mit


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Dbal\StorageReflection;

use Nextras\Orm\StorageReflection\StringHelper;

class UnderscoredStorageReflection extends StorageReflection
{
	protected function formatStorageKey($key)
	{
		return StringHelper::underscore($key);
	}

	protected function formatEntityKey($key)
	{
		return StringHelper::camelize($key);
	}

	protected function formatEntityForeignKey($key)
	{
		if (substr($key, -3) === '_id') {
			$key = substr($key, 0, -3);
		}
		return $this->formatEntityKey($key);
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper;

use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Repository\IRepository;
use Nextras\Orm\StorageReflection\IStorageReflection;
use stdClass;

interface IMapper
{
	/**
	 * Returns all entities.
	 * @return ICollection
	 */
	public function findAll();

	/**
	 * Transforms value from mapper, which is not a collection.
	 * @return ICollection
	 */
	public function toCollection($data);

	/**
	 * Returns cache object for collections.
	 * @return stdClass
	 */
	public function getCollectionCache();

	/**
	 * Clears cache object for collection.
	 */
	public function clearCollectionCache();

	/**
	 * Creates collection with HasOne mapper.
	 * @param  PropertyMetadata $metadata
	 * @param  IEntity          $parent
	 * @return ICollection
	 */
	public function createCollectionManyHasOne(PropertyMetadata $metadata, IEntity $parent);

	/**
	 * Creates collection with OneHasOneDirected mapper.
	 * @param  PropertyMetadata $metadata
	 * @param  IEntity          $parent
	 * @return ICollection
	 */
	public function createCollectionOneHasOne(PropertyMetadata $metadata, IEntity $parent);

	/**
	 * Creates collection with ManyHasMany mapper.
	 * @param  IMapper          $mapper
	 * @param  PropertyMetadata $metadata
	 * @param  IEntity          $parent
	 * @return ICollection
	 */
	public function createCollectionManyHasMany(IMapper $mapper, PropertyMetadata $metadata, IEntity $parent);

	/**
	 * Creates collection with OneHasMany mapper.
	 * @param  PropertyMetadata $metadata
	 * @param  IEntity          $parent
	 * @return ICollection
	 */
	public function createCollectionOneHasMany(PropertyMetadata $metadata, IEntity $parent);

	/**
	 * @param  IRepository  $repository
	 */
	public function setRepository(IRepository $repository);

	/**
	 * @return IRepository
	 */
	public function getRepository();

	/**
	 * @return string
	 */
	public function getTableName();

	/**
	 * @return IStorageReflection
	 */
	public function getStorageReflection();

	/**
	 * @see    IRepository::persist()
	 * @param  IEntity  $entity
	 * @return IEntity
	 */
	public function persist(IEntity $entity);

	/**
	 * @see    IRepository::remove()
	 * @param  IEntity  $entity
	 * @return bool
	 */
	public function remove(IEntity $entity);

	/**
	 * @see    IRepository::flush()
	 * @return void
	 */
	public function flush();

	/**
	 * @see    IRepository::roolback()
	 * @return void
	 */
	public function rollback();
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper;

use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Traversable;

interface IRelationshipMapper
{
	/**
	 * Returns iterator.
	 * @param  IEntity      $parent
	 * @param  ICollection  $collection
	 * @return Traversable
	 */
	public function getIterator(IEntity $parent, ICollection $collection);

	/**
	 * Returns iterator's counts.
	 * @param  IEntity      $parent
	 * @param  ICollection  $collection
	 * @return int
	 */
	public function getIteratorCount(IEntity $parent, ICollection $collection);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper;

use Nextras\Orm\Entity\IEntity;

interface IRelationshipMapperManyHasMany extends IRelationshipMapper
{
	/**
	 * Adds entity relationshios with passed ids.
	 * @param  IEntity  $parent
	 * @param  array    $add array of ids to be connected
	 */
	public function add(IEntity $parent, array $add);

	/**
	 * Removes entity relationships with passed ids.
	 * @param  IEntity  $parent
	 * @param  array    $remove array of connected ids to be removed
	 */
	public function remove(IEntity $parent, array $remove);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper;

use Nextras\Orm\Mapper\Dbal\DbalMapper;

/**
 * Default mapper.
 */
abstract class Mapper extends DbalMapper
{
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Memory;

use Nextras\Orm\Collection\ArrayCollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\IProperty;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\IOException;
use Nextras\Orm\LogicException;
use Nextras\Orm\Mapper\BaseMapper;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Relationships\IRelationshipCollection;
use Nextras\Orm\StorageReflection\CommonReflection;

/**
 * Array Mapper.
 */
abstract class ArrayMapper extends BaseMapper
{
	/** @var IEntity[]|null */
	protected $data;

	/** @var array */
	protected $dataToStore = [];

	/** @var array */
	protected $relationshipData = [];

	/** @var resource */
	static protected $lock;

	public function findAll()
	{
		return new ArrayCollection($this->getData(), $this->getRepository());
	}

	public function toCollection($data)
	{
		if (!is_array($data)) {
			throw new InvalidArgumentException("ArrayMapper can convert only array to ICollection.");
		}
		return new ArrayCollection($data, $this->getRepository());
	}

	public function createCollectionManyHasOne(PropertyMetadata $metadata, IEntity $parent)
	{
		$collection = $this->findAll();
		$collection->setRelationshipMapping(new RelationshipMapperManyHasOne($metadata), $parent);
		return $collection;
	}

	public function createCollectionOneHasOne(PropertyMetadata $metadata, IEntity $parent)
	{
		$collection = $this->findAll();
		$collection->setRelationshipMapping(
			$metadata->relationship->isMain
				? new RelationshipMapperManyHasOne($metadata)
				: new RelationshipMapperOneHasOne($this, $metadata),
			$parent
		);
		return $collection;
	}

	public function createCollectionManyHasMany(IMapper $mapperTwo, PropertyMetadata $metadata, IEntity $parent)
	{
		$targetMapper = $metadata->relationship->isMain ? $mapperTwo : $this;
		$collection = $targetMapper->findAll();
		$collection->setRelationshipMapping(new RelationshipMapperManyHasMany($metadata, $this), $parent);
		return $collection;
	}

	public function createCollectionOneHasMany(PropertyMetadata $metadata, IEntity $parent)
	{
		$collection = $this->findAll();
		$collection->setRelationshipMapping(new RelationshipMapperOneHasMany($this, $metadata), $parent);
		return $collection;
	}

	public function clearCollectionCache()
	{
		parent::clearCollectionCache();
		$this->data = null;
	}

	public function &getRelationshipDataStorage($key)
	{
		$value = & $this->relationshipData[$key];
		$value = (array) $value;
		return $value;
	}

	public function persist(IEntity $entity)
	{
		$this->initializeData();

		$data = $this->entityToArray($entity);
		$data = $this->getStorageReflection()->convertEntityToStorage($data);

		if ($entity->isPersisted()) {
			$id = $entity->getPersistedId();
			$primaryValue = implode(',', (array) $id);

		} else {
			$this->lock();
			try {
				$storedData = $this->readEntityData();
				if (!$entity->hasValue('id')) {
					$id = $storedData ? max(array_keys($storedData)) + 1 : 1;
					$storagePrimaryKey = $this->storageReflection->getStoragePrimaryKey();
					$data[$storagePrimaryKey[0]] = $id;
				} else {
					$id = $entity->getValue('id');
				}
				$primaryValue = implode(',', (array) $id);
				if (isset($storedData[$primaryValue])) {
					throw new InvalidStateException("Unique constraint violation: entity with '$primaryValue' primary value already exists.");
				}
				$storedData[$primaryValue] = null;
				$this->saveEntityData($storedData);
			} finally {
				$this->unlock();
			}
		}

		$this->data[$primaryValue] = $entity;
		$this->dataToStore[$primaryValue] = $data;
		return $id;
	}

	public function remove(IEntity $entity)
	{
		$this->initializeData();
		$id = implode(',', (array) $entity->getPersistedId());
		$this->data[$id] = null;
		$this->dataToStore[$id] = null;
	}

	public function flush()
	{
		parent::flush();
		$storageData = $this->readEntityData();
		foreach ($this->dataToStore as $id => $data) {
			$storageData[$id] = $data;
		}
		$this->saveEntityData($storageData);
		$this->dataToStore = [];
	}

	public function rollback()
	{
		$this->data = null;
	}

	protected function createStorageReflection()
	{
		return new CommonReflection(
			$this,
			$this->getTableName(),
			$this->getRepository()->getEntityMetadata()->getPrimaryKey()
		);
	}

	protected function initializeData()
	{
		if ($this->data !== null) {
			return;
		}

		$repository = $this->getRepository();
		$data = $this->readEntityData();

		$this->data = [];
		foreach ($data as $row) {
			if ($row === null) {
				// auto increment placeholder
				continue;
			}

			$entity = $repository->hydrateEntity($row);
			$this->data[implode(',', (array) $entity->getPersistedId())] = $entity;
		}
	}

	protected function getData()
	{
		$this->initializeData();
		return array_filter($this->data);
	}

	protected function lock()
	{
		if (self::$lock) {
			throw new LogicException('Critical section has already beed entered.');
		}

		$file = realpath(sys_get_temp_dir()) . '/NextrasOrmArrayMapper.lock.' . md5(__FILE__);
		$handle = fopen($file, 'c+');
		if (!$handle) {
			throw new IOException('Unable to create critical section.');
		}

		flock($handle, LOCK_EX);
		self::$lock = $handle;
	}

	protected function unlock()
	{
		if (!self::$lock) {
			throw new LogicException('Critical section has not been initialized.');
		}

		flock(self::$lock, LOCK_UN);
		fclose(self::$lock);
		self::$lock = null;
	}

	protected function entityToArray(IEntity $entity)
	{
		$return = [];
		$metadata = $entity->getMetadata();

		foreach ($metadata->getProperties() as $name => $metadataProperty) {
			if ($metadataProperty->isVirtual) {
				continue;
			} elseif ($metadataProperty->isPrimary && !$entity->hasValue($name)) {
				continue;
			}

			$property = $entity->getProperty($name);
			if ($property instanceof IRelationshipCollection) {
				continue;
			}

			if ($property instanceof IProperty) {
				$value = $property->getRawValue();

			} else {
				$value = $entity->getValue($name);
			}

			$return[$name] = $value;
		}

		return $return;
	}

	protected function readEntityData()
	{
		list($data, $relationshipData) = $this->readData() ?: [[], []];
		if (!$this->relationshipData) {
			$this->relationshipData = $relationshipData;
		}
		return $data;
	}

	protected function saveEntityData(array $data)
	{
		$this->saveData([$data, $this->relationshipData]);
	}

	/**
	 * Reads stored data
	 * @return array
	 */
	abstract protected function readData();

	/**
	 * Stores data
	 * @param  array    $data
	 */
	abstract protected function saveData(array $data);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Memory;

use Nette\Object;
use Nextras\Orm\Collection\EntityIterator;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Mapper\IRelationshipMapperManyHasMany;

class RelationshipMapperManyHasMany extends Object implements IRelationshipMapperManyHasMany
{
	/** @var PropertyMetadata */
	protected $metadata;

	/** @var ArrayMapper */
	protected $mapper;

	public function __construct(PropertyMetadata $metadata, ArrayMapper $mapper)
	{
		$this->metadata = $metadata;
		$this->mapper = $mapper;
	}

	public function getIterator(IEntity $parent, ICollection $collection)
	{
		if ($this->metadata->relationship->isMain) {
			$relationshipData = $this->mapper->getRelationshipDataStorage($this->metadata->name);
			$id = $parent->getValue('id');
			$ids = isset($relationshipData[$id]) ? array_keys($relationshipData[$id]) : [];
		} else {
			$ids = [];
			$parentId = $parent->getValue('id');
			$relationshipData = $this->mapper->getRelationshipDataStorage($this->metadata->relationship->property);
			foreach ($relationshipData as $id => $parentIds) {
				if (isset($parentIds[$parentId])) {
					$ids[] = $id;
				}
			}
		}

		$data = $collection->findBy(['id' => $ids])->fetchAll();
		return new EntityIterator($data);
	}

	public function getIteratorCount(IEntity $parent, ICollection $collection)
	{
		return count($this->getIterator($parent, $collection));
	}

	public function add(IEntity $parent, array $add)
	{
		$id = $parent->getValue('id');
		$data = & $this->mapper->getRelationshipDataStorage($this->metadata->name);
		foreach ($add as $addId) {
			$data[$id][$addId] = true;
		}
	}

	public function remove(IEntity $parent, array $remove)
	{
		$id = $parent->getValue('id');
		$data = & $this->mapper->getRelationshipDataStorage($this->metadata->name);
		foreach ($remove as $removeId) {
			unset($data[$id][$removeId]);
		}
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Memory;

use Nette\Object;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\NotSupportedException;

class RelationshipMapperManyHasOne extends Object implements IRelationshipMapper
{
	/** @var PropertyMetadata */
	protected $metadata;

	public function __construct(PropertyMetadata $metadata)
	{
		$this->metadata = $metadata;
	}

	public function getIterator(IEntity $parent, ICollection $collection)
	{
		$key = $parent->getRawValue($this->metadata->name);
		return [$key ? $collection->getBy(['id' => $key]) : null];
	}

	public function getIteratorCount(IEntity $parent, ICollection $collection)
	{
		throw new NotSupportedException();
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Memory;

use Nette\Object;
use Nextras\Orm\Collection\EntityIterator;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Mapper\IRelationshipMapper;

class RelationshipMapperOneHasMany extends Object implements IRelationshipMapper
{
	/** @var PropertyMetadata */
	protected $metadata;

	/** @var string */
	protected $joinStorageKey;

	public function __construct(IMapper $targetMapper, PropertyMetadata $metadata)
	{
		$this->metadata = $metadata;
		$this->joinStorageKey = $targetMapper->getStorageReflection()->convertEntityToStorageKey($this->metadata->relationship->property);
	}

	public function getIterator(IEntity $parent, ICollection $collection)
	{
		$data = $collection->findBy(["this->{$this->joinStorageKey}->id" => $parent->getValue('id')])->fetchAll();
		return new EntityIterator($data);
	}

	public function getIteratorCount(IEntity $parent, ICollection $collection)
	{
		return count($this->getIterator($parent, $collection));
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Mapper\Memory;

use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;

class RelationshipMapperOneHasOne extends RelationshipMapperOneHasMany
{
	public function getIterator(IEntity $parent, ICollection $collection)
	{
		return [parent::getIterator($parent, $collection)->current()];
	}

	public function getIteratorCount(IEntity $parent, ICollection $collection)
	{
		throw new NotSupportedException();
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Model;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Repository\IRepository;

interface IModel
{
	/** @const use as an argument when needed */
	const I_KNOW_WHAT_I_AM_DOING = 'i_know_what_i_am_doing';

	/**
	 * Returns true if repository with name is attached to model.
	 * @param  string   $name
	 * @return bool
	 */
	public function hasRepositoryByName($name);

	/**
	 * Returns repository by repository name.
	 * @param  string   $name
	 * @return IRepository
	 */
	public function getRepositoryByName($name);

	/**
	 * Returns true if repository class is attached to model.
	 * @param  string   $className
	 * @return bool
	 */
	public function hasRepository($className);

	/**
	 * Returns repository by repository class.
	 * @param  string   $className
	 * @return IRepository
	 */
	public function getRepository($className);

	/**
	 * Returns repository associated for entity type.
	 * @param  IEntity|string   $entity
	 * @return IRepository
	 */
	public function getRepositoryForEntity($entity);

	/**
	 * Returns entity metadata storage.
	 * @return MetadataStorage
	 */
	public function getMetadataStorage();

	/**
	 * Persist the entity with cascade.
	 * @param  IEntity $entity
	 * @return IEntity
	 */
	public function persist(IEntity $entity, $withCascade = true);

	/**
	 * @param  IEntity $entity
	 * @param  bool $withCascade
	 * @return mixed
	 */
	public function remove(IEntity $entity, $withCascade = true);

	/**
	 * Flushes all persisted changes in repositories.
	 * @return void
	 */
	public function flush();

	/**
	 * Persist the entity with cascade and flushes the model.
	 * @param  IEntity $entity
	 * @return IEntity
	 */
	public function persistAndFlush(IEntity $entity);

	/**
	 * USE ONLY IF YOU ARE SURE YOU KNOW WHAT ARE YOU DOING.
	 * Clears repository identity map and other possible caches.
	 * Make sure that all references to already used entites are released,
	 * this makes possible to free the memory for garbage collector.
	 * Orm will not allow you to work with these entities anymore.
	 * @dangerous
	 * @internal
	 * @ignore
	 */
	public function clearIdentityMapAndCaches($areYouSure);
}


code:
<?php

namespace Nextras\Orm\Model;

use Nextras\Orm\Repository\IRepository;

interface IRepositoryLoader
{
	/**
	 * Returns true if repository exists.
	 * @param  string $className
	 * @return bool
	 */
	public function hasRepository($className);

	/**
	 * Returns instance of repository.
	 * @param  string   $className
	 * @return IRepository
	 */
	public function getRepository($className);

	/**
	 * Checks, if repository has been already created.
	 * @param  string   $className
	 * @return bool
	 */
	public function isCreated($className);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Model;

use Nette\Caching\Cache;
use Nette\Object;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory;
use Nextras\Orm\Entity\Reflection\MetadataValidator;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;

class MetadataStorage extends Object
{
	/** @var EntityMetadata[] */
	private static $metadata;

	public static function get($className)
	{
		if (!isset(static::$metadata[$className])) {
			if (static::$metadata === null) {
				throw new InvalidStateException("MetadataStorage::get() called too early. You have to instantiate your model first.");
			}
			throw new InvalidArgumentException("Entity metadata for '{$className}' does not exist.");
		}
		return static::$metadata[$className];
	}

	public function __construct(
		array $entityClassesMap,
		Cache $cache,
		IMetadataParserFactory $metadataParserFactory,
		IRepositoryLoader $repositoryLoader
	)
	{
		static::$metadata = $cache->derive('metadata')->load(
			$entityClassesMap,
			function (& $dp) use ($entityClassesMap, $metadataParserFactory, $repositoryLoader) {
				$metadata = [];
				$annotationParser = $metadataParserFactory->create($entityClassesMap);
				foreach (array_keys($entityClassesMap) as $className) {
					$metadata[$className] = $annotationParser->parseMetadata($className, $dp[Cache::FILES]);
				}

				$validator = new MetadataValidator();
				$validator->validate($metadata, $repositoryLoader);

				return $metadata;
			}
		);
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Model;

use Nette\Object;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\LogicException;
use Nextras\Orm\Relationships\IRelationshipCollection;
use Nextras\Orm\Repository\IRepository;
use Nextras\Orm\Repository\PersistenceHelper;
use Nextras\Orm\Repository\RemovalHelper;

class Model extends Object implements IModel
{
	/** @var array of callbacks with (IEntity[] $persisted, IEntity $removed) arguments */
	public $onFlush = [];

	/** @var IRepositoryLoader */
	private $loader;

	/** @var MetadataStorage */
	private $metadataStorage;

	/** @var array */
	private $configuration;

	/**
	 * Creates repository list configuration.
	 * @param  IRepository[] $repositories
	 * @return array
	 */
	public static function getConfiguration(array $repositories)
	{
		$config = [[], [], []];
		foreach ($repositories as $name => $repository) {
			$className = is_object($repository) ? get_class($repository) : $repository;
			$config[0][$className] = true;
			$config[1][$name] = $className;
			foreach ($repository::getEntityClassNames() as $entityClassName) {
				$config[2][$entityClassName] = $className;
			}
		}
		return $config;
	}

	public function __construct(array $configuration, IRepositoryLoader $repositoryLoader, MetadataStorage $metadataStorage)
	{
		$this->loader = $repositoryLoader;
		$this->metadataStorage = $metadataStorage;
		$this->configuration = $configuration;
	}

	public function hasRepositoryByName($name)
	{
		return isset($this->configuration[1][$name]);
	}

	public function getRepositoryByName($name)
	{
		if (!isset($this->configuration[1][$name])) {
			throw new InvalidArgumentException("Repository '$name' does not exist.");
		}
		return $this->getRepository($this->configuration[1][$name]);
	}

	public function hasRepository($className)
	{
		return isset($this->configuration[0][$className]);
	}

	public function getRepository($className)
	{
		if (!isset($this->configuration[0][$className])) {
			throw new InvalidArgumentException("Repository '$className' does not exist.");
		}
		return $this->loader->getRepository($className);
	}

	public function getRepositoryForEntity($entity)
	{
		$entityClassName = is_string($entity) ? $entity : get_class($entity);
		if (!isset($this->configuration[2][$entityClassName])) {
			throw new InvalidArgumentException("Repository for '$entityClassName' entity does not exist.");
		}
		return $this->getRepository($this->configuration[2][$entityClassName]);
	}

	public function getMetadataStorage()
	{
		return $this->metadataStorage;
	}

	/** @inheritdoc */
	public function persist(IEntity $entity, $withCascade = true)
	{
		$queue = PersistenceHelper::getCascadeQueue($entity, $this, $withCascade);
		foreach ($queue as $object) {
			if ($object instanceof IEntity) {
				$repository = $this->configuration[2][get_class($object)];
				$this->loader->getRepository($repository)->doPersist($object);
			} elseif ($object instanceof IRelationshipCollection) {
				$object->doPersist();
			}
		}
		return $entity;
	}

	public function remove(IEntity $entity, $withCascade = true)
	{
		$queuePersist = $queueRemove = [];
		RemovalHelper::getCascadeQueueAndSetNulls($entity, $this, $withCascade, $queuePersist, $queueRemove);
		foreach ($queuePersist as $object) {
			if ($object instanceof IEntity) {
				$repository = $this->configuration[2][get_class($object)];
				$this->loader->getRepository($repository)->doPersist($object);
			} elseif ($object instanceof IRelationshipCollection) {
				$object->doPersist();
			}
		}
		foreach ($queueRemove as $object) {
			$repository = $this->configuration[2][get_class($object)];
			$this->loader->getRepository($repository)->doRemove($object);
		}
		return $entity;
	}

	/** @inheritdoc */
	public function flush()
	{
		$allPersisted = [];
		$allRemoved = [];
		foreach ($this->getLoadedRepositories() as $repository) {
			list($persisted, $removed) = $repository->doFlush();
			$allPersisted = array_merge($allPersisted, $persisted);
			$allRemoved = array_merge($allRemoved, $removed);
		}

		$this->onFlush($allPersisted, $allRemoved);
	}

	/** @inheritdoc */
	public function persistAndFlush(IEntity $entity)
	{
		$this->persist($entity);
		$this->flush();
		return $entity;
	}

	public function clearIdentityMapAndCaches($areYouSure)
	{
		if ($areYouSure !== self::I_KNOW_WHAT_I_AM_DOING) {
			throw new LogicException('Use this method only if you are sure what are you doing.');
		}

		foreach ($this->getLoadedRepositories() as $repository) {
			$repository->doClearIdentityMap($areYouSure);
		}
	}

	/**
	 * Returns repository by name.
	 * @param  string   $name
	 * @return IRepository
	 */
	public function &__get($name)
	{
		$repository = $this->getRepositoryByName($name);
		return $repository;
	}

	/** @return IRepository[] */
	private function getLoadedRepositories()
	{
		$repositories = [];
		foreach (array_keys($this->configuration[0]) as $className) {
			if ($this->loader->isCreated($className)) {
				$repositories[] = $this->loader->getRepository($className);
			}
		}

		return $repositories;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Model;

use Nette\Caching\Cache;
use Nette\Object;
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory;
use Nextras\Orm\Entity\Reflection\MetadataParserFactory;
use Nextras\Orm\Repository\IRepository;

class SimpleModelFactory extends Object
{
	/** @var Cache */
	private $cache;

	/** @var IRepository[] */
	private $repositories;

	/** @var IMetadataParserFactory */
	private $metadataParserFactory;

	public function __construct(Cache $cache, array $repositories, IMetadataParserFactory $metadataParserFactory = null)
	{
		$this->cache = $cache;
		$this->repositories = $repositories;
		$this->metadataParserFactory = $metadataParserFactory;
	}

	/**
	 * @return Model
	 */
	public function create()
	{
		$config   = Model::getConfiguration($this->repositories);
		$parser   = $this->metadataParserFactory ?: new MetadataParserFactory();
		$loader   = new SimpleRepositoryLoader($this->repositories);
		$metadata = new MetadataStorage($config[2], $this->cache, $parser, $loader);
		$model    = new Model($config, $loader, $metadata);

		foreach ($this->repositories as $repository) {
			$repository->setModel($model);
		}

		return $model;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Model;

use Nette\Object;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\Repository\IRepository;

class SimpleRepositoryLoader extends Object implements IRepositoryLoader
{
	/** @var IRepository[] */
	private $repositories;

	/**
	 * @param IRepository[] $repositories
	 */
	public function __construct(array $repositories)
	{
		$this->repositories = [];
		foreach ($repositories as $repository) {
			$this->repositories[get_class($repository)] = $repository;
		}
	}

	public function hasRepository($className)
	{
		return isset($this->repositories[$className]);
	}

	public function getRepository($className)
	{
		if (!isset($this->repositories[$className])) {
			throw new InvalidArgumentException("Repository '$className' not defined.");
		}
		return $this->repositories[$className];
	}

	public function isCreated($className)
	{
		return true;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Nette\Object;
use Nextras\Orm\Collection\ArrayCollection;
use Nextras\Orm\Collection\EmptyCollection;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\Repository\IRepository;

abstract class HasMany extends Object implements IRelationshipCollection
{
	/** @var IEntity */
	protected $parent;

	/** @var PropertyMetadata */
	protected $metadata;

	/** @var ICollection */
	protected $collection;

	/** @var IEntity[] */
	protected $toAdd = [];

	/** @var IEntity[] */
	protected $toRemove = [];

	/** @var IRepository */
	protected $targetRepository;

	/** @var bool */
	protected $updatingReverseRelationship = false;

	/** @var bool */
	protected $isModified = false;

	/** @var IRelationshipMapper */
	protected $relationshipMapper;

	public function __construct(IEntity $parent, PropertyMetadata $metadata)
	{
		$this->parent = $parent;
		$this->metadata = $metadata;
	}

	public function setParent(IEntity $parent)
	{
		$this->parent = $parent;
	}

	public function setRawValue($value)
	{
		if ($value !== null) { // null passed when property is initialized
			$this->set($value);
		}
	}

	public function add($entity)
	{
		if ($this->updatingReverseRelationship) {
			return null;
		}

		$entity = $this->createEntity($entity);
		$entityHash = spl_object_hash($entity);

		if (isset($this->toRemove[$entityHash])) {
			unset($this->toRemove[$entityHash]);
		} else {
			$this->toAdd[$entityHash] = $entity;
		}

		$this->updateRelationshipAdd($entity);
		$this->modify();
		$this->collection = null;
		return $entity;
	}

	public function remove($entity)
	{
		if ($this->updatingReverseRelationship) {
			return null;
		}

		$entity = $this->createEntity($entity);
		$entityHash = spl_object_hash($entity);

		if (isset($this->toAdd[$entityHash])) {
			unset($this->toAdd[$entityHash]);
		} else {
			$this->toRemove[$entityHash] = $entity;
		}

		$this->updateRelationshipRemove($entity);
		$this->modify();
		$this->collection = null;
		return $entity;
	}

	public function has($entity)
	{
		$entity = $this->createEntity($entity, false);
		if (!$entity) {
			return false;
		}

		$entityHash = spl_object_hash($entity);
		if (isset($this->toAdd[$entityHash])) {
			return true;

		} elseif (isset($this->toRemove[$entityHash])) {
			return false;

		} elseif (!$entity->isPersisted()) {
			return false;

		} else {
			return (bool) $this->getCollection()->getBy(['id' => $entity->getValue('id')]);
		}
	}

	public function set(array $data)
	{
		foreach ($this->getCollection() as $entity) {
			$this->remove($entity);
		}

		foreach ($data as $entity) {
			$this->add($entity);
		}

		return $this;
	}

	public function get()
	{
		return clone $this->getCollection(true);
	}

	public function count()
	{
		return iterator_count($this->getIterator());
	}

	public function countStored()
	{
		/** @var ICollection $collection */
		$collection = $this->collection === null && !$this->toAdd && !$this->toRemove && $this->parent->isPersisted()
			? $this->getCachedCollection(null)
			: $this->getCollection();
		return $collection->getEntityCount($this->parent);
	}

	/**
	 * @return ICollection|IEntity[]|\Traversable
	 */
	public function getIterator()
	{
		/** @var ICollection $collection */
		$collection = $this->collection === null && !$this->toAdd && !$this->toRemove && $this->parent->isPersisted()
			? $this->getCachedCollection(null)
			: $this->getCollection();
		return $collection->getEntityIterator($this->parent);
	}

	public function isLoaded()
	{
		return !($this->collection === null && empty($this->toAdd) && empty($this->toRemove));
	}

	public function isModified()
	{
		return $this->isModified;
	}

	/**
	 * Returns primary values of enitities in relationship.
	 * @return mixed[]
	 */
	public function getRawValue()
	{
		$primaryValues = [];
		foreach ($this->getIterator() as $entity) {
			if ($entity->isPersisted()) {
				$primaryValues[] = $entity->getValue('id');
			}
		}
		return $primaryValues;
	}

	/**
	 * @return ICollection
	 */
	protected function getCollection($forceNew = false)
	{
		if ($this->collection !== null && !$forceNew) {
			return $this->collection;
		}

		if ($this->parent->isPersisted()) {
			$collection = $this->createCollection();
		} else {
			$collection = new EmptyCollection();
		}

		if ($this->toAdd || $this->toRemove) {
			$all = [];

			foreach ($collection as $entity) {
				$all[spl_object_hash($entity)] = $entity;
			}
			foreach ($this->toAdd as $hash => $entity) {
				$all[$hash] = $entity;
			}
			foreach ($this->toRemove as $hash => $entity) {
				unset($all[$hash]);
			}

			$collection = new ArrayCollection(array_values($all), $this->getTargetRepository());
			$collection = $this->applyDefaultOrder($collection);
		}

		return $this->collection = $collection;
	}

	/**
	 * @param  string   $collectionName
	 * @return ICollection
	 */
	protected function getCachedCollection($collectionName)
	{
		$key = $this->metadata->name . '_' . $collectionName;
		$cache = $this->parent->getRepository()->getMapper()->getCollectionCache();

		if (!isset($cache->$key)) {
			if ($collectionName !== null) {
				$filterMethod = 'filter' . $collectionName;
				$cache->$key = call_user_func([$this->parent, $filterMethod], $this->createCollection());
			} else {
				$cache->$key = $this->createCollection();
			}
		}

		if (!$collectionName) {
			$this->collection = $cache->$key;
		}
		return $cache->$key;
	}

	/**
	 * @param  IEntity|mixed    $entity
	 * @param  bool             $need
	 * @return IEntity
	 */
	protected function createEntity($entity, $need = true)
	{
		if ($entity instanceof IEntity) {
			if ($model = $entity->getModel(false)) {
				$repository = $model->getRepositoryForEntity($this->parent);
				$repository->attach($this->parent);

			} elseif ($model = $this->parent->getModel(false)) {
				$repository = $model->getRepositoryForEntity($entity);
				$repository->attach($entity);
			}

			return $entity;

		} else {
			$foundEntity = $this->getTargetRepository()->getById($entity);
			if (!$foundEntity && $need) {
				throw new InvalidStateException("Entity with primary value '$entity' was not found.");
			}

			return $foundEntity;
		}
	}

	public function __clone()
	{
		$this->collection = null;
	}

	/**
	 * @return IRepository
	 */
	protected function getTargetRepository()
	{
		if (!$this->targetRepository) {
			$this->targetRepository = $this->parent->getModel()->getRepository($this->metadata->relationship->repository);
		}

		return $this->targetRepository;
	}

	protected function getRelationshipMapper()
	{
		if (!$this->relationshipMapper) {
			$this->relationshipMapper = $this->createCollection()->getRelationshipMapper();
		}

		return $this->relationshipMapper;
	}

	protected function applyDefaultOrder(ICollection $collection)
	{
		if ($this->metadata->relationship->order !== null) {
			return $collection->orderBy($this->metadata->relationship->order[0], $this->metadata->relationship->order[1]);
		} else {
			return $collection;
		}
	}

	/**
	 * Sets relationship (and entity) as modified.
	 * @return void
	 */
	abstract protected function modify();

	/**
	 * Returns collection for has many relationship.
	 * @return ICollection
	 */
	abstract protected function createCollection();

	/**
	 * Updates relationship change for the $entity.
	 * @param  IEntity $entity
	 * @return void
	 */
	abstract protected function updateRelationshipAdd(IEntity $entity);

	/**
	 * Updates relationship change for the $entity.
	 * @param  IEntity $entity
	 * @return void
	 */
	abstract protected function updateRelationshipRemove(IEntity $entity);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Nette\Object;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\NullValueException;
use Nextras\Orm\Repository\IRepository;

abstract class HasOne extends Object implements IRelationshipContainer
{
	/** @var IEntity */
	protected $parent;

	/** @var PropertyMetadata */
	protected $metadata;

	/** @var mixed */
	protected $primaryValue;

	/** @var IEntity|null|false */
	protected $value = false;

	/** @var IRepository */
	protected $targetRepository;

	/** @var bool */
	protected $updatingReverseRelationship = false;

	/** @var bool */
	protected $isModified;

	/** @var IRelationshipMapper */
	protected $relationshipMapper;

	public function __construct(IEntity $parent, PropertyMetadata $metadata)
	{
		$this->parent = $parent;
		$this->metadata = $metadata;
	}

	public function setParent(IEntity $parent)
	{
		$this->parent = $parent;
	}

	public function setRawValue($value)
	{
		$this->primaryValue = $value;
	}

	public function getRawValue()
	{
		return $this->getPrimaryValue();
	}

	public function setInjectedValue($value)
	{
		$this->set($value);
	}

	public function &getInjectedValue()
	{
		$value = $this->getEntity(false);
		return $value;
	}

	public function hasInjectedValue()
	{
		return $this->getEntity(true) !== null;
	}

	public function isLoaded()
	{
		return $this->value !== false;
	}

	public function set($value, $allowNull = false)
	{
		if ($this->updatingReverseRelationship) {
			return null;
		}

		$value = $this->createEntity($value, $allowNull);

		if ($this->isChanged($value)) {
			$this->modify();
			$oldValue = $this->value;
			if ($oldValue === false) {
				$primaryValue = $this->getPrimaryValue();
				$oldValue = $primaryValue !== null ? $this->getTargetRepository()->getById($primaryValue) : null;
			}
			$this->updateRelationship($oldValue, $value, $allowNull);
		}

		$this->primaryValue = $value && $value->isPersisted() ? $value->getValue('id') : null;
		$this->value = $value;
	}

	public function getEntity($allowNull = false)
	{
		if ($this->value === false) {
			if (!$this->parent->isPersisted()) {
				$entity = null;
			} else {
				$collection = $this->getCachedCollection(null);
				$entity = $collection->getEntityIterator($this->parent)[0];
			}

			$this->set($entity, $allowNull);
		}

		if ($this->value === null && !$this->metadata->isNullable && !$allowNull) {
			throw new NullValueException($this->parent, $this->metadata);
		}

		return $this->value;
	}

	public function isModified()
	{
		return $this->isModified;
	}

	protected function getPrimaryValue()
	{
		if (!$this->primaryValue && $this->value && $this->value->isPersisted()) {
			$this->primaryValue = $this->value->getValue('id');
		}

		return $this->primaryValue;
	}

	protected function getTargetRepository()
	{
		if (!$this->targetRepository) {
			$this->targetRepository = $this->parent->getRepository()->getModel()->getRepository($this->metadata->relationship->repository);
		}

		return $this->targetRepository;
	}

	/**
	 * @param  string   $collectionName
	 * @return ICollection
	 */
	protected function getCachedCollection($collectionName)
	{
		$key = $this->metadata->name . '_' . $collectionName;
		$cache = $this->parent->getRepository()->getMapper()->getCollectionCache();
		if (isset($cache->$key)) {
			return $cache->$key;
		}

		if ($collectionName !== null) {
			$filterMethod = 'filter' . $collectionName;
			$cache->$key = call_user_func([$this->parent, $filterMethod], $this->createCollection());
		} else {
			$cache->$key = $this->createCollection();
		}

		return $cache->$key;
	}

	protected function createEntity($value, $allowNull)
	{
		if ($value instanceof IEntity) {
			if ($model = $this->parent->getModel(false)) {
				$repo = $model->getRepository($this->metadata->relationship->repository);
				$repo->attach($value);

			} elseif ($model = $value->getModel(false)) {
				$repository = $model->getRepositoryForEntity($this->parent);
				$repository->attach($this->parent);
			}

		} elseif ($value === null) {
			if (!$this->metadata->isNullable && !$allowNull) {
				throw new NullValueException($this->parent, $this->metadata);
			}

		} elseif (is_scalar($value)) {
			$value = $this->getTargetRepository()->getById($value);

		} else {
			throw new InvalidArgumentException('Value is not a valid entity representation.');
		}

		return $value;
	}

	protected function isChanged($newValue)
	{
		// newValue is IEntity or null

		if ($this->value instanceof IEntity && $newValue instanceof IEntity) {
			return $this->value !== $newValue;

		} elseif ($this->value instanceof IEntity) {
			// value is some entity
			// newValue is null
			return true;

		} elseif ($newValue instanceof IEntity && $newValue->isPersisted()) {
			// value is persited entity or null
			// newValue is persisted entity
			return (string) $this->getPrimaryValue() !== (string) $newValue->getValue('id');

		} else {
			// value is persisted entity or null
			// newValue is null
			return $this->getPrimaryValue() !== $newValue;
		}
	}

	/**
	 * Creates relationship collection.
	 * @return ICollection
	 */
	abstract protected function createCollection();

	/**
	 * Sets relationship (and entity) as modified.
	 * @return void
	 */
	abstract protected function modify();

	/**
	 * Updates relationship on the other side.
	 * @param  IEntity|null $oldEntity
	 * @param  IEntity|null $newEntity
	 * @param  bool $allowNull
	 * @return void
	 */
	abstract protected function updateRelationship($oldEntity, $newEntity, $allowNull);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Countable;
use IteratorAggregate;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\IProperty;

interface IRelationshipCollection extends IProperty, IteratorAggregate, Countable
{
	/**
	 * @ignore
	 * @internal
	 * @param IEntity   $parent
	 */
	public function setParent(IEntity $parent);

	/**
	 * Adds entity.
	 * @param  IEntity|scalar   $entity
	 * @return IEntity
	 */
	public function add($entity);

	/**
	 * Replaces all entities with given ones.
	 * @param  IEntity[]|scalar[]   $data
	 * @return IRelationshipCollection
	 */
	public function set(array $data);

	/**
	 * Removes entity.
	 * @param  IEntity|scalar   $entity
	 * @return IEntity
	 */
	public function remove($entity);

	/**
	 * @param  IEntity|scalar   $entity
	 * @return bool
	 */
	public function has($entity);

	/**
	 * Returns collection of all entity.
	 * @return ICollection
	 */
	public function get();

	/**
	 * Returns true if colletion was loaded.
	 * @return bool
	 */
	public function isLoaded();

	/**
	 * Returns true if relationship is modified.
	 * @return bool
	 */
	public function isModified();

	/**
	 * Counts collection entities without fetching them from storage.
	 * @return int
	 */
	public function countStored();

	/**
	 * Returns IEntity or IRelationshipContainer for persistence.
	 * @internal
	 * @ignore
	 * @return mixed[]
	 */
	public function getEntitiesForPersistence();

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * @internal
	 * @ignore
	 */
	public function doPersist();
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\IPropertyContainer;

interface IRelationshipContainer extends IPropertyContainer
{
	/**
	 * @ignore
	 * @internal
	 * @param  IEntity  $parent
	 */
	public function setParent(IEntity $parent);

	/**
	 * @return IEntity
	 */
	public function getEntity();

	/**
	 * Returns true if container was loaded.
	 * @return bool
	 */
	public function isLoaded();

	/**
	 * Returns true if relationship is modified.
	 * @return bool
	 */
	public function isModified();
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Nextras\Orm\Entity\IEntity;

class ManyHasMany extends HasMany
{
	public function getEntitiesForPersistence()
	{
		$entities = [];
		foreach ($this->toAdd as $entity) {
			$entities[] = $entity;
		}
		if ($this->collection) {
			foreach ($this->collection as $entity) {
				$entities[] = $entity;
			}
		}
		return $entities;
	}

	public function doPersist()
	{
		$toRemove = [];
		foreach ($this->toRemove as $entity) {
			$id = $entity->getValue('id');
			$toRemove[$id] = $id;
		}
		$toAdd = [];
		foreach ($this->toAdd as $entity) {
			$id = $entity->getValue('id');
			$toAdd[$id] = $id;
		}

		$this->toAdd = [];
		$this->toRemove = [];
		$this->isModified = false;
		$this->collection = null;

		if ($this->metadata->relationship->isMain) {
			$this->getRelationshipMapper()->remove($this->parent, $toRemove);
			$this->getRelationshipMapper()->add($this->parent, $toAdd);
		}
	}

	protected function modify()
	{
		$this->isModified = true;
	}

	protected function createCollection()
	{
		if ($this->metadata->relationship->isMain) {
			$mapperOne = $this->parent->getRepository()->getMapper();
			$mapperTwo = $this->getTargetRepository()->getMapper();
		} else {
			$mapperOne = $this->getTargetRepository()->getMapper();
			$mapperTwo = $this->parent->getRepository()->getMapper();
		}

		$collection = $mapperOne->createCollectionManyHasMany($mapperTwo, $this->metadata, $this->parent);
		return $this->applyDefaultOrder($collection);
	}

	protected function updateRelationshipAdd(IEntity $entity)
	{
		if (!$this->metadata->relationship->property) {
			return;
		}

		$otherSide = $entity->getProperty($this->metadata->relationship->property);
		$otherSide->collection = null;
		$otherSide->toAdd[spl_object_hash($this->parent)] = $this->parent;
	}

	protected function updateRelationshipRemove(IEntity $entity)
	{
		if (!$this->metadata->relationship->property) {
			return;
		}

		$otherSide = $entity->getProperty($this->metadata->relationship->property);
		$otherSide->collection = null;
		$otherSide->toRemove[spl_object_hash($this->parent)] = $this->parent;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

class ManyHasOne extends HasOne
{
	protected function createCollection()
	{
		return $this->getTargetRepository()->getMapper()->createCollectionManyHasOne($this->metadata, $this->parent);
	}

	protected function modify()
	{
		$this->isModified = true;
		$this->parent->setAsModified($this->metadata->name);
	}

	protected function updateRelationship($oldEntity, $newEntity, $allowNull)
	{
		$key = $this->metadata->relationship->property;
		if (!$key) {
			return;
		}

		$this->updatingReverseRelationship = true;
		if ($oldEntity) {
			$oldEntity->getValue($key)->remove($this->parent);
		}

		if ($newEntity) {
			$newEntity->getValue($key)->add($this->parent);
		}
		$this->updatingReverseRelationship = false;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

use Nextras\Orm\Entity\IEntity;

class OneHasMany extends HasMany
{
	public function getEntitiesForPersistence()
	{
		$entities = [];
		foreach ($this->toAdd as $add) {
			$entities[] = $add;
		}
		foreach ($this->toRemove as $remove) {
			if ($remove->isPersisted()) {
				$entities[] = $remove;
			}
		}
		if ($this->collection !== null) {
			foreach ($this->getIterator() as $entity) {
				$entities[] = $entity;
			}
		}
		return $entities;
	}

	public function doPersist()
	{
		$this->toAdd = [];
		$this->toRemove = [];
		$this->collection = null;
		$this->isModified = false;
	}

	protected function modify()
	{
		$this->isModified = true;
	}

	protected function createCollection()
	{
		$collection = $this->getTargetRepository()->getMapper()->createCollectionOneHasMany($this->metadata, $this->parent);
		return $this->applyDefaultOrder($collection);
	}

	protected function updateRelationshipAdd(IEntity $entity)
	{
		if (!$this->metadata->relationship->property) {
			return;
		}

		$this->updatingReverseRelationship = true;
		$entity->getProperty($this->metadata->relationship->property)->setInjectedValue($this->parent);
		$this->updatingReverseRelationship = false;
	}

	protected function updateRelationshipRemove(IEntity $entity)
	{
		if (!$this->metadata->relationship->property) {
			return;
		}

		$this->updatingReverseRelationship = true;
		$entity->getProperty($this->metadata->relationship->property)->setInjectedValue(null);
		$this->updatingReverseRelationship = false;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Relationships;

class OneHasOne extends HasOne
{
	protected function createCollection()
	{
		return $this->getTargetRepository()->getMapper()->createCollectionOneHasOne($this->metadata, $this->parent);
	}

	public function getRawValue()
	{
		if ($this->primaryValue === null && $this->value === false && !$this->metadata->relationship->isMain) {
			$this->getEntity(); // init the value
		}
		return parent::getRawValue();
	}

	protected function modify()
	{
		$this->isModified = true;
		if ($this->metadata->relationship->isMain) {
			$this->parent->setAsModified($this->metadata->name);
		}
	}

	protected function updateRelationship($oldEntity, $newEntity, $allowNull)
	{
		$key = $this->metadata->relationship->property;
		if (!$key) {
			return;
		}

		$this->updatingReverseRelationship = true;
		if ($oldEntity) {
			$oldEntity->getProperty($key)->set(null, $allowNull);
		}
		if ($newEntity) {
			$newEntity->getProperty($key)->set($this->parent, $allowNull);
		}
		$this->updatingReverseRelationship = false;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Repository;

use Nextras\Orm\Entity\IEntity;

interface IDependencyProvider
{
	/**
	 * Injects required dependencies into the entity.
	 * @param  IEntity $entity
	 */
	public function injectDependencies(IEntity $entity);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * This file was inspired by PetrP's ORM library https://github.com/PetrP/Orm/.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Repository;

use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Model\IModel;

interface IRepository
{
	/**
	 * @param  bool $need
	 * @return IModel
	 */
	public function getModel($need = true);

	/**
	 * @param  IModel   $model
	 */
	public function setModel(IModel $model);

	/**
	 * @return IMapper
	 */
	public function getMapper();

	/**
	 * Hydrates entity.
	 * @param  array    $data
	 * @return IEntity
	 */
	public function hydrateEntity(array $data);

	/**
	 * Attaches entity to repository.
	 * @param  IEntity  $entity
	 */
	public function attach(IEntity $entity);

	/**
	 * Detaches entity from repository.
	 * @param  IEntity  $entity
	 */
	public function detach(IEntity $entity);

	/**
	 * Returns possible entity class names for current repository.
	 * @return string[]
	 */
	public static function getEntityClassNames();

	/**
	 * Returns entity metadata.
	 * @return EntityMetadata
	 */
	public function getEntityMetadata();

	/**
	 * Returns entity class name.
	 * @param  array    $data
	 * @return string
	 */
	public function getEntityClassName(array $data);

	/**
	 * Returns IEntity filtered by conditions
	 * @param  array $where
	 * @return IEntity|null
	 */
	public function getBy(array $conds);

	/**
	 * Returns entity by primary value.
	 * @param  mixed    $primaryValue
	 * @return IEntity
	 */
	public function getById($primaryValue);

	/**
	 * Returns entity collection with all entities.
	 * @return ICollection
	 */
	public function findAll();

	/**
	 * Returns entity collection filtered by conditions.
	 * @param  array $where
	 * @return ICollection
	 */
	public function findBy(array $where);

	/**
	 * Returns entities by primary values.
	 * @param  mixed[]  $primaryValues
	 * @return ICollection
	 */
	public function findById($primaryValues);

	/**
	 * @param  IEntity  $entity
	 * @param  bool     $withCascade
	 * @return mixed
	 */
	public function persist(IEntity $entity, $withCascade = true);

	/**
	 * @param  IEntity  $entity
	 * @param  bool     $withCascade
	 * @return mixed
	 */
	public function persistAndFlush(IEntity $entity, $withCascade = true);

	/**
	 * @param  IEntity|mixed    $entity
	 * @param  bool             $withCascade
	 * @return IEntity
	 */
	public function remove($entity, $withCascade = true);

	/**
	 * @param  IEntity|mixed    $entity
	 * @param  bool             $withCascade
	 * @return IEntity
	 */
	public function removeAndFlush($entity, $withCascade = true);

	/**
	 * Flushes all persisted changes in all repositories.
	 */
	public function flush();

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * @internal
	 * @ignore
	 */
	public function doPersist(IEntity $entity);

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * @internal
	 * @ignore
	 */
	public function doRemove(IEntity $entity);

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * @internal
	 * @ignore
	 * The first key contains all flushed persisted entities.
	 * The second key contains all flushed removed entities.
	 * @return [IEntity[], IEntity[]]
	 */
	public function doFlush();

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * @internal
	 * @ignore
	 */
	public function doClearIdentityMap($areYouSure);

	/**
	 * DO NOT CALL THIS METHOD DIRECTLY.
	 * Fires the event on the entity.
	 * @internal
	 * @ignore
	 * @param  IEntity $entity
	 * @param  string $event
	 * @return void
	 */
	public function doFireEvent(IEntity $entity, $event);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * This file was inspired by PetrP's ORM library https://github.com/PetrP/Orm/.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Repository;

use Nette\Object;
use Nette\Reflection\ClassType;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\StorageReflection\IStorageReflection;

class IdentityMap extends Object
{
	/** @var IRepository */
	private $repository;

	/** @var array of IEntity|bool */
	private $entities = [];

	/** @var IStorageReflection cached instance */
	private $storageReflection;

	/** @var mixed cached primary key */
	private $storagePrimaryKey;

	/** @var ClassType[] */
	private $entityReflections;

	/** @var IDependencyProvider */
	private $dependencyProvider;

	public function __construct(IRepository $repository, IDependencyProvider $dependencyProvider = null)
	{
		$this->repository = $repository;
		$this->dependencyProvider = $dependencyProvider;
	}

	public function hasById($id)
	{
		return isset($this->entities[implode(',', (array) $id)]);
	}

	public function getById($id)
	{
		$id = implode(',', (array) $id);
		if (!isset($this->entities[$id])) {
			return null;
		}

		return $this->entities[$id];
	}

	public function add(IEntity $entity)
	{
		$this->entities[implode(',', (array) $entity->getPersistedId())] = $entity;
	}

	public function remove($id)
	{
		$this->entities[implode(',', (array) $id)] = false;
	}

	public function create($data)
	{
		if ($this->storagePrimaryKey === null) {
			$this->storageReflection = $this->repository->getMapper()->getStorageReflection();
			$this->storagePrimaryKey = (array) $this->storageReflection->getStoragePrimaryKey();
		}

		$entity = $this->createEntity($data);
		$id = implode(',', (array) $entity->getPersistedId());

		if (isset($this->entities[$id])) {
			$this->repository->detach($entity);
			return $this->entities[$id] ?: null;
		}

		return $this->entities[$id] = $entity; // = intentionally
	}

	/**
	 * @return IEntity[]
	 */
	public function getAll()
	{
		return $this->entities;
	}

	public function check(IEntity $entity)
	{
		if (!in_array(get_class($entity), $this->repository->getEntityClassNames(), true)) {
			throw new InvalidArgumentException("Entity '" . get_class($entity) . "' is not accepted by '" . get_class($this->repository) . "' repository.");
		}
	}

	public function destroyAllEntities()
	{
		foreach ($this->entities as $entity) {
			if ($entity) {
				$this->repository->detach($entity);
				$entity->fireEvent('onFree');
			}
		}

		$this->entities = [];
	}

	/**
	 * @param  array
	 * @return IEntity
	 */
	protected function createEntity(array $data)
	{
		$data = $this->storageReflection->convertStorageToEntity($data);
		$entityClass = $this->repository->getEntityClassName($data);

		if (!isset($this->entityReflections[$entityClass])) {
			$this->entityReflections[$entityClass] = ClassType::from($entityClass);
		}

		$entity = $this->entityReflections[$entityClass]->newInstanceWithoutConstructor();
		$this->repository->attach($entity);
		$entity->fireEvent('onLoad', [$data]);
		return $entity;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * This file was inspired by PetrP's ORM library https://github.com/PetrP/Orm/.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Repository;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Entity\Reflection\PropertyRelationshipMetadata as Relationship;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Relationships\IRelationshipCollection;

class PersistenceHelper
{
	/** @var array */
	protected static $inputQueue;

	/** @var array */
	protected static $outputQueue;

	/**
	 * @param  IEntity $entity
	 * @param  IModel $model
	 * @param  bool $withCascade
	 * @return array
	 *
	 * @see https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
	 */
	public static function getCascadeQueue(IEntity $entity, IModel $model, $withCascade)
	{
		self::$inputQueue = [];
		self::$outputQueue = [];
		self::visitEntity($entity, $model, $withCascade);

		for ($i = 0; $i < count(self::$inputQueue); $i++) {
			$value = self::$inputQueue[$i];

			if ($value instanceof IEntity) {
				self::visitEntity($value, $model);
			} else {
				self::visitRelationship($value, $model);
			}
		}

		return self::$outputQueue;
	}

	protected static function visitEntity(IEntity $entity, IModel $model, $withCascade = true)
	{
		$entityHash = spl_object_hash($entity);
		if (isset(self::$outputQueue[$entityHash])) {
			if (self::$outputQueue[$entityHash] === true) {
				$cycle = [];
				$bt = debug_backtrace();
				foreach ($bt as $item) {
					if ($item['function'] === 'getCascadeQueue') {
						break;

					} elseif ($item['function'] === 'addRelationshipToQueue') {
						$cycle[] = get_class($item['args'][0]) . '::$' . $item['args'][1]->name;
					}
				}

				throw new InvalidStateException('Persist cycle detected in ' . implode(' - ', $cycle) . '. Use manual two phase persist.');
			}
			return;
		}

		$repository = $model->getRepositoryForEntity($entity);
		$repository->attach($entity);
		$repository->doFireEvent($entity, 'onBeforePersist');

		if ($withCascade) {
			self::$outputQueue[$entityHash] = true;
			foreach ($entity->getMetadata()->getProperties() as $propertyMeta) {
				if ($propertyMeta->relationship !== null && $propertyMeta->relationship->cascade['persist']) {
					self::addRelationshipToQueue($entity, $propertyMeta, $model);
				}
			}
			unset(self::$outputQueue[$entityHash]); // reenqueue
		}

		self::$outputQueue[$entityHash] = $entity;
	}

	protected static function visitRelationship(IRelationshipCollection $rel, IModel $model)
	{
		foreach ($rel->getEntitiesForPersistence() as $entity) {
			self::visitEntity($entity, $model);
		}

		self::$outputQueue[spl_object_hash($rel)] = $rel;
	}

	protected static function addRelationshipToQueue(IEntity $entity, PropertyMetadata $propertyMeta, IModel $model)
	{
		$isPersisted = $entity->isPersisted();
		$rawValue = $entity->getRawProperty($propertyMeta->name);
		if ($rawValue === null && ($propertyMeta->isNullable || $isPersisted)) {
			return;
		} elseif (!$entity->getProperty($propertyMeta->name)->isLoaded() && $isPersisted) {
			return;
		}

		$value = $entity->getValue($propertyMeta->name);
		if ($value instanceof IEntity && !$value->isPersisted()) {
			if ($propertyMeta->relationship->type !== Relationship::ONE_HAS_ONE || $propertyMeta->relationship->isMain) {
				self::visitEntity($value, $model);
			}

		} elseif ($value !== null) {
			self::$inputQueue[] = $value;
		}
	}
}


code:
<?php

namespace Nextras\Orm\Repository;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Entity\Reflection\PropertyRelationshipMetadata as Relationship;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Relationships\IRelationshipCollection;
use Nextras\Orm\Relationships\IRelationshipContainer;

class RemovalHelper
{
	public static function getCascadeQueueAndSetNulls(IEntity $entity, IModel $model, $withCascade, & $queuePersist, & $queueRemove)
	{
		$entityHash = spl_object_hash($entity);
		if (isset($queueRemove[$entityHash])) {
			return;
		}

		$repository = $model->getRepositoryForEntity($entity);
		$repository->attach($entity);
		$repository->doFireEvent($entity, 'onBeforeRemove');

		list ($pre, $post, $nulls) = static::getRelationships($entity);
		$prePersist = [];
		static::setNulls($entity, $nulls, $model, $prePersist);

		if (!$withCascade) {
			$queueRemove[$entityHash] = $entity;
			return;
		}

		foreach ($prePersist as $value) {
			$queuePersist[spl_object_hash($value)] = $value;
		}
		foreach ($pre as $value) {
			if ($value instanceof IEntity) {
				static::getCascadeQueueAndSetNulls($value, $model, true, $queuePersist, $queueRemove);
			} elseif ($value instanceof IRelationshipCollection) {
				foreach ($value->getIterator() as $subValue) {
					static::getCascadeQueueAndSetNulls($subValue, $model, true, $queuePersist, $queueRemove);
				}
				$queuePersist[spl_object_hash($value)] = $value;
			}
		}
		$queueRemove[$entityHash] = $entity;
		unset($queuePersist[$entityHash]);
		foreach ($post as $value) {
			if ($value instanceof IEntity) {
				static::getCascadeQueueAndSetNulls($value, $model, true, $queuePersist, $queueRemove);
			} elseif ($value instanceof IRelationshipCollection) {
				foreach ($value->getIterator() as $subValue) {
					static::getCascadeQueueAndSetNulls($subValue, $model, true, $queuePersist, $queueRemove);
				}
				$queuePersist[spl_object_hash($value)] = $value;
			}
		}
	}

	/**
	 * Returns entity relationships as array, 0 => pre, 1 => post, 2 => nulls
	 * @param  IEntity  $entity
	 * @return array
	 */
	public static function getRelationships(IEntity $entity)
	{
		$return = [[], [], []];
		foreach ($entity->getMetadata()->getProperties() as $propertyMeta) {
			if ($propertyMeta->relationship === null) {
				continue;
			}

			$name = $propertyMeta->name;
			if (!$propertyMeta->relationship->cascade['remove']) {
				$return[2][$name] = $propertyMeta;
				continue;
			}

			$rawValue = $entity->getRawValue($name);
			if ($rawValue === null && $propertyMeta->isNullable) {
				continue;
			}

			$property = $entity->getProperty($name);
			if ($property instanceof IRelationshipContainer) {
				$value = $entity->getValue($name);
				if ($value) {
					if ($propertyMeta->relationship->type === Relationship::ONE_HAS_ONE && !$propertyMeta->relationship->isMain) {
						$return[0][$name] = $value;
					} else {
						$return[1][$name] = $value;
					}
				}

			} elseif ($property instanceof IRelationshipCollection) {
				$return[0][$name] = $entity->getValue($name);
			}
		}

		return $return;
	}

	/**
	 * @param  IEntity $entity
	 * @param  PropertyMetadata[] $metadata
	 * @parma  IModel $model
	 * @param  array $pre
	 */
	private static function setNulls($entity, array $metadata, IModel $model, array & $pre)
	{
		foreach ($metadata as $propertyMeta) {
			$type = $propertyMeta->relationship->type;
			$name = $propertyMeta->name;
			if ($type === Relationship::MANY_HAS_MANY) {
				$entity->setValue($name, []);

			} elseif ($type === Relationship::MANY_HAS_ONE || ($type === Relationship::ONE_HAS_ONE && $propertyMeta->relationship->isMain)) {
				$entity->getProperty($name)->set(null, true);

			} else {
				// $type === Relationship::ONE_HAS_MANY or
				// $type === Relationship::ONE_HAS_ONE && !$isMain
				if (!$entity->hasValue($name)) {
					continue;
				}

				$reverseRepository = $model->getRepository($propertyMeta->relationship->repository);
				$reverseProperty = $reverseRepository->getEntityMetadata()->getProperty($propertyMeta->relationship->property);

				if ($reverseProperty->isNullable) {
					if ($type === Relationship::ONE_HAS_MANY) {
						foreach ($entity->getValue($name) as $subValue) {
							$pre[] = $subValue;
						}
						$entity->getValue($name)->set([]);
					} else {
						$pre[] = $entity->getValue($name);
						$entity->getProperty($name)->set(null, true);
					}

				} else {
					if ($type === Relationship::ONE_HAS_MANY && $entity->getValue($name)->count() === 0) {
						continue;
					}

					$entityClass = get_class($entity);
					$reverseEntityClass = $propertyMeta->relationship->entity;
					$primaryValue = $entity->getValue('id');
					$primaryValue = is_array($primaryValue) ? '[' . implode(', ', $primaryValue) . ']' : $primaryValue;
					throw new InvalidStateException(
						"Cannot remove {$entityClass}::\$id={$primaryValue} because {$reverseEntityClass}::\${$reverseProperty->name} cannot be a null."
					);
				}
			}
		}
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * This file was inspired by PetrP's ORM library https://github.com/PetrP/Orm/.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\Repository;

use Nette\Object;
use Nette\Utils\ObjectMixin;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\LogicException;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Model\MetadataStorage;

abstract class Repository extends Object implements IRepository
{
	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onBeforePersist = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onAfterPersist = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onBeforeInsert = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onAfterInsert = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onBeforeUpdate = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onAfterUpdate = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onBeforeRemove = [];

	/** @var array of callbacks with (IEntity $entity) arguments */
	public $onAfterRemove = [];

	/** @var array of callbacks with (IEntity[] $persisted, IEntity[] $removed) arguments */
	public $onFlush = [];

	/** @var IMapper */
	protected $mapper;

	/** @var string */
	protected $entityClassName;

	/** @var IModel */
	private $model;

	/** @var IdentityMap */
	private $identityMap;

	/** @var array */
	private $proxyMethods;

	/** @var MetadataStorage */
	private $metadataStorage;

	/** @var array */
	private $entitiesToFlush = [[], []];

	/** @var IDependencyProvider */
	private $dependencyProvider;

	/**
	 * @param  IMapper              $mapper
	 * @param  IDependencyProvider  $dependencyProvider
	 */
	public function __construct(IMapper $mapper, IDependencyProvider $dependencyProvider = null)
	{
		$this->mapper = $mapper;
		$this->mapper->setRepository($this);
		$this->identityMap = new IdentityMap($this, $dependencyProvider);
		$this->dependencyProvider = $dependencyProvider;

		$annotations = $this->reflection->getAnnotations();
		if (isset($annotations['method'])) {
			foreach ((array) $annotations['method'] as $annotation) {
				$this->proxyMethods[strtolower(preg_replace('#^[^\s]+\s+(\w+)\(.*\).*$#', '$1', $annotation))] = true;
			}
		}
	}

	/** @inheritdoc */
	public function getModel($need = true)
	{
		if ($this->model === null && $need) {
			throw new InvalidStateException('Repository is not attached to model.');
		}

		return $this->model;
	}

	/** @inheritdoc */
	public function setModel(IModel $model)
	{
		if ($this->model && $this->model !== $model) {
			throw new InvalidStateException('Repository is already attached.');
		}

		$this->model = $model;
		$this->metadataStorage = $model->getMetadataStorage();
	}

	/** @inheritdoc */
	public function getMapper()
	{
		if (!$this->mapper) {
			throw new InvalidStateException('Repository does not have injected any mapper.');
		}

		return $this->mapper;
	}

	/** @inheritdoc */
	public function getBy(array $conds)
	{
		return call_user_func_array([$this->findAll(), 'getBy'], func_get_args());
	}

	/** @inheritdoc */
	public function getById($id)
	{
		if ($id === null) {
			return null;
		} elseif ($id instanceof IEntity) {
			$id = $id->getValue('id');
		} elseif (!(is_scalar($id) || is_array($id))) {
			throw new InvalidArgumentException('Primary key value has to be a scalar.');
		}

		$entity = $this->identityMap->getById($id);
		if ($entity === false) { // entity was removed
			return null;
		} elseif ($entity instanceof IEntity) {
			return $entity;
		}

		$entity = $this->findAll()->getBy(['id' => $id]);
		if ($entity === null) {
			$this->identityMap->remove($id);
		}

		return $entity;
	}

	/** @inheritdoc */
	public function findAll()
	{
		return $this->getMapper()->findAll();
	}

	/** @inheritdoc */
	public function findBy(array $conds)
	{
		return call_user_func_array([$this->findAll(), 'findBy'], func_get_args());
	}

	/** @inheritdoc */
	public function findById($ids)
	{
		return call_user_func_array([$this->findAll(), 'findBy'], [['id' => $ids]]);
	}

	/** @inheritdoc */
	public function attach(IEntity $entity)
	{
		if (!$entity->isAttached()) {
			$entity->fireEvent('onAttach', [$this, $this->metadataStorage->get(get_class($entity))]);
			if ($this->dependencyProvider) {
				$this->dependencyProvider->injectDependencies($entity);
			}
		}
	}

	/** @inheritdoc */
	public function detach(IEntity $entity)
	{
		if ($entity->isAttached()) {
			$entity->fireEvent('onDetach');
		}
	}

	/** @inheritdoc */
	public function hydrateEntity(array $data)
	{
		return $this->identityMap->create($data);
	}

	/** @inheritdoc */
	public function getEntityMetadata()
	{
		return $this->metadataStorage->get(static::getEntityClassNames()[0]);
	}

	/** @inheritdoc */
	public function getEntityClassName(array $data)
	{
		if (!$this->entityClassName) {
			$this->entityClassName = static::getEntityClassNames()[0];
		}

		return $this->entityClassName;
	}

	/** @inheritdoc */
	public function persist(IEntity $entity, $withCascade = true)
	{
		$this->identityMap->check($entity);
		return $this->model->persist($entity, $withCascade);
	}

	/** @inheritdoc */
	public function doPersist(IEntity $entity)
	{
		if (!$entity->isModified()) {
			return;
		}

		$isPersisted = $entity->isPersisted();
		$this->doFireEvent($entity, $isPersisted ? 'onBeforeUpdate' : 'onBeforeInsert');

		$isPersisted && $this->identityMap->remove($entity->getPersistedId()); // id can change in composite key
		$id = $this->mapper->persist($entity);
		$entity->fireEvent('onPersist', [$id]);
		$this->identityMap->add($entity);
		$this->entitiesToFlush[0][] = $entity;

		$this->doFireEvent($entity, $isPersisted ? 'onAfterUpdate' : 'onAfterInsert');
		$this->doFireEvent($entity, 'onAfterPersist');
	}

	/** @inheritdoc */
	public function remove($entity, $withCascade = true)
	{
		$entity = $entity instanceof IEntity ? $entity : $this->getById($entity);
		$this->identityMap->check($entity);
		return $this->model->remove($entity, $withCascade);
	}

	/** @inheritdoc */
	public function doRemove(IEntity $entity)
	{
		$this->detach($entity);
		if (!$entity->isPersisted()) {
			return;
		}

		$this->mapper->remove($entity);
		$this->identityMap->remove($entity->getPersistedId());
		$this->entitiesToFlush[1][] = $entity;
		$this->doFireEvent($entity, 'onAfterRemove');
	}

	/** @inheritdoc */
	public function flush()
	{
		$this->getModel()->flush();
	}

	/** @inheritdoc */
	public function persistAndFlush(IEntity $entity, $withCascade = true)
	{
		$this->persist($entity, $withCascade);
		$this->flush();
		return $entity;
	}

	/** @inheritdoc */
	public function removeAndFlush($entity, $withCascade = true)
	{
		$this->remove($entity, $withCascade);
		$this->flush();
		return $entity;
	}

	/** @inheritdoc */
	public function doFlush()
	{
		$this->mapper->flush();
		$this->onFlush($this->entitiesToFlush[0], $this->entitiesToFlush[1]);
		$entities = $this->entitiesToFlush;
		$this->entitiesToFlush = [[], []];
		return $entities;
	}

	/** @inheritdoc */
	public function doClearIdentityMap($areYouSure = null)
	{
		if ($areYouSure !== IModel::I_KNOW_WHAT_I_AM_DOING) {
			throw new LogicException('Do not call this method directly. Use IModel::clearIdentityMapAndCaches().');
		}

		$this->identityMap->destroyAllEntities();
		$this->mapper->clearCollectionCache();
	}

	/** @inheritdoc */
	public function doFireEvent(IEntity $entity, $event)
	{
		if (!property_exists($this, $event)) {
			throw new InvalidArgumentException("Event '{$event}' is not defined.");
		}

		$entity->fireEvent($event);
		ObjectMixin::call($this, $event, [$entity]);
	}

	public function __call($method, $args)
	{
		if (isset($this->proxyMethods[strtolower($method)])) {
			$result = call_user_func_array([$this->mapper, $method], $args);
			if (!($result instanceof ICollection || $result instanceof IEntity || $result === null)) {
				$result = $this->mapper->toCollection($result);
			}
			return $result;

		} else {
			return parent::__call($method, $args);
		}
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\StorageReflection;

use Nette\Object;
use Nextras\Orm\Mapper\IMapper;

class CommonReflection extends Object implements IStorageReflection
{
	/** @var IMapper */
	private $mapper;

	/** @var string */
	private $storageName;

	/** @var */
	private $primaryKeys;

	public function __construct(IMapper $mapper, $storageName, $primaryKeys)
	{
		$this->mapper = $mapper;
		$this->storageName = $storageName;
		$this->primaryKeys = $primaryKeys;
	}

	public function getStorageName()
	{
		return $this->storageName;
	}

	public function getStoragePrimaryKey()
	{
		return $this->primaryKeys;
	}

	public function convertEntityToStorage($data)
	{
		$data = (array) $data;
		return $data;
	}

	public function convertStorageToEntity($data)
	{
		$data = (array) $data;
		return $data;
	}

	public function convertEntityToStorageKey($key)
	{
		return $key;
	}

	public function convertStorageToEntityKey($key)
	{
		return $key;
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\StorageReflection;

use Traversable;

interface IStorageReflection
{
	/**
	 * Returns storage name.
	 * @return string
	 */
	public function getStorageName();

	/**
	 * Returns storage primary key name.
	 * @return array
	 */
	public function getStoragePrimaryKey();

	/**
	 * Converts entity data to storage key format.
	 *
	 * @param  array|Traversable   $in
	 * @return array
	 */
	public function convertEntityToStorage($in);

	/**
	 * Converts entity key name to storage key format.
	 * @param  string   $key
	 * @return string
	 */
	public function convertEntityToStorageKey($key);

	/**
	 * Converts storage data to entity key format.
	 *
	 * @param  array|Traversable   $in
	 * @return array
	 */
	public function convertStorageToEntity($in);

	/**
	 * Converts storage key name to entity key format.
	 * @param  string   $key
	 * @return string
	 */
	public function convertStorageToEntityKey($key);
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\StorageReflection;

class StringHelper
{
	/**
	 * @param  string   $string
	 * @return string
	 */
	public static function camelize($string)
	{
		return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $string))));
	}

	/**
	 * @param  string
	 * @return string
	 */
	public static function underscore($string)
	{
		return strtolower(preg_replace('#(\w)([A-Z])#', '$1_$2', $string));
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\TestHelper;

use DateTime;
use DateTimeImmutable;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Entity\Reflection\PropertyRelationshipMetadata;
use Nextras\Orm\Model\IModel;

class EntityCreator
{
	/** @var IModel */
	private $model;

	public function __construct(IModel $model)
	{
		$this->model = $model;
	}

	public function create($entity, array $params = [])
	{
		$entity = new $entity;
		$repository = $this->model->getRepositoryForEntity($entity);
		$repository->attach($entity);

		$this->fill($entity, $params);
		return $entity;
	}

	protected function fill(IEntity $entity, array $params)
	{
		foreach ($entity->getMetadata()->getProperties() as $property) {
			if ($property->isReadonly) {
				continue;
			}

			$key = $property->name;

			if (array_key_exists($key, $params)) {
				$value = $params[$key];
				unset($params[$key]);
			} elseif ($property->isNullable || $property->isVirtual || $property->isPrimary || $entity->hasValue($key)) {
				continue;
			} else {
				$value = $this->random($property);
			}

			$entity->setReadOnlyValue($key, $value);
		}
	}

	protected function random(PropertyMetadata $property)
	{
		if ($property->relationship && in_array($property->relationship->type, [
				PropertyRelationshipMetadata::MANY_HAS_ONE,
				PropertyRelationshipMetadata::ONE_HAS_ONE,
		])) {
			return $this->create($property->relationship->entity);
		}

		$possibilities = [];
		if ($property->enum) {
			$possibilities = $property->enum;

		} else {
			foreach (array_keys($property->types) as $type) {
				$type = strtolower($type);
				switch ($type) {
					case 'datetime':
						$possibilities[] = new DateTime(
							$this->randomInt(2010, 2020) . '-' . $this->randomInt(1, 12) . '-' . $this->randomInt(1, 31)
						);
						break;
					case 'datetimeimmutable':
						$possibilities[] = new DateTimeImmutable(
							$this->randomInt(2010, 2020) . '-' . $this->randomInt(1, 12) . '-' . $this->randomInt(1, 31)
						);
						break;
					case 'string':
						$possibilities[] = $this->randomWords(20, 50);
						break;
					case 'int':
						$possibilities[] = $this->randomInt(0, 100);
						break;
					case 'float':
						$possibilities[] = $this->randomInt(0, 100) + $this->randomInt(0, 100) / 100;
						break;
					case 'bool':
						$possibilities[] = (bool) $this->randomInt(0, 1);
						break;
				}
			}
		}

		if (!$possibilities) {
			return null;
		}

		return $possibilities[array_rand($possibilities)];
	}

	protected function randomInt($min, $max)
	{
		return rand($min, $max);
	}

	protected function randomWords($min, $max)
	{
		static $lipsumDictionary = null;
		if (!$lipsumDictionary) {
			$sentence = '
				Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque auctor nunc vestibulum neque tristique
				hendrerit. Nulla lobortis viverra laoreet. Nullam dignissim velit tortor. Pellentesque habitant morbi
				tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas condimentum molestie nunc,
				non placerat sapien porta commodo. In hendrerit tellus ut ante congue mattis. Sed sed lacus vel lectus
				malesuada ornare. Duis eu mollis elit. Etiam dignissim sit amet lacus eget lobortis. Quisque facilisis
				tincidunt odio, sit amet pretium odio elementum et. Nam vestibulum metus nunc, tincidunt tempus nunc
				interdum eu. Nullam nisl nunc, dignissim id ligula eu, posuere interdum mauris.
			';
			$lipsumDictionary = preg_split('#,?\.?\s+#', $sentence);
		}

		$word = '';
		while (strlen($word) < ($max - 3)) {
			if (strlen($word) > $min && $this->randomInt(0, 1)) {
				break;
			}

			$word .= $lipsumDictionary[array_rand($lipsumDictionary)] . ' ';
		}

		return ucfirst(substr(trim($word), 0, $max));
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\TestHelper;

trait TestCaseEntityTrait
{
	protected function e($entityClass, array $parameters = [])
	{
		return $this->container->getByType(EntityCreator::class)->create($entityClass, $parameters);
	}
}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm\TestHelper;

use Nette\Utils\Callback;
use Nextras\Orm\Mapper\Memory\ArrayMapper;

class TestMapper extends ArrayMapper
{
	/** @var array */
	protected $storage = '';

	/** @var mixed[] array of callbacks */
	protected $methods = [];

	public function addMethod($name, $callback)
	{
		$this->methods[strtolower($name)] = $callback;
	}

	public function __call($name, $args)
	{
		if (isset($this->methods[strtolower($name)])) {
			return Callback::invokeArgs($this->methods[strtolower($name)], $args);
		} else {
			return parent::__call($name, $args);
		}
	}

	protected function readData()
	{
		return unserialize($this->storage);
	}

	protected function saveData(array $data)
	{
		$this->storage = serialize($data);
	}

}


code:
<?php

/**
 * This file is part of the Nextras\Orm library.
 * @license    MIT
 * @link       https://github.com/nextras/orm
 */

namespace Nextras\Orm;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;

class InvalidArgumentException extends \InvalidArgumentException
{
}

class InvalidModifierDefinitionException extends InvalidArgumentException
{
}

class NullValueException extends InvalidArgumentException
{
	public function __construct(IEntity $entity, PropertyMetadata $propertyMetadata)
	{
		$class = get_class($entity);
		parent::__construct("Property {$class}::\${$propertyMetadata->name} is not nullable.");
	}
}

class RuntimeException extends \RuntimeException
{
}

class InvalidStateException extends RuntimeException
{
}

class IOException extends RuntimeException
{
}

class LogicException extends \LogicException
{
}

class MemberAccessException extends LogicException
{
}

class NotImplementedException extends LogicException
{
}

class NotSupportedException extends LogicException
{
}


tracy/tracy

description: Tracy: useful PHP debugger

keywords: debug debugger nette

minimum-stability: dev


code:
<?php

/**
 * This file is part of the Tracy (https://tracy.nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Tracy\Bridges\Nette;

use Nette;

/**
 * Tracy extension for Nette DI.
 */
class TracyExtension extends Nette\DI\CompilerExtension
{
	public $defaults = array(
		'email' => NULL,
		'fromEmail' => NULL,
		'logSeverity' => NULL,
		'editor' => NULL,
		'browser' => NULL,
		'errorTemplate' => NULL,
		'strictMode' => NULL,
		'showBar' => NULL,
		'maxLen' => NULL,
		'maxDepth' => NULL,
		'showLocation' => NULL,
		'scream' => NULL,
		'bar' => array(), // of class name
		'blueScreen' => array(), // of callback
	);

	/** @var bool */
	private $debugMode;

	public function __construct($debugMode = FALSE)
	{
		$this->debugMode = $debugMode;
	}

	public function loadConfiguration()
	{
		$this->validateConfig($this->defaults);
		$container = $this->getContainerBuilder();

		$container->addDefinition($this->prefix('logger'))
			->setClass('Tracy\ILogger')
			->setFactory('Tracy\Debugger::getLogger');

		$container->addDefinition($this->prefix('blueScreen'))
			->setFactory('Tracy\Debugger::getBlueScreen');

		$container->addDefinition($this->prefix('bar'))
			->setFactory('Tracy\Debugger::getBar');
	}

	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$initialize = $class->getMethod('initialize');
		$container = $this->getContainerBuilder();

		$options = $this->config;
		unset($options['bar'], $options['blueScreen']);
		foreach ($options as $key => $value) {
			if ($value !== NULL) {
				$key = ($key === 'fromEmail' ? 'getLogger()->' : '$') . $key;
				$initialize->addBody($container->formatPhp(
					'Tracy\Debugger::' . $key . ' = ?;',
					Nette\DI\Compiler::filterArguments(array($value))
				));
			}
		}

		if ($this->debugMode) {
			foreach ((array) $this->config['bar'] as $item) {
				$initialize->addBody($container->formatPhp(
					'$this->getService(?)->addPanel(?);',
					Nette\DI\Compiler::filterArguments(array(
						$this->prefix('bar'),
						is_string($item) ? new Nette\DI\Statement($item) : $item,
					))
				));
			}
		}

		foreach ((array) $this->config['blueScreen'] as $item) {
			$initialize->addBody($container->formatPhp(
				'$this->getService(?)->addPanel(?);',
				Nette\DI\Compiler::filterArguments(array($this->prefix('blueScreen'), $item))
			));
		}
	}

}


code:
<?php

/**
 * This file is part of the Tracy (https://tracy.nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Tracy;

/**
 * Debug Bar.
 */
class Bar
{
	/** @deprecated */
	public $info = array();

	/** @var IBarPanel[] */
	private $panels = array();

	/**
	 * Add custom panel.
	 * @param  IBarPanel
	 * @param  string
	 * @return self
	 */
	public function addPanel(IBarPanel $panel, $id = NULL)
	{
		if ($id === NULL) {
			$c = 0;
			do {
				$id = get_class($panel) . ($c++ ? "-$c" : '');
			} while (isset($this->panels[$id]));
		}
		$this->panels[$id] = $panel;
		return $this;
	}

	/**
	 * Returns panel with given id
	 * @param  string
	 * @return IBarPanel|NULL
	 */
	public function getPanel($id)
	{
		return isset($this->panels[$id]) ? $this->panels[$id] : NULL;
	}

	/**
	 * Renders debug bar.
	 * @return void
	 */
	public function render()
	{
		@session_start(); // @ session may be already started or it is not possible to start session
		$session = & $_SESSION['__NF']['debuggerbar'];
		$redirect = preg_match('#^Location:#im', implode("\n", headers_list()));
		if ($redirect) {
			Dumper::fetchLiveData();
			Dumper::$livePrefix = count($session) . 'p';
		}

		$obLevel = ob_get_level();
		$panels = array();
		foreach ($this->panels as $id => $panel) {
			$idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id);
			try {
				$tab = (string) $panel->getTab();
				$panelHtml = $tab ? (string) $panel->getPanel() : NULL;
				if ($tab && $panel instanceof \Nette\Diagnostics\IBarPanel) {
					$panelHtml = preg_replace('~(["\'.\s#])nette-(debug|inner|collapsed|toggle|toggle-collapsed)(["\'\s])~', '$1tracy-$2$3', $panelHtml);
					$panelHtml = str_replace('tracy-toggle-collapsed', 'tracy-toggle tracy-collapsed', $panelHtml);
				}
				$panels[] = array('id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml);

			} catch (\Throwable $e) {
			} catch (\Exception $e) {
			}
			if (isset($e)) {
				$panels[] = array(
					'id' => "error-$idHtml",
					'tab' => "Error in $id",
					'panel' => '
Error: ' . $id . '

'
						. nl2br(htmlSpecialChars($e, ENT_IGNORE, 'UTF-8')) . '
',
				);
				while (ob_get_level() > $obLevel) { // restore ob-level if broken
					ob_end_clean();
				}
			}
		}

		if ($redirect) {
			$session[] = array('panels' => $panels, 'liveData' => Dumper::fetchLiveData());
			return;
		}

		$liveData = Dumper::fetchLiveData();

		foreach (array_reverse((array) $session) as $reqId => $info) {
			$panels[] = array(
				'tab' => 'previous',
				'panel' => NULL,
				'previous' => TRUE,
			);
			foreach ($info['panels'] as $panel) {
				$panel['id'] .= '-' . $reqId;
				$panels[] = $panel;
			}
			$liveData += $info['liveData'];
		}
		$session = NULL;

		require __DIR__ . '/assets/Bar/bar.phtml';
	}

}