/** * Add an configution for a model. * * @param ModelConfig $config */ protected function registerModel($config) { if (isset($this->configs[$config->name])) { warning('Overwriting model: "' . $config->name . '"'); // @todo? Allow overwritting models? or throw Exception? } $this->collectionMappings[$config->name] = array_flip($config->properties); // Add properties to the collectionMapping if ($config->class === null) { // Detect class $config->class = false; // fallback to a generated class foreach ($this->namespaces as $namespace) { $class = $namespace . $config->name; if (class_exists($class)) { // Is the class known? $config->class = $class; break; } } } if ($config->class === false) { // Should registerBackand generate a class? if (empty(self::$instances['default']) || self::$instances['default'] !== $this) { $config->class = '\\Generated\\' . $this->ref() . '\\' . $config->name; // Multiple Repositories have multiple namespaces } else { $config->class = '\\Generated\\' . $config->name; } } if (substr($config->class, 0, 1) !== '\\') { $config->class = '\\' . $config->class; } if ($config->plural === null) { $config->plural = Inflector::pluralize($config->name); } $this->configs[$config->name] = $config; $this->created[$config->name] = []; if (isset($this->plurals[$config->plural])) { warning('Overwriting plural[' . $config->plural . '] "' . $this->plurals[$config->plural] . '" with "' . $config->name . '"'); } $this->plurals[$config->plural] = $config->name; }
/** * Create model configs based on the tables in the database schema. * * @param string $dbLink */ public function inspectDatabase($dbLink = 'default', $tablePrefix = '') { // Pass 1: Retrieve and parse schema information $db = Connection::instance($dbLink); if (self::$cacheTimeout === false) { $schema = $this->getSchema($db, $tablePrefix); } else { $cacheIdentifier = $dbLink . ' ' . $tablePrefix . ' '; if (count($db->logger->entries) > 0) { $cacheIdentifier .= $db->logger->entries[0][0]; // Add the connect statement to the identifier. } $backend = $this; $schema = \Sledgehammer\cache('DatabaseRepositoryBackend[' . md5($cacheIdentifier) . ']', self::$cacheTimeout, function () use($backend, $db, $tablePrefix) { return $backend->getSchema($db, $tablePrefix); }); } $models = []; $junctions = []; foreach ($schema as $tableName => $table) { $name = Inflector::modelize($tableName, ['prefix' => $tablePrefix, 'singularizeLast' => true]); $plural = Inflector::modelize($tableName, ['prefix' => $tablePrefix, 'singularizeLast' => false]); $config = new ModelConfig($name, ['plural' => $plural, 'backendConfig' => $table]); $config->backendConfig['dbLink'] = $dbLink; $config->backendConfig['collection'] = ['columns' => []]; // database collection config. $config->id = $table['primaryKeys']; foreach ($table['columns'] as $column => $info) { $default = @$info['default']; if (empty($info['foreignKeys'])) { $property = Inflector::variablize($column); $config->properties[$column] = $property; $config->defaults[$property] = $default; if (substr($info['type'], -10) === 'tinyint(1)' && $info['null'] === false) { $config->readFilters[$column] = __CLASS__ . '::valueToBool'; $config->writeFilters[$column] = __CLASS__ . '::boolToValue'; $config->defaults[$property] = self::valueToBool($config->defaults[$column]); } } else { if (count($info['foreignKeys']) > 1) { notice('Multiple foreign-keys per column not supported'); } $foreignKey = $info['foreignKeys'][0]; $property = $this->stripIdSuffix($column); if ($column !== $property && array_key_exists($property, $table['columns'])) { $property = $column; // restore prefix, to prevent a naming collision. } if (array_key_exists($property, $table['columns']) && $property != $column) { notice('Unable to use belongsTo["' . $property . '"], an column with the same name exists', ['belongsTo' => $info, 'Exising column' => $table['columns'][$property]]); } $foreignModel = Inflector::modelize($foreignKey['table'], ['prefix' => $tablePrefix, 'singularizeLast' => true]); $config->belongsTo[$property] = ['reference' => $column, 'model' => $foreignModel, 'id' => $foreignKey['column'], 'default' => $default]; } } // Detect junction table if (count($config->id) === 2) { $primaryKeyAreForeignKeys = true; foreach ($config->belongsTo as $belongsTo) { if (in_array($belongsTo['reference'], $config->id) === false) { $primaryKeyAreForeignKeys = false; break; } } if ($primaryKeyAreForeignKeys) { $junctions[$tableName] = $config; $this->junctions[$config->name] = $config; continue; } } $models[$tableName] = $config; if (isset($this->configs[$config->name])) { notice('Model "' . $config->name . '" (for the "' . $tableName . '" table) is defined in both "' . $this->configs[$config->name]->backendConfig['dbLink'] . '" and "' . $dbLink . '" '); $suffix = 2; while (isset($this->configs[$config->name . '_' . $suffix])) { ++$suffix; } $this->configs[$config->name . '_' . $suffix] = $config; } else { $this->configs[$config->name] = $config; } } // Pass 2: hasMany foreach ($this->configs as $config) { $table = $schema[$config->backendConfig['table']]; foreach ($table['referencedBy'] as $i => $reference) { $property = $reference['table']; if ($tablePrefix != '' && substr($property, 0, strlen($tablePrefix)) == $tablePrefix) { $property = substr($property, strlen($tablePrefix)); // Strip prefix } foreach ($table['referencedBy'] as $j => $otherRef) { if ($i != $j && $otherRef['table'] === $reference['table']) { // The model has relations to the same table. $property = $this->stripIdSuffix($reference['column']) . '_' . $property; } } $property = Inflector::variablize($property); if (in_array($property, $config->getPropertyNames())) { notice('Unable to use ' . $config->name . '->hasMany[' . $property . '] a property with the same name exists'); break; } if (isset($models[$reference['table']])) { // One-to-many relation $belongsToModel = $models[$reference['table']]; foreach ($belongsToModel->belongsTo as $belongsToProperty => $belongsTo) { if ($belongsTo['model'] == $config->name && $belongsTo['reference'] == $reference['column']) { $config->hasMany[$property] = ['model' => $belongsToModel->name, 'reference' => $reference['column'], 'belongsTo' => $belongsToProperty]; $config->defaults[$property] = []; break; } } } elseif (empty($junctions[$reference['table']])) { notice('Missing a model or relation for "' . $reference['table'] . '" in the database schema'); } } } // Pass 3: hasMany junctions foreach ($this->configs as $config) { $table = $schema[$config->backendConfig['table']]; foreach ($table['referencedBy'] as $reference) { if (isset($junctions[$reference['table']])) { // Many-to-many realtion $junction = $junctions[$reference['table']]; $hasMany = ['through' => $junction->name, 'fields' => $junction->properties]; foreach ($junction->belongsTo as $belongsToProperty => $belongsTo) { if ($belongsTo['model'] === $config->name) { $hasMany['reference'] = $belongsTo['reference']; } else { $property = Inflector::pluralize($belongsToProperty); $hasMany['model'] = $belongsTo['model']; $hasMany['id'] = $belongsTo['reference']; } } if (in_array($property, $config->getPropertyNames())) { $property = Inflector::variablize($reference['table']); } if (in_array($property, $config->getPropertyNames())) { notice('Unable to use ' . $property . '->hasMany[' . $property . '] a property with the same name exists'); break; } $config->hasMany[$property] = $hasMany; $config->defaults[$property] = []; } } } }