/** * @param string $serialized */ public final function unserialize($serialized) { $props = unserialize($serialized); $reflect = new \ReflectionClass($this); /** @var $prop \ReflectionProperty */ foreach ($reflect->getProperties() as $prop) { $key = $prop->getName(); $this->{$key} = $props[$key] ?? null; } $this->schemaTable = Schema::getTable($this->schemaTable); call_user_func_array([$this, "callBack"], Schema::getCallbackArgs()); }
/** * Load configuration to bootstrap Kernel */ private function loadConfig() { // Read configuration if not already $this->readConfig(); // Databases if (property_exists($this->config, "databases")) { // Database component defined in container? if ($this->container->has("Database")) { $this->setDatabases($this->config->databases); } // Fluent/ORM callback args Schema::setCallbackArgs($this); // Remove databases node from config unset($this->config->databases); } // Site if (property_exists($this->config, "site")) { // Build a URL from given props. if (!property_exists($this->config->site, "url")) { $domain = $this->config->site->domain ?? null; $https = $this->config->site->https ?? null; $port = intval($this->config->site->port ?? 0); if (!empty($domain)) { $this->config->site->url = sprintf('%s://%s%s/', $https === true ? "https" : "http", $domain, $port > 0 ? sprintf(':%d', $port) : ''); } } } // App if (property_exists($this->config, "app")) { // Timezone if (property_exists($this->config->app, "timeZone")) { $this->dateTime->setTimeZone($this->config->app->timeZone); } // Error Handler if (property_exists($this->config->app, "errorHandler")) { // Format if (property_exists($this->config->app->errorHandler, "format")) { $this->errorHandler->setFormat($this->config->app->errorHandler->format); } // Flag for handling triggered error messages $this->errorHandler->setFlag(Kernel::ERRORS_COLLECT); if (property_exists($this->config->app->errorHandler, "hideErrors")) { if (!$this->config->app->errorHandler->hideErrors) { $this->errorHandler->setFlag(Kernel::ERRORS_DEFAULT); } } } // Security if (property_exists($this->config->app, "security")) { // Cipher Component if (isset($this->cipher)) { // Configure Cipher $this->registerCipher(); } // Remove security prop. from config->app unset($this->config->app->security); } // Mailer if ($this->container->has("Mailer")) { // Register Mailer $this->registerMailer(); } // Cache if ($this->container->has("Cache")) { // Register Cache $this->registerCache(); } // Sessions if ($this->container->has("Session")) { // Register Session $this->registerSession(); } // Translator if ($this->container->has("Translator")) { $this->registerTranslator(); } // Knit if ($this->container->has("Knit")) { $this->registerKnit(); } } }
/** * Using OOP magic to create magical findBy* methods * Arguments: * First = (mixed) Value to search in database with * Second = (int) Number of rows to return * Third = (bool) Throw exception on no rows found or return false? * * @param $name * @param $arguments * @throws SchemaException * @return object|bool|array */ public static final function __callStatic($name, $arguments) { // Check if necessary constants are defined if (!defined("static::SCHEMA_TABLE")) { throw SchemaException::tableInitConstant("SCHEMA_TABLE"); } elseif (!defined("static::SCHEMA_MODEL")) { throw SchemaException::tableInitConstant("SCHEMA_MODEL"); } $tableNameConstant = constant("static::SCHEMA_TABLE"); $modelNameConstant = constant("static::SCHEMA_MODEL"); // Check if calling findBy* method if (substr($name, 0, 6) === "findBy") { $findBy = substr($name, 6); $findValue[] = $arguments[0] ?? null; $findLimit = 1; if (array_key_exists(1, $arguments)) { // Check if fetch limit is explicitly provided if (is_int($arguments[1])) { // Fetch limit is provided and is an Integer $findLimit = $arguments[1]; } else { // Fetch limit must be an Integer throw SchemaException::badArgType(__METHOD__, 2, "integer", gettype($arguments[1])); } } // Throw exception is row not found? $throwException = true; if (isset($arguments[2]) && $arguments[2] === false) { $throwException = false; } // Convert PascalCase/camelCase to snake_case $findBy = Comely::snakeCase($findBy); // SELECT query $findQuery = sprintf('SELECT * FROM `%1$s` WHERE `%2$s`=? LIMIT %3$s', $tableNameConstant, $findBy, $findLimit); // Run Query $tableName = get_called_class(); $db = Schema::getTable($tableName)->getDb(); $rows = $db->query($findQuery, $findValue, Database::QUERY_FETCH); // Row(s) were found? if (empty($rows)) { // No rows were found if ($throwException) { throw new SchemaException($name, "No rows were returned from database", 1201); } else { // Return FALSE return false; } } // Check if SCHEMA_MODEL constant is set $modelClass = $modelNameConstant; $callbackArgs = Schema::getCallbackArgs(); if (!is_null($modelClass)) { if ($findLimit === 1) { // Return single model instance $model = new $modelClass($tableName, $rows[0]); // Arguments injection if (method_exists($model, "callBack")) { $model->callBack(...$callbackArgs); } return $model; } else { $models = []; // Iterate through rows foreach ($rows as $row) { $model = new $modelClass($tableName, $row); // Arguments injection if (method_exists($model, "callBack")) { $model->callBack(...$callbackArgs); } $models[] = $model; } // return indexed Array containing Model's instances return $models; } } else { // Model is not set, return fetched Array return $findLimit === 1 ? $rows[0] : $rows; } } // Throw undefined method exception throw SchemaException::undefinedMethod($name); }
/** * Fluent constructor. * * When directly constructing a model that extends Fluent, no argument should be passed as these parameters accept * NULL values and they are only intended to be filled by AbstractTable::findBy[COL*] method * * @param string|null $table * @param array|null $row * @throws FluentException */ public final function __construct(string $table = null, array $row = null) { // Set modelName $this->modelName = get_called_class(); // Check if table relation is defined if (!defined("static::SCHEMA_TABLE")) { throw FluentException::initConstant("SCHEMA_TABLE", $this->modelName); } $modelSchemaTable = constant("static::SCHEMA_TABLE"); // Check if $table is provided for cross-checking if (!is_null($table)) { // Cross-check $table with model's SCHEMA_TABLE if ($table !== $modelSchemaTable) { // On fail, Cross-check if $table has SCHEMA_TABLE constant and that matches $tableConstant = sprintf("%s::SCHEMA_TABLE", $table); if (!defined($tableConstant) || constant($tableConstant) !== $modelSchemaTable) { // Model and table are NOT related throw FluentException::tableModelMismatch($this->modelName, $modelSchemaTable, $table); } } } // Save AbstractTable instance $this->schemaTable = Schema::table($modelSchemaTable); // Bootstrap data mapping $this->private = []; // Check if $row is Array if (is_array($row)) { // Verify that $row has all columns defined for AbstractTable $columnsKeys = $this->schemaTable->getColumnsKeys(); foreach ($columnsKeys as $column) { if (!array_key_exists($column, $row)) { throw FluentException::missingColumn($column, $this->modelName); } } // Get all columns $columns = $this->schemaTable->getColumns(); // Data mapping foreach ($row as $key => $value) { // Get column switch ($columns[$key]->scalarType) { case "integer": $value = (int) $value; break; case "double": $d = $columns[$key]->attributes["d"]; $value = round($value, $d + 1); break; default: break; } // Sort this key as public or private? $camelKey = Comely::camelCase($key); if (property_exists($this->modelName, $camelKey)) { // Public property $this->{$camelKey} = $value; } else { // Private variable $this->private[$camelKey] = $value; } } } }
/** * @param Database $db * @return StorageInterface */ public static function Database(Database $db) : StorageInterface { Schema::loadTable($db, "Comely\\IO\\Session\\Storage\\Database"); return Schema::table("comely_sessions"); }