src/RocknRoot/StrayFw/Database/Postgres/Schema.php
private function generateModel(string $modelName, array $modelDefinition)
    {
        $definition = $this->getDefinition();
        $primary = array();
        $constructor = '    public function __construct(array $fetch = null)' . "\n    {\n        parent::__construct();\n";
        $constructorDefaults = '        if (is_array($fetch) === true && count($fetch) > 0) {' . PHP_EOL . '            $this->new = false;' . "\n        } else {\n" . '            $fetch = array();' . "\n        }\n";
        $consts = null;
        $properties = null;
        $accessors = null;
        $allFieldsRealNames = "    public function getAllFieldsRealNames() : array\n    {\n        return [ ";
        $allFieldsAliases = "    public function getAllFieldsAliases() : array\n    {\n        return [ ";

        $modelRealName = null;
        if (isset($modelDefinition['name']) === true) {
            $modelRealName = $modelDefinition['name'];
        } else {
            $modelRealName = Helper::codifyName($this->mapping) . '_' . Helper::codifyName($modelName);
        }

        if (isset($modelDefinition['fields']) === false) {
            throw new InvalidSchemaDefinition('model "' . $modelName . '" has no field');
        }
        foreach ($modelDefinition['fields'] as $fieldName => $fieldDefinition) {
            $fieldRealName = null;
            if (isset($fieldDefinition['name']) === true) {
                $fieldRealName = $fieldDefinition['name'];
            } else {
                $fieldRealName = Helper::codifyName($modelName) . '_' . Helper::codifyName($fieldName);
            }

            $properties .= '    protected $field' . ucfirst($fieldName) . ";\n";
            $consts .= '    const FIELD_' . strtoupper(Helper::codifyName($fieldName)) . ' = \'' . $modelRealName . '.' . $fieldRealName . "';\n";

            $constructor .= '        $this->field' . ucfirst($fieldName) . ' = [ \'alias\' => \'' . $fieldName . "', 'value' => null ];\n";
            $constructorDefaults .= '        if (isset($fetch[\'' . $fieldRealName . '\']) === true) {' . "\n            ";
            $constructorDefaults .= '$this->set' . ucfirst($fieldName) . '($fetch[\'' . $fieldRealName . "']);\n        } else {\n            ";
            $constructorDefaults .= '$this->set' . ucfirst($fieldName) . '(';
            if (isset($fieldDefinition['default']) === true) {
                if (is_bool($fieldDefinition['default']) === true) {
                    $constructorDefaults .= ($fieldDefinition['default'] === true) ? 'true' : 'false';
                } else {
                    $constructorDefaults .= '\'' . $fieldDefinition['default'] . '\'';
                }
            } else {
                $constructorDefaults .= 'null';
            }
            $constructorDefaults .= ");\n        }\n";

            if (isset($fieldDefinition['primary']) === true && $fieldDefinition['primary'] === true) {
                $primary[] = $fieldName;
            }

            $accessors .= '    public function get' . ucfirst($fieldName) . "()\n    {\n        ";
            switch ($fieldDefinition['type']) {
                case 'string':
                    $accessors .= 'return stripslashes($this->field' . ucfirst($fieldName) . '[\'value\']);';
                    break;
                case 'char':
                    $accessors .= 'return stripslashes($this->field' . ucfirst($fieldName) . '[\'value\']);';
                    break;
                case 'bool':
                    $accessors .= 'return filter_var($this->field' . ucfirst($fieldName) . '[\'value\'], FILTER_VALIDATE_BOOLEAN);';
                    break;
                case 'json':
                    $accessors .= 'return json_decode($this->field' . ucfirst($fieldName) . '[\'value\'], true);';
                    break;
                default:
                    $accessors .= 'return $this->field' . ucfirst($fieldName) . '[\'value\'];';
                    break;
            }
            $accessors .= "\n    }\n\n";

            $accessors .= '    public function set' . ucfirst($fieldName) . '($value)' . "\n    {\n        ";
            if ($fieldDefinition['type'] == 'bool') {
                $accessors .= '$this->field' . ucfirst($fieldName) . '[\'value\'] = (bool) $value;';
            } elseif ($fieldDefinition['type'] == 'json') {
                $accessors .= 'if (is_string($value) === true) {' . PHP_EOL;
                $accessors .= '            $this->field' . ucfirst($fieldName) . '[\'value\'] = $value;' . PHP_EOL;
                $accessors .= '        } else {' . PHP_EOL;
                $accessors .= '            $this->field' . ucfirst($fieldName) . '[\'value\'] = json_encode($value);' . PHP_EOL;
                $accessors .= '        }' . PHP_EOL;
            } else {
                $accessors .= '$this->field' . ucfirst($fieldName) . '[\'value\'] = $value;';
            }
            $accessors .= PHP_EOL . '        $this->modified[\'' . $fieldName . '\'] = true;';
            $accessors .= "\n        return true;\n    }\n\n";

            $allFieldsRealNames .= '\'' . $modelRealName . '.' . $fieldRealName . '\', ';
            $allFieldsAliases .= '\'' . $fieldName . '\', ';
        }

        if (isset($modelDefinition['links']) === true) {
            foreach ($modelDefinition['links'] as $linkName => $linkDefinition) {
                if (isset($definition[$linkDefinition['model']]) === false) {
                    throw new InvalidSchemaDefinition('unknown model for link "' . $linkName . '" of model "' . $modelName . '"');
                }
                $linkedModel = $definition[$linkDefinition['model']];
                $accessors .= '    public function getLinked' . ucfirst($linkName) . "()\n    {\n        ";
                $accessors .= 'return Models\\' . ucfirst($linkDefinition['model']) . '::fetchEntity([ ';
                $links = array();
                foreach ($linkDefinition['fields'] as $from => $to) {
                    if (isset($modelDefinition['fields'][$from]) === false) {
                        throw new InvalidSchemaDefinition('building link : model "' . $modelName . '" has no field named "' . $from . '"');
                    }
                    if (isset($linkedModel['fields'], $linkedModel['fields'][$to]) === false) {
                        throw new InvalidSchemaDefinition('building link : model "' . $linkDefinition['model'] . '" has no field named "' . $to . '"');
                    }
                    $links[] = '\'' . $to . '\' => $this->get' . ucfirst($from) . '()';
                }
                $accessors .= implode(', ', $links) . " ]);\n    }\n\n";
            }
        }

        $allFieldsRealNames = substr($allFieldsRealNames, 0, -2) . " ];\n    }\n\n";
        $allFieldsAliases = substr($allFieldsAliases, 0, -2) . " ];\n    }\n\n";
        $constructor .= $constructorDefaults;
        $constructor .= PHP_EOL . '        $this->modified = array();' . PHP_EOL;
        $constructor .= "    }\n\n";

        $mapping = Mapping::get($this->mapping);

        $path = rtrim($mapping['config']['models']['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $path .= 'Base' . DIRECTORY_SEPARATOR . ucfirst($modelName) . '.php';
        $file = fopen($path, 'w+');
        if ($file === false) {
            throw new FileNotWritable('can\'t open "' . $path . '" with write permission');
        }
        $content = "<?php\n\nnamespace " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . "\\Base;\n\nuse RocknRoot\StrayFw\Database\Postgres\Model;\n";
        $content .= "\nclass " . ucfirst($modelName) . " extends Model\n{\n";
        $content .= "    public function getDatabaseName() : string\n    {\n        return '" . $mapping['config']['database'] . "';\n    }\n\n";
        $content .= "    public function getTableName() : string\n    {\n        return '" . $modelRealName . "';\n    }\n\n";
        $content .= PHP_EOL . $consts . PHP_EOL . $properties . PHP_EOL;
        $content .= $constructor . $accessors . $allFieldsRealNames . $allFieldsAliases;
        $content .= "    public function getPrimary() : array\n    {\n        return [ '" . implode('\', \'', $primary) . "' ];\n    }\n";
        $content .= "}";
        if (fwrite($file, $content) === false) {
            throw new FileNotWritable('can\'t write in "' . $path . '"');
        }
        fclose($file);

        $path = rtrim($mapping['config']['models']['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $path .= ucfirst($modelName) . '.php';
        if (file_exists($path) === false) {
            $file = fopen($path, 'w+');
            if ($file === false) {
                throw new FileNotWritable('can\'t open "' . $path . '" with write permission');
            }
            $content = "<?php\n\nnamespace " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . ";\n\nuse " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . "\\Base\\" . ucfirst($modelName) . " as BaseModel;\n\nclass " . ucfirst($modelName) . " extends BaseModel\n{\n}";
            if (fwrite($file, $content) === false) {
                throw new FileNotWritable('can\'t write in "' . $path . '"');
            }
        }

        echo $modelName . ' - Done' . PHP_EOL;
    }
private function generateModel(string $modelName, array $modelDefinition)
    {
        $definition = $this->getDefinition();
        $primary = array();
        $constructor = '    public function __construct(array $fetch = null)' . "\n    {\n        parent::__construct();\n";
        $constructorDefaults = '        if (is_array($fetch) === true && count($fetch) > 0) {' . PHP_EOL . '            $this->new = false;' . "\n        } else {\n" . '            $fetch = array();' . "\n        }\n";
        $consts = null;
        $properties = null;
        $accessors = null;
        $allFieldsRealNames = "    public function getAllFieldsRealNames() : array\n    {\n        return [ ";
        $allFieldsAliases = "    public function getAllFieldsAliases() : array\n    {\n        return [ ";

        $modelRealName = null;
        if (isset($modelDefinition['name']) === true) {
            $modelRealName = $modelDefinition['name'];
        } else {
            $modelRealName = Helper::codifyName($this->mapping) . '_' . Helper::codifyName($modelName);
        }

        if (isset($modelDefinition['fields']) === false) {
            throw new InvalidSchemaDefinition('model "' . $modelName . '" has no field');
        }
        foreach ($modelDefinition['fields'] as $fieldName => $fieldDefinition) {
            $fieldRealName = null;
            if (isset($fieldDefinition['name']) === true) {
                $fieldRealName = $fieldDefinition['name'];
            } else {
                $fieldRealName = Helper::codifyName($modelName) . '_' . Helper::codifyName($fieldName);
            }

            $properties .= '    protected $field' . ucfirst($fieldName) . ";\n";
            $consts .= '    const FIELD_' . strtoupper(Helper::codifyName($fieldName)) . ' = \'' . $modelRealName . '.' . $fieldRealName . "';\n";

            $constructor .= '        $this->field' . ucfirst($fieldName) . ' = [ \'alias\' => \'' . $fieldName . "', 'value' => null ];\n";
            $constructorDefaults .= '        if (isset($fetch[\'' . $fieldRealName . '\']) === true) {' . "\n            ";
            $constructorDefaults .= '$this->set' . ucfirst($fieldName) . '($fetch[\'' . $fieldRealName . "']);\n        } else {\n            ";
            $constructorDefaults .= '$this->set' . ucfirst($fieldName) . '(';
            if (isset($fieldDefinition['default']) === true) {
                if (is_bool($fieldDefinition['default']) === true) {
                    $constructorDefaults .= ($fieldDefinition['default'] === true) ? 'true' : 'false';
                } else {
                    $constructorDefaults .= '\'' . $fieldDefinition['default'] . '\'';
                }
            } else {
                $constructorDefaults .= 'null';
            }
            $constructorDefaults .= ");\n        }\n";

            if (isset($fieldDefinition['primary']) === true && $fieldDefinition['primary'] === true) {
                $primary[] = $fieldName;
            }

            $accessors .= '    public function get' . ucfirst($fieldName) . "()\n    {\n        ";
            switch ($fieldDefinition['type']) {
                case 'string':
                    $accessors .= 'return stripslashes($this->field' . ucfirst($fieldName) . '[\'value\']);';
                    break;
                case 'char':
                    $accessors .= 'return stripslashes($this->field' . ucfirst($fieldName) . '[\'value\']);';
                    break;
                case 'bool':
                    $accessors .= 'return filter_var($this->field' . ucfirst($fieldName) . '[\'value\'], FILTER_VALIDATE_BOOLEAN);';
                    break;
                case 'json':
                    $accessors .= 'return json_decode($this->field' . ucfirst($fieldName) . '[\'value\'], true);';
                    break;
                default:
                    $accessors .= 'return $this->field' . ucfirst($fieldName) . '[\'value\'];';
                    break;
            }
            $accessors .= "\n    }\n\n";

            $accessors .= '    public function set' . ucfirst($fieldName) . '($value)' . "\n    {\n        ";
            if ($fieldDefinition['type'] == 'bool') {
                $accessors .= '$this->field' . ucfirst($fieldName) . '[\'value\'] = (bool) $value;';
            } elseif ($fieldDefinition['type'] == 'json') {
                $accessors .= 'if (is_string($value) === true) {' . PHP_EOL;
                $accessors .= '            $this->field' . ucfirst($fieldName) . '[\'value\'] = $value;' . PHP_EOL;
                $accessors .= '        } else {' . PHP_EOL;
                $accessors .= '            $this->field' . ucfirst($fieldName) . '[\'value\'] = json_encode($value);' . PHP_EOL;
                $accessors .= '        }' . PHP_EOL;
            } else {
                $accessors .= '$this->field' . ucfirst($fieldName) . '[\'value\'] = $value;';
            }
            $accessors .= PHP_EOL . '        $this->modified[\'' . $fieldName . '\'] = true;';
            $accessors .= "\n        return true;\n    }\n\n";

            $allFieldsRealNames .= '\'' . $modelRealName . '.' . $fieldRealName . '\', ';
            $allFieldsAliases .= '\'' . $fieldName . '\', ';
        }

        if (isset($modelDefinition['links']) === true) {
            foreach ($modelDefinition['links'] as $linkName => $linkDefinition) {
                if (isset($definition[$linkDefinition['model']]) === false) {
                    throw new InvalidSchemaDefinition('unknown model for link "' . $linkName . '" of model "' . $modelName . '"');
                }
                $linkedModel = $definition[$linkDefinition['model']];
                $accessors .= '    public function getLinked' . ucfirst($linkName) . "()\n    {\n        ";
                $accessors .= 'return Models\\' . ucfirst($linkDefinition['model']) . '::fetchEntity([ ';
                $links = array();
                foreach ($linkDefinition['fields'] as $from => $to) {
                    if (isset($modelDefinition['fields'][$from]) === false) {
                        throw new InvalidSchemaDefinition('building link : model "' . $modelName . '" has no field named "' . $from . '"');
                    }
                    if (isset($linkedModel['fields'], $linkedModel['fields'][$to]) === false) {
                        throw new InvalidSchemaDefinition('building link : model "' . $linkDefinition['model'] . '" has no field named "' . $to . '"');
                    }
                    $links[] = '\'' . $to . '\' => $this->get' . ucfirst($from) . '()';
                }
                $accessors .= implode(', ', $links) . " ]);\n    }\n\n";
            }
        }

        $allFieldsRealNames = substr($allFieldsRealNames, 0, -2) . " ];\n    }\n\n";
        $allFieldsAliases = substr($allFieldsAliases, 0, -2) . " ];\n    }\n\n";
        $constructor .= $constructorDefaults;
        $constructor .= PHP_EOL . '        $this->modified = array();' . PHP_EOL;
        $constructor .= "    }\n\n";

        $mapping = Mapping::get($this->mapping);

        $path = rtrim($mapping['config']['models']['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $path .= 'Base' . DIRECTORY_SEPARATOR . ucfirst($modelName) . '.php';
        $file = fopen($path, 'w+');
        if ($file === false) {
            throw new FileNotWritable('can\'t open "' . $path . '" with write permission');
        }
        $content = "<?php\n\nnamespace " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . "\\Base;\n\nuse RocknRoot\StrayFw\Database\Postgres\Model;\n";
        $content .= "\nclass " . ucfirst($modelName) . " extends Model\n{\n";
        $content .= "    public function getDatabaseName() : string\n    {\n        return '" . $mapping['config']['database'] . "';\n    }\n\n";
        $content .= "    public function getTableName() : string\n    {\n        return '" . $modelRealName . "';\n    }\n\n";
        $content .= PHP_EOL . $consts . PHP_EOL . $properties . PHP_EOL;
        $content .= $constructor . $accessors . $allFieldsRealNames . $allFieldsAliases;
        $content .= "    public function getPrimary() : array\n    {\n        return [ '" . implode('\', \'', $primary) . "' ];\n    }\n";
        $content .= "}";
        if (fwrite($file, $content) === false) {
            throw new FileNotWritable('can\'t write in "' . $path . '"');
        }
        fclose($file);

        $path = rtrim($mapping['config']['models']['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        $path .= ucfirst($modelName) . '.php';
        if (file_exists($path) === false) {
            $file = fopen($path, 'w+');
            if ($file === false) {
                throw new FileNotWritable('can\'t open "' . $path . '" with write permission');
            }
            $content = "<?php\n\nnamespace " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . ";\n\nuse " . ltrim(rtrim($mapping['config']['models']['namespace'], '\\'), '\\') . "\\Base\\" . ucfirst($modelName) . " as BaseModel;\n\nclass " . ucfirst($modelName) . " extends BaseModel\n{\n}";
            if (fwrite($file, $content) === false) {
                throw new FileNotWritable('can\'t write in "' . $path . '"');
            }
        }

        echo $modelName . ' - Done' . PHP_EOL;
    }
Generate classes for a model.

@param string $modelName model alias
@param array $modelDefinition model definition from a schema file
@throws InvalidSchemaDefinition if a model has no field
@throws InvalidSchemaDefinition if a model is linked to an unknown enum
@throws InvalidSchemaDefinition if a model is linked to an unknown model
@throws InvalidSchemaDefinition if, while building a link, a model has an unknown needed field
@throws FileNotWritable         if a base file can't be opened with write permission
@throws FileNotWritable         if a user file can't be opened with write permission