/** * @param string $class * @param ORM $orm * @throws SourceException */ public function __construct($class = null, ORM $orm = null) { if (empty($class)) { if (empty(static::RECORD)) { throw new SourceException("Unable to create source without associate class."); } $class = static::RECORD; } $this->class = $class; $this->orm = $this->saturate($orm, ORM::class); $this->setSelector($this->orm->selector($this->class)); }
/** * @param Debugger $debugger * @param ORM $orm * @param ContainerInterface $container * @param ClassLocator $locator */ public function perform(Debugger $debugger, ORM $orm, ContainerInterface $container, ClassLocator $locator) { //We don't really need location errors here $locator->setLogger(new NullLogger()); $benchmark = $debugger->benchmark($this, 'update'); $builder = $orm->schemaBuilder($locator); //To make builder available for other commands (in sequence) $container->bind(get_class($builder), $builder); if (!$this->option('sync')) { $this->writeln("<comment>Silent mode on, no databases to be altered.</comment>"); } $orm->updateSchema($builder, $this->option('sync')); $elapsed = number_format($debugger->benchmark($this, $benchmark), 3); $countModels = count($builder->getRecords()); $this->write("<info>ORM Schema has been updated: <comment>{$elapsed} s</comment>"); $this->writeln(", found records: <comment>{$countModels}</comment></info>"); }
/** * {@inheritdoc} * * @return RecordInterface * @see ORM::record() * @see Record::setContext() * @throws ORMException */ public function current() { if (isset($this->instances[$this->position])) { //Due record was pre-constructed we must update it's context to force values for relations //and pivot fields return $this->instances[$this->position]; } //Let's ask ORM to create needed record return $this->instances[$this->position] = $this->orm->record($this->class, $this->data[$this->position], $this->cache); }
/** * Fetch one record from database. Attention, LIMIT statement will be used, meaning you can not * use loaders for HAS_MANY or MANY_TO_MANY relations with data inload (joins), use default * loading method. * * @see findByPK() * @param array $where Selection WHERE statement. * @param bool $setLimit Use limit 1. * @return RecordEntity|null */ public function findOne(array $where = [], $setLimit = true) { if (!empty($where)) { $this->where($where); } $data = $this->limit($setLimit ? 1 : null)->fetchData(); if (empty($data)) { return null; } return $this->orm->record($this->class, $data[0]); }
/** * Create instance of inspector associated with ORM and ODM entities. * * @param InspectionsConfig $config * @param ODM $odm * @param ORM $orm * @return Inspector */ protected function createInspector(InspectionsConfig $config, ODM $odm, ORM $orm) { if ($this->container->has(\Spiral\ODM\Entities\SchemaBuilder::class)) { $odmBuilder = $this->container->get(\Spiral\ODM\Entities\SchemaBuilder::class); } else { $odmBuilder = $odm->schemaBuilder(); } if ($this->container->has(\Spiral\ORM\Entities\SchemaBuilder::class)) { $ormBuilder = $this->container->get(\Spiral\ORM\Entities\SchemaBuilder::class); } else { $ormBuilder = $orm->schemaBuilder(); } return new Inspector($config, array_merge($odmBuilder->getDocuments(), $ormBuilder->getRecords())); }
/** * Create appropriate instance of RelationSchema based on it's definition provided by ORM Record * or manually. Due internal format first definition key will be stated as definition type and * key value as record/entity definition relates too. * * @param RecordSchema $record * @param string $name * @param array $definition * @return RelationInterface * @throws SchemaException */ public function relationSchema(RecordSchema $record, $name, array $definition) { if (empty($definition)) { throw new SchemaException("Relation definition can not be empty."); } reset($definition); //Relation type must be provided as first in definition $type = key($definition); //We are letting ORM to resolve relation schema using container $relation = $this->orm->relationSchema($type, $this, $record, $name, $definition); if ($relation->hasEquivalent()) { //Some relations may declare equivalent relation to be used instead, used for Morphed //relations return $relation->createEquivalent(); } return $relation; }
/** * Save simple related entity. * * @param EntityInterface $entity * @param bool $validate * @return bool|void */ private function saveEntity(EntityInterface $entity, $validate) { if ($entity instanceof RecordInterface && $entity->isDeleted()) { return true; } if (!$entity instanceof ActiveEntityInterface) { throw new RelationException("Unable to save non active entity."); } $this->mountRelation($entity); if (!$entity->save($validate)) { return false; } if ($entity instanceof IdentifiedInterface) { $this->orm->cache()->remember($entity); } return true; }
/** * Execute query and every related query to compile records data in tree form - every relation * data will be included as sub key. * * Attention, Selector will cache compiled data tree and not query itself to keep data integrity * and to skip data compilation on second query. * * @return array */ public function fetchData() { //Pagination! $this->applyPagination(); //Generating statement $statement = $this->sqlStatement(); if (!empty($this->cacheLifetime)) { $cacheKey = $this->cacheKey ?: md5(serialize([$statement, $this->getParameters()])); if (empty($this->cacheStore)) { $this->cacheStore = $this->orm->container()->get(CacheInterface::class)->store(); } if ($this->cacheStore->has($cacheKey)) { $this->logger()->debug("Selector result were fetched from cache."); //We are going to store parsed result, not queries return $this->cacheStore->get($cacheKey); } } //We are bypassing run() method here to prevent query caching, we will prefer to cache //parsed data rather that database response $result = $this->database->query($statement, $this->getParameters()); //In many cases (too many inloads, too complex queries) parsing can take significant amount //of time, so we better profile it $benchmark = $this->benchmark('parseResult', $statement); //Here we are feeding selected data to our primary loaded to parse it and and create //data tree for our records $this->loader->parseResult($result, $rowsCount); $this->benchmark($benchmark); //Memory freeing $result->close(); //This must force loader to execute all post loaders (including ODM and etc) $this->loader->loadData(); //Now we can request our primary loader for compiled data $data = $this->loader->getResult(); //Memory free! Attention, it will not reset columns aliases but only make possible to run //query again $this->loader->clean(); if (!empty($this->cacheLifetime) && !empty($cacheKey)) { //We are caching full records tree, not queries $this->cacheStore->set($cacheKey, $data, $this->cacheLifetime); } return $data; }
/** * Create selector dedicated to load data for current loader. * * @return RecordSelector|null */ public function createSelector() { if (!$this->isLoadable()) { return null; } $selector = $this->orm->selector($this->definition[static::RELATION_TYPE], $this); //Setting columns to be loaded $this->configureColumns($selector); foreach ($this->loaders as $loader) { if ($loader instanceof self) { //Allowing sub loaders to configure required columns and conditions as well $loader->configureSelector($selector); } } foreach ($this->joiners as $joiner) { //Joiners must configure selector as well $joiner->configureSelector($selector); } return $selector; }
/** * {@inheritdoc} */ protected function container() { if (empty($this->orm)) { return parent::container(); } return $this->orm->container(); }
/** * Instance of DBAL\Table associated with relation pivot table. * * @return Table */ protected function pivotTable() { return $this->orm->database($this->definition[ORM::R_DATABASE])->table($this->definition[RecordEntity::PIVOT_TABLE]); }
/** * Filter data on inner relation or relation chain. Method automatically called by Selector, * see with() method. Logic is identical to loader() method. * * @see Selector::load() * @param string $relation Relation name, or chain of relations separated by. * @param array $options Loader options (will be applied to last chain element only). * @return Loader * @throws LoaderException */ public function joiner($relation, array $options = []) { //We have to force joining method for full chain $options['method'] = self::JOIN; if (($position = strpos($relation, '.')) !== false) { //Chain of relations provided $nested = $this->joiner(substr($relation, 0, $position), []); if (empty($nested) || !$nested instanceof self) { throw new LoaderException("Only ORM loaders can be used to generate/configure chain of relation joiners."); } //Recursively (will work only with ORM loaders). return $nested->joiner(substr($relation, $position + 1), $options); } if (!isset($this->schema[ORM::M_RELATIONS][$relation])) { $container = $this->container ?: $this->schema[ORM::M_ROLE_NAME]; throw new LoaderException("Undefined relation '{$relation}' under '{$container}'."); } if (isset($this->joiners[$relation])) { //Updating existed joiner options return $this->joiners[$relation]->setOptions($options); } $relationOptions = $this->schema[ORM::M_RELATIONS][$relation]; $joiner = $this->orm->loader($relationOptions[ORM::R_TYPE], $relation, $relationOptions[ORM::R_DEFINITION], $this); if (!$joiner instanceof self) { throw new LoaderException("Only ORM loaders can be used to generate/configure chain of relation joiners."); } return $this->joiners[$relation] = $joiner->setOptions($options); }