Ejemplo n.º 1
0
 public function test_assemble()
 {
     $path = '[element]->property';
     $this->assertSame(PropertyPath::assemble(PropertyPath::parse($path)), $path);
     $path = 'any[element]->property';
     $this->assertSame(PropertyPath::assemble(PropertyPath::parse($path)), $path);
     $path = '->property[element].any';
     $this->assertSame(PropertyPath::assemble(PropertyPath::parse($path)), $path);
 }
Ejemplo n.º 2
0
 /**
  * Register all models from the backend.
  * Aslo validates and corrects the model configurations.
  *
  * @param RepositoryBackend $backend
  */
 public function registerBackend($backend)
 {
     if ($backend->identifier === null) {
         throw new Exception('RepositoryBackend->identifier is required');
     }
     if (count($backend->configs) === 0) {
         notice(get_class($backend) . ': "' . $backend->identifier . '" doesn\'t have any ModelConfigs');
         return;
     }
     if (isset($this->backends[$backend->identifier])) {
         throw new Exception('RepositoryBackend "' . $backend->identifier . '" already registered');
     }
     $this->backends[$backend->identifier] = $backend;
     // Pass 1: Register configs
     foreach ($backend->configs as $config) {
         if ($config->backend === null) {
             $config->backend = $backend->identifier;
         }
         $this->registerModel($config);
     }
     // Pass 2: Register junctions
     foreach ($backend->junctions as $junction) {
         if ($junction->backend === null) {
             $junction->backend = $backend->identifier;
         }
         if (isset($this->junctions[$junction->name])) {
             notice('overwriting junction ' . $junction->name);
         }
         $this->junctions[$junction->name] = $junction;
     }
     // Pass 3: Auto detect id's
     foreach ($backend->configs as $backendConfig) {
         $config = $this->configs[$backendConfig->name];
         if (count($config->id) === 0) {
             if (isset($config->properties['id'])) {
                 // No id set, but the column 'id' exists?
                 $config->id = array('id');
             } else {
                 warning('Invalid config: ' . $config->name . '->id is not configured and could not detect an "id" element');
             }
         }
     }
     // Pass 4: Validate and correct configs
     foreach ($backend->configs as $backendConfig) {
         $config = $this->configs[$backendConfig->name];
         if (count($config->properties) === 0) {
             warning('Invalid config: ' . $config->name . '->properties array is not configured');
         }
         foreach ($config->id as $idIndex => $idColumn) {
             if (isset($config->properties[$idColumn])) {
                 $idProperty = $config->properties[$idColumn];
             } else {
                 warning('Invalid config: ' . $config->name . '->id[' . $idIndex . ']: "' . $idColumn . '" isn\'t mapped as a property');
             }
         }
         foreach ($config->belongsTo as $property => $belongsTo) {
             $validationError = false;
             if (is_array($belongsTo) === false) {
                 $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '] should be an array';
             }
             if (empty($belongsTo['model'])) {
                 $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '][model] not set';
             } elseif (empty($belongsTo['reference']) && empty($belongsTo['convert'])) {
                 $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '] is missing a [reference] or [convert] element';
             } elseif (isset($belongsTo['convert']) && isset($belongsTo['reference'])) {
                 $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '] can\'t contain both a [reference] and a [convert] element';
             }
             if (isset($belongsTo['reference'])) {
                 if (empty($belongsTo['id'])) {
                     // id not set, but (target)model is configured?
                     if (empty($this->configs[$belongsTo['model']])) {
                         $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '][id] couldn\'t be inferred, because model "' . $belongsTo['model'] . '" isn\'t registered';
                     } else {
                         $belongsToConfig = $this->_getConfig($belongsTo['model']);
                         // Infer/Assume that the id is the ID from the model
                         if (count($belongsToConfig->id) == 1) {
                             $belongsTo['id'] = current($belongsToConfig->id);
                             $config->belongsTo[$property]['id'] = $belongsTo['id'];
                             // Update config
                         } else {
                             $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '][id] not set and can\'t be inferred (for a complex key)';
                         }
                     }
                 }
                 if (isset($belongsTo['reference']) && isset($belongsTo['useIndex']) == false) {
                     if (empty($this->configs[$belongsTo['model']])) {
                         $validationError = 'Invalid config: ' . $config->name . '->belongsTo[' . $property . '][useIndex] couldn\'t be inferred, because model "' . $belongsTo['model'] . '" isn\'t registered';
                     } else {
                         $belongsToConfig = $this->_getConfig($belongsTo['model']);
                         // Is the foreign key is linked to the model id
                         $belongsTo['useIndex'] = count($belongsToConfig->id) == 1 && $belongsTo['id'] == current($belongsToConfig->id);
                         $config->belongsTo[$property]['useIndex'] = $belongsTo['useIndex'];
                         // Update config
                     }
                 }
                 if (isset($belongsTo['id'])) {
                     // Add foreign key to the collection mapping
                     $this->collectionMappings[$config->name][$property . '->' . $belongsToConfig->properties[$belongsTo['id']]] = $belongsTo['reference'];
                     $this->collectionMappings[$config->name][$property . '.' . $belongsToConfig->properties[$belongsTo['id']]] = $belongsTo['reference'];
                 }
             }
             // @todo Add collectionMapping for "convert" relations?
             if (empty($this->configs[$belongsTo['model']])) {
                 //					$validationError = 'Invalid config: '.$config->name.'->belongsTo['.$property.'][model] "'.$belongsTo['model'].'" isn\'t registered';
             }
             // Remove invalid relations
             if ($validationError) {
                 warning($validationError);
                 unset($config->belongsTo[$property]);
             }
         }
         foreach ($config->hasMany as $property => $hasMany) {
             $validationError = false;
             if (empty($hasMany['model'])) {
                 $validationError = 'Invalid config: ' . $config->name . '->hasMany[' . $property . '][model] not set';
             } elseif (isset($hasMany['convert'])) {
                 // no additional fields are needed.
             } elseif (empty($hasMany['reference'])) {
                 // @todo Infer property (lookup belongsTo)
                 $validationError = 'Invalid hasMany: ' . $config->name . '->hasMany[' . $property . '][reference] not set';
             } elseif (isset($hasMany['reference']) && empty($hasMany['belongsTo'])) {
                 $referencePath = PropertyPath::parse($hasMany['reference']);
                 if (count($referencePath) == 1) {
                     // The foreign key is linked directly
                 } elseif (empty($this->configs[$hasMany['model']])) {
                     $validationError = 'Invalid config: ' . $config->name . '->hasMany[' . $property . '][belongsTo] couldn\'t be inferred, because model "' . $hasMany['model'] . '" isn\'t registered';
                 } else {
                     // Infer the belongsTo path based on the model and reference path.
                     $hasManyConfig = $this->configs[$hasMany['model']];
                     $idProperty = array(array_value(array_pop($referencePath), 1));
                     if ($idProperty == $hasManyConfig->id) {
                         $hasMany['belongsTo'] = PropertyPath::assemble($referencePath);
                         $config->hasMany[$property]['belongsTo'] = $hasMany['belongsTo'];
                         // update config
                     }
                 }
             }
             if (isset($hasMany['through'])) {
                 if (array_key_exists('fields', $hasMany) === false) {
                     $config->hasMany[$property]['fields'] = [];
                 } elseif (array_key_exists('junctionClass', $hasMany) === false) {
                     $config->hasMany[$property]['junctionClass'] = Junction::class;
                 }
                 $junctionConfig = @$this->junctions[$hasMany['through']];
                 if (array_key_exists('idPath', $hasMany) === false && $junctionConfig) {
                     //                        dump($junctionConfig);
                 }
             }
             // Remove invalid relations
             if ($validationError) {
                 warning($validationError);
                 unset($config->hasMany[$property]);
             }
         }
         // Validate read & write filters
         foreach ($config->readFilters as $column => $filter) {
             if (empty($config->properties[$column])) {
                 notice('Invalid config: ' . $config->name . '->readFilters[' . $column . '] isn\'t mapped as property', $filter);
             }
         }
         foreach ($config->writeFilters as $column => $filter) {
             if (empty($config->properties[$column])) {
                 notice('Invalid config: ' . $config->name . '->writeFilters[' . $column . '] isn\'t mapped as property', $filter);
             }
         }
     }
     // Pass 5: Generate classes based on properties when no class is detected/found.
     foreach ($backend->configs as $config) {
         if (substr($config->class, 0, 11) !== '\\Generated\\') {
             $this->validated[$config->name] = false;
         } else {
             $this->validated[$config->name] = true;
             // Generated classes are valid by design.
             if (class_exists($config->class, false)) {
                 notice('Skipped generating class: "' . $config->class . '", a class with the same name exists');
                 continue;
             }
             $parts = explode('\\', $config->class);
             array_pop($parts);
             // remove class part.
             $namespace = implode('\\', array_slice($parts, 1));
             // Generate class
             $use = '';
             $aliases = [];
             $alias = $this->buildAlias(Object::class, $aliases, $use);
             $php = "\nclass " . $config->name . " extends " . $alias . "\n{\n";
             $properties = [];
             foreach ($config->properties as $path) {
                 $parsedPath = PropertyPath::parse($path);
                 $property = $parsedPath[0][1];
                 if (!in_array($property, $properties)) {
                     $php .= "    public \$" . $property . ";\n";
                     $properties[] = $property;
                 }
             }
             foreach ($config->belongsTo as $path => $belongsTo) {
                 $parsedPath = PropertyPath::parse($path);
                 $belongsToConfig = $this->_getConfig($belongsTo['model']);
                 $alias = $this->buildAlias($belongsToConfig->class, $aliases, $use);
                 $property = $parsedPath[0][1];
                 $php .= "\n";
                 $php .= "    /**\n";
                 $php .= "     * @var " . $alias . ' The associated ' . $belongsToConfig->name . "\n";
                 $php .= "     */\n";
                 $php .= "    public \$" . $property . ";\n";
             }
             foreach ($config->hasMany as $path => $hasMany) {
                 $parsedPath = PropertyPath::parse($path);
                 $hasManyConfig = $this->_getConfig($hasMany['model']);
                 $property = $parsedPath[0][1];
                 $aliasC = $this->buildAlias(Collection::class, $aliases, $use);
                 $alias = $this->buildAlias($hasManyConfig->class, $aliases, $use);
                 $php .= "\n";
                 $php .= "    /**\n";
                 $php .= "     * @var " . $aliasC . "|" . $alias . "[] A collection with the associated " . $hasManyConfig->plural . "\n";
                 $php .= "     */\n";
                 $php .= "    public \$" . $property . ";\n";
             }
             $php .= '}';
             $php = "namespace " . $namespace . ";\n\n" . $use . $php;
             if (\Sledgehammer\ENVIRONMENT === 'development' && $namespace === 'Generated') {
                 // Write autoComplete helper
                 // @todo Only write file when needed, aka validate $this->autoComplete
                 \Sledgehammer\mkdirs(\Sledgehammer\TMP_DIR . 'AutoComplete');
                 file_put_contents(\Sledgehammer\TMP_DIR . 'AutoComplete/' . $config->name . '.php', "<?php\n\n" . $php . "\n");
             }
             eval($php);
         }
     }
     // Pass 6: Generate or update the AutoComplete Helper for the default repository?
     if (\Sledgehammer\ENVIRONMENT == 'development' && isset(self::$instances['default']) && self::$instances['default'] === $this) {
         $autoCompleteFile = \Sledgehammer\TMP_DIR . 'AutoComplete/repository.ini';
         if ($this->autoComplete === null) {
             if (file_exists($autoCompleteFile)) {
                 $this->autoComplete = parse_ini_file($autoCompleteFile, true);
             } else {
                 $this->autoComplete = [];
             }
         }
         // Validate AutoCompleteHelper
         foreach ($backend->configs as $config) {
             $autoComplete = array('class' => $config->class, 'properties' => implode(', ', $config->properties));
             if (empty($this->autoComplete[$config->name]) || $this->autoComplete[$config->name] != $autoComplete) {
                 $this->autoComplete[$config->name] = $autoComplete;
                 \Sledgehammer\mkdirs(\Sledgehammer\TMP_DIR . 'AutoComplete');
                 \Sledgehammer\write_ini_file($autoCompleteFile, $this->autoComplete, 'Repository AutoComplete config');
                 $this->writeAutoCompleteHelper(\Sledgehammer\TMP_DIR . 'AutoComplete/DefaultRepository.php', 'DefaultRepository', 'Generated');
             }
         }
     }
 }