├── .github └── FUNDING.yml ├── .sonarcloud.properties ├── .travis.yml ├── ActiveRecord.php ├── Autoloader.php ├── BaseRecord.php ├── Db.php ├── LICENSE ├── LiteRecord.php ├── Metadata ├── Metadata.php ├── MysqlMetadata.php ├── PgsqlMetadata.php ├── SqliteMetadata.php └── SqlsrvMetadata.php ├── Paginator.php ├── Query ├── mysql_last_insert_id.php ├── mysql_limit.php ├── pgsql_last_insert_id.php ├── pgsql_limit.php ├── sqlite_last_insert_id.php ├── sqlite_limit.php └── sqlsrv_limit.php ├── QueryGenerator.php ├── README.en.md ├── README.md ├── To-do.md ├── composer.json ├── config └── config_databases.php └── tests ├── ActiveRecord ├── DbTest.php └── Metadata │ ├── MetadataTest.php │ ├── MysqlMetadataTest.php │ ├── PgsqlMetadataTest.php │ └── SqliteMetadataTest.php ├── Utils └── PrivateUtil.php ├── bootstrap.php ├── config └── databases.php └── phpunit.xml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: kumbiaphp 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | dist: bionic 3 | 4 | php: 5 | - 7.4 6 | - 8.0 7 | - 8.1 8 | - nightly 9 | 10 | matrix: 11 | allow_failures: 12 | - php: nightly 13 | 14 | notifications: 15 | slack: kumbiaphp:51JaKQTXASwf52D8b32OyWb9 16 | # irc: "irc.freenode.org#kumbiaphp" 17 | # email: 18 | # - xxxxx@gmail.com 19 | 20 | services: 21 | - mysql 22 | - postgresql 23 | 24 | install: 25 | - composer install 26 | 27 | before_script: 28 | - "mysql -e 'DROP DATABASE IF EXISTS kumbia_test;'" 29 | - "mysql -e 'create database kumbia_test;'" 30 | - "psql -c 'DROP DATABASE IF EXISTS kumbia_test;' -U postgres" 31 | - "psql -c 'create database kumbia_test;' -U postgres" 32 | 33 | script: 34 | - phpunit --version 35 | - phpunit --configuration tests/phpunit.xml --coverage-text --colors --coverage-clover=coverage.clover 36 | - wget https://scrutinizer-ci.com/ocular.phar 37 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 38 | -------------------------------------------------------------------------------- /ActiveRecord.php: -------------------------------------------------------------------------------- 1 | model; 42 | if ($relations->type === self::HAS_MANY) { 43 | return $model::allBy($relations->via, $obj->pk()); 44 | } 45 | 46 | return $model::first($relations->via, $obj->pk()); 47 | } 48 | 49 | public static function hasMany($name, $class, $via = null) 50 | { 51 | $str = strtolower($name); 52 | $name = static::getTable(); 53 | static::$relations[$str] = (object) [ 54 | 'model' => $class, 55 | 'type' => self::HAS_MANY, 56 | 'via' => $via ? $via : "{$name}_id", 57 | ]; 58 | } 59 | 60 | public static function hasOne($name, $class, $via = null) 61 | { 62 | $str = strtolower($name); 63 | $name = static::getTable(); 64 | static::$relations[$str] = (object) [ 65 | 'model' => $class, 66 | 'type' => self::HAS_ONE, 67 | 'via' => $via ? $via : "{$name}_id", 68 | ]; 69 | } 70 | 71 | /** 72 | * json_encode() method. 73 | */ 74 | public function jsonSerialize() 75 | { 76 | return $this; //TODO: populate relations before 77 | } 78 | 79 | public function __get($key) 80 | { 81 | if (property_exists($this, $key)) { 82 | return $this->$key; 83 | } 84 | //it's a relationship 85 | if (isset(static::$relations[$key])) { 86 | $this->populate($key); 87 | 88 | return $this->$key; 89 | } 90 | 91 | return null; //TODO: change for error 92 | } 93 | 94 | protected static function getRelationship($rel) 95 | { 96 | if (!isset(static::$relations[$rel])) { 97 | throw new \RuntimeException("Invalid relationship '$rel'", 500); 98 | } 99 | 100 | return static::$relations[$rel]; 101 | } 102 | 103 | public function populate($rel) 104 | { 105 | $relations = static::getRelationship($rel); 106 | $this->$rel = static::resolver($relations, $this); 107 | } 108 | 109 | /** 110 | * Pagination of Results. 111 | * 112 | * @param array $params [description] 113 | * @param array $values [description] 114 | * @param int $page [description] 115 | * @param int $per_page [description] 116 | * 117 | * @return Paginator [description] 118 | */ 119 | public static function pagination($params = [], $values = [], $page = 1, $per_page = 10) 120 | { 121 | $model = static::class; 122 | unset($params['limit'], $params['offset']); 123 | $sql = QueryGenerator::select($model::getSource(), $model::getDriver(), $params); 124 | 125 | return new Paginator($model, $sql, $page, $per_page, $values); 126 | } 127 | 128 | /** 129 | * Actualizar registros. 130 | * 131 | * @param array $fields 132 | * @param string $where condiciones 133 | * @param array $values valores para condiciones 134 | * 135 | * @return int numero de registros actualizados 136 | */ 137 | public static function updateAll(array $fields, string $where = '', array $values = []) 138 | { 139 | $sql = QueryGenerator::updateAll(static::class, $fields, $values, $where); 140 | $sth = self::prepare($sql); 141 | $sth->execute($values); 142 | 143 | return $sth->rowCount(); 144 | } 145 | 146 | /** 147 | * Eliminar registro. 148 | * 149 | * @param string $where condiciones 150 | * @param array |string $values valores 151 | * 152 | * @return int numero de registros eliminados 153 | */ 154 | public static function deleteAll($where = null, $values = null) 155 | { 156 | $source = static::getSource(); 157 | $sql = QueryGenerator::deleteAll($source, $where); 158 | $sth = self::query($sql, $values); 159 | 160 | return $sth->rowCount(); 161 | } 162 | 163 | /** 164 | * Elimina caracteres que podrian ayudar a ejecutar 165 | * un ataque de Inyeccion SQL. 166 | * 167 | * @param string $sqlItem 168 | * 169 | * @return string 170 | * @throws \RuntimeException 171 | */ 172 | public static function sqlItemSanitize($sqlItem) 173 | { 174 | $sqlItem = \trim($sqlItem); 175 | if ($sqlItem !== '' && $sqlItem !== null) { 176 | $sql_temp = \preg_replace('/\s+/', '', $sqlItem); 177 | if (!\preg_match('/^[a-zA-Z0-9_\.]+$/', $sql_temp)) { 178 | throw new \RuntimeException('Se está tratando de ejecutar un SQL peligroso!'); 179 | } 180 | } 181 | 182 | return $sqlItem; 183 | } 184 | 185 | /** 186 | * Obtener la primera coincidencia por el campo indicado. 187 | * 188 | * @param string $field campo 189 | * @param string $value valor 190 | * @param array $params parametros adicionales 191 | * order: criterio de ordenamiento 192 | * fields: lista de campos 193 | * join: joins de tablas 194 | * group: agrupar campos 195 | * having: condiciones de grupo 196 | * offset: valor offset 197 | * 198 | * @return ActiveRecord 199 | */ 200 | public static function firstBy($field, $value, $params = []) 201 | { 202 | $field = self::sqlItemSanitize($field); 203 | $params['where'] = "$field = ?"; 204 | 205 | return self::first($params, $value); 206 | } 207 | 208 | /** 209 | * Obtener la primera coincidencia de las condiciones indicadas. 210 | * 211 | * @param array $params parametros adicionales 212 | * order: criterio de ordenamiento 213 | * fields: lista de campos 214 | * group: agrupar campos 215 | * join: joins de tablas 216 | * having: condiciones de grupo 217 | * offset: valor offset queda 218 | * @param array $values valores de busqueda 219 | * 220 | * @return ActiveRecord 221 | */ 222 | public static function first($params = [], $values = []) 223 | { 224 | $args = func_get_args(); 225 | /*Reescribe el limit*/ 226 | $args[0]['limit'] = 1; 227 | $res = self::doQuery($args); 228 | 229 | return $res->fetch(); 230 | } 231 | 232 | /** 233 | * Obtener todos los registros. 234 | * 235 | * @param array $params 236 | * where: condiciones where 237 | * order: criterio de ordenamiento 238 | * fields: lista de campos 239 | * join: joins de tablas 240 | * group: agrupar campos 241 | * having: condiciones de grupo 242 | * limit: valor limit 243 | * offset: valor offset 244 | * @param array $values valores de busqueda 245 | * 246 | * @return \PDOStatement 247 | */ 248 | public static function all($params = [], $values = []) 249 | { 250 | $res = self::doQuery(func_get_args()); 251 | 252 | return $res->fetchAll(); 253 | } 254 | 255 | /** 256 | * Do a query. 257 | * 258 | * @param array $array params of query 259 | * 260 | * @return \PDOStatement|false 261 | */ 262 | protected static function doQuery(array $array) 263 | { 264 | $params = self::getParam($array); 265 | $values = self::getValues($array); 266 | $sql = QueryGenerator::select(static::getSource(), static::getDriver(), $params); 267 | $sth = static::query($sql, $values); 268 | 269 | return $sth; 270 | } 271 | 272 | /** 273 | * Retorna los parametros para el doQuery. 274 | * 275 | * @param array $array 276 | * 277 | * @return array 278 | */ 279 | protected static function getParam(array &$array) 280 | { 281 | $val = array_shift($array); 282 | 283 | return is_null($val) ? [] : $val; 284 | } 285 | 286 | /** 287 | * Retorna los values para el doQuery. 288 | * 289 | * @param array $array 290 | * 291 | * @return array 292 | */ 293 | protected static function getValues(array $array) 294 | { 295 | return isset($array[0]) ? 296 | (is_array($array[0]) ? $array[0] : [$array[0]]) : $array; 297 | } 298 | 299 | /** 300 | * Obtener todas las coincidencias por el campo indicado. 301 | * 302 | * @param string $field campo 303 | * @param string $value valor 304 | * @param array $params 305 | * order: criterio de ordenamiento 306 | * fields: lista de campos 307 | * join: joins de tablas 308 | * group: agrupar campos 309 | * having: condiciones de grupo 310 | * limit: valor limit 311 | * offset: valor offset 312 | * 313 | * @return \PDOStatement 314 | */ 315 | public static function allBy($field, $value, $params = []) 316 | { 317 | $field = self::sqlItemSanitize($field); 318 | $params['where'] = "$field = ?"; 319 | 320 | return self::all($params, $value); 321 | } 322 | 323 | /** 324 | * Cuenta los registros que coincidan con las condiciones indicadas. 325 | * 326 | * @param string $where condiciones 327 | * @param array $values valores 328 | * 329 | * @return int 330 | */ 331 | public static function count(string $where = '', array $values = []) 332 | { 333 | $source = static::getSource(); 334 | $sql = QueryGenerator::count($source, $where); 335 | 336 | $sth = static::query($sql, $values); 337 | 338 | return $sth->fetch()->count; 339 | } 340 | 341 | /** 342 | * Paginar. 343 | * 344 | * @param array $params 345 | * @param int $page numero de pagina 346 | * @param int $perPage cantidad de items por pagina 347 | * @param array $values valores 348 | * 349 | * @return Paginator 350 | */ 351 | public static function paginate(array $params, int $page, int $perPage, $values = null) 352 | { 353 | unset($params['limit'], $params['offset']); 354 | $sql = QueryGenerator::select(static::getSource(), static::getDriver(), $params); 355 | 356 | // Valores para consulta 357 | if ($values !== null && !\is_array($values)) { 358 | $values = \array_slice(func_get_args(), 3); 359 | } 360 | 361 | return new Paginator(static::class, $sql, $page, $perPage, $values); 362 | } 363 | 364 | /** 365 | * Obtiene todos los registros de la consulta sql. 366 | * 367 | * @param string $sql 368 | * @param string | array $values 369 | * 370 | * @return array 371 | */ 372 | public static function allBySql($sql, $values = null) 373 | { 374 | if (!is_array($values)) { 375 | $values = \array_slice(\func_get_args(), 1); 376 | } 377 | 378 | return parent::all($sql, $values); 379 | } 380 | 381 | /** 382 | * Obtiene el primer registro de la consulta sql. 383 | * 384 | * @param string $sql 385 | * @param string | array $values 386 | * 387 | * @return array 388 | */ 389 | public static function firstBySql($sql, $values = null) 390 | { 391 | if (!is_array($values)) { 392 | $values = \array_slice(\func_get_args(), 1); 393 | } 394 | 395 | return parent::first($sql, $values); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /Autoloader.php: -------------------------------------------------------------------------------- 1 | dump($data); 64 | } 65 | 66 | /** 67 | * Get the Primary Key value for the object 68 | * @return string 69 | */ 70 | public function pk(): string 71 | { 72 | return $this->{static::$pk}; 73 | } 74 | 75 | /** 76 | * Cargar datos al objeto. 77 | * 78 | * @param array $data 79 | */ 80 | public function dump(array $data = []): void 81 | { 82 | foreach ($data as $k => $v) { 83 | $this->$k = $v; 84 | } 85 | } 86 | 87 | /** 88 | * Listado de los campos. 89 | * 90 | * @return string[] 91 | */ 92 | public function getFields(): array 93 | { 94 | return \array_keys(\get_object_vars($this)); 95 | } 96 | 97 | /** 98 | * Alias de los campos. 99 | * 100 | * @return string[] 101 | */ 102 | public function getAlias(): array 103 | { 104 | //$humanize = function () 105 | return \array_map('\ucwords', $this->getFields()); 106 | } 107 | 108 | /** 109 | * Verifica que PK este seteado. 110 | * 111 | * @return bool 112 | */ 113 | protected function hasPK(): bool 114 | { 115 | return isset($this->{static::$pk}); 116 | } 117 | 118 | /** 119 | * Get the name of the primary key 120 | * 121 | * @return string 122 | */ 123 | public static function getPK(): string 124 | { 125 | return static::$pk ?? static::$pk = self::metadata()->getPK(); 126 | } 127 | 128 | /** 129 | * Obtiene nombre de tabla en la bd. 130 | * 131 | * @return string smallcase del nombre de la clase 132 | */ 133 | public static function getTable(): string 134 | { 135 | if (static::$table) { 136 | return static::$table; 137 | } 138 | 139 | $split = \explode('\\', static::class); 140 | $table = \preg_replace('/[A-Z]/', '_$0', \lcfirst(\end($split))); 141 | 142 | return static::$table = \strtolower($table); 143 | } 144 | 145 | /** 146 | * Obtiene el schema al que pertenece. 147 | * 148 | * @return string 149 | */ 150 | public static function getSchema(): string 151 | { 152 | return ''; 153 | } 154 | 155 | /** 156 | * Obtiene la combinación de esquema y tabla. 157 | * 158 | * @return string 159 | */ 160 | public static function getSource(): string 161 | { 162 | $source = static::getTable(); 163 | if ($schema = static::getSchema()) { 164 | $source = "$schema.$source"; 165 | } 166 | 167 | return $source; 168 | } 169 | 170 | /** 171 | * Obtiene la conexión que se utilizará (contenidas en databases.php). 172 | * 173 | * @return string 174 | */ 175 | public static function getDatabase(): string 176 | { 177 | return static::$database; 178 | } 179 | 180 | /** 181 | * Obtiene metadatos. 182 | * 183 | * @return Metadata\Metadata 184 | */ 185 | public static function metadata(): Metadata\Metadata 186 | { 187 | return Metadata\Metadata::get( 188 | static::getDatabase(), 189 | static::getTable(), 190 | static::getSchema() 191 | ); 192 | } 193 | 194 | /** 195 | * Obtiene manejador de conexion a la base de datos. 196 | * 197 | * @return \PDO 198 | */ 199 | protected static function dbh(): \PDO 200 | { 201 | return Db::get(static::getDatabase()); 202 | } 203 | 204 | /** 205 | * Consulta sql preparada. 206 | * 207 | * @param string $sql 208 | * 209 | * @throws \PDOException 210 | * @return \PDOStatement 211 | */ 212 | public static function prepare(string $sql): PDOStatement 213 | { 214 | $sth = self::dbh()->prepare($sql); 215 | $sth->setFetchMode(\PDO::FETCH_CLASS, static::class); 216 | 217 | return $sth; 218 | } 219 | 220 | /** 221 | * Retorna el último ID insertado. 222 | * 223 | * @return string 224 | */ 225 | public static function lastInsertId(): string 226 | { 227 | return self::dbh()->lastInsertId(); 228 | } 229 | 230 | /** 231 | * Consulta sql. 232 | * 233 | * @param string $sql 234 | * 235 | * @throws \PDOException 236 | * @return \PDOStatement 237 | */ 238 | public static function sql(string $sql): PDOStatement 239 | { 240 | return self::dbh()->query($sql, \PDO::FETCH_CLASS, static::class); 241 | } 242 | 243 | /** 244 | * Ejecuta consulta sql. 245 | * 246 | * @param string $sql 247 | * @param array $values valores 248 | * 249 | * @throws PDOException 250 | * @return bool|PDOStatement 251 | */ 252 | public static function query(string $sql, array $values = []) 253 | { 254 | if (empty($values)) { 255 | return self::sql($sql); 256 | } 257 | 258 | $sth = self::prepare($sql); 259 | 260 | return $sth->execute($values) ? $sth : \false; 261 | } 262 | 263 | /** 264 | * Verifica si existe el registro. 265 | * 266 | * @param string $pk valor para clave primaria 267 | * @return bool 268 | */ 269 | public static function exists($pk): bool 270 | { 271 | $source = static::getSource(); 272 | $pkField = static::getPK(); 273 | 274 | return self::query("SELECT COUNT(*) AS count FROM $source WHERE $pkField = ?", [$pk])->fetch()->count > 0; 275 | } 276 | 277 | /** 278 | * Paginar consulta sql. 279 | * 280 | * @param string $sql consulta select sql 281 | * @param int $page numero de pagina 282 | * @param int $perPage cantidad de items por pagina 283 | * @param array $values valores 284 | * @return Paginator 285 | */ 286 | public static function paginateQuery(string $sql, int $page, int $perPage, array $values = []): Paginator 287 | { 288 | return new Paginator(static::class, $sql, $page, $perPage, $values); 289 | } 290 | 291 | /** 292 | * Devuelve el nombre del drive PDO utilizado. 293 | * 294 | * @return string 295 | */ 296 | public static function getDriver(): string 297 | { 298 | return self::dbh()->getAttribute(\PDO::ATTR_DRIVER_NAME); 299 | } 300 | 301 | /** 302 | * Comienza una trasacción. 303 | * 304 | * @throws PDOException If there is already a transaction started or the driver does not support transactions 305 | * @return bool 306 | */ 307 | public static function begin(): bool 308 | { 309 | return self::dbh()->beginTransaction(); 310 | } 311 | 312 | /** 313 | * Da marcha atrás a una trasacción. 314 | * 315 | * @throws PDOException if there is no active transaction. 316 | * @return bool 317 | */ 318 | public static function rollback(): bool 319 | { 320 | return self::dbh()->rollBack(); 321 | } 322 | 323 | /** 324 | * Realiza el commit de una trasacción. 325 | * 326 | * @throws \PDOException if there is no active transaction. 327 | * @return bool 328 | */ 329 | public static function commit(): bool 330 | { 331 | return self::dbh()->commit(); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /Db.php: -------------------------------------------------------------------------------- 1 | getMessage(); 70 | throw new \RuntimeException("No se pudo realizar la conexión con '{$config['dsn']}'. {$message}"); 71 | } 72 | } 73 | 74 | /** 75 | * Obtiene manejador de conexión a la base de datos. 76 | * 77 | * @param string $database base de datos a conectar 78 | * 79 | * @throws \RuntimeException 80 | * @return array 81 | */ 82 | private static function getConfig(string $database): array 83 | { 84 | if (empty(self::$config)) { 85 | // Leer la configuración de conexión 86 | self::$config = require \APP_PATH.'config/databases.php'; 87 | } 88 | if ( ! isset(self::$config[$database])) { 89 | throw new \RuntimeException("No existen datos de conexión para la bd '$database' en ".\APP_PATH.'config/databases.php'); 90 | } 91 | 92 | // Envia y carga los valores por defecto para la conexión, si no existen 93 | 94 | return self::$config[$database] + [ 95 | 'dns' => \null, 96 | 'username' => \null, 97 | 'password' => \null, 98 | 'params' => [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION] 99 | ]; 100 | } 101 | 102 | /** 103 | * Permite agregar una base de datos sin leer del archivo de configuracion. 104 | * 105 | * @param array $value Valores de la configuración 106 | */ 107 | public static function setConfig(array $value) 108 | { 109 | self::$config = [] + self::$config + $value; //TODO retornar PDO 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2005 - 2022, KumbiaPHP Team www.kumbiaphp.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /LiteRecord.php: -------------------------------------------------------------------------------- 1 | $callback(); 49 | } 50 | } 51 | 52 | /** 53 | * Crear registro. 54 | * 55 | * @param array $data 56 | * 57 | * @throws \PDOException 58 | * @return bool 59 | */ 60 | public function create(array $data = []): bool 61 | { 62 | $this->dump($data); 63 | 64 | // Callback antes de crear 65 | if ($this->callback('_beforeCreate') === \false) { 66 | return \false; 67 | } 68 | 69 | $sql = QueryGenerator::insert($this, $data); 70 | 71 | if ( ! self::prepare($sql)->execute($data)) { 72 | return \false; 73 | } 74 | 75 | // Verifica si la PK es autogenerada 76 | $pk = static::$pk; 77 | if ( ! isset($this->$pk)) { 78 | $this->$pk = QueryGenerator::query( 79 | static::getDriver(), 80 | 'last_insert_id', 81 | self::dbh(), 82 | $pk, 83 | static::getTable(), 84 | static::getSchema() 85 | ); 86 | } 87 | // Callback despues de crear 88 | $this->callback('_afterCreate'); 89 | 90 | return \true; 91 | } 92 | 93 | /** 94 | * Actualizar registro. 95 | * 96 | * @param array $data 97 | * 98 | * @throws \KumbiaException 99 | * @return bool 100 | */ 101 | public function update(array $data = []): bool 102 | { 103 | $this->dump($data); 104 | // Callback antes de actualizar 105 | if ($this->callback('_beforeUpdate') === \false) { 106 | return \false; 107 | } 108 | 109 | $values = []; 110 | $sql = QueryGenerator::update($this, $values); 111 | 112 | if ( ! self::prepare($sql)->execute($values)) { 113 | return \false; 114 | } 115 | // Callback despues de actualizar 116 | $this->callback('_afterUpdate'); 117 | 118 | return \true; 119 | } 120 | 121 | /** 122 | * Guardar registro. 123 | * 124 | * @param array $data 125 | * 126 | * @return bool 127 | */ 128 | public function save(array $data = []): bool 129 | { 130 | $this->dump($data); 131 | 132 | if ($this->callback('_beforeSave') === \false) { 133 | return \false; 134 | } 135 | 136 | $method = $this->saveMethod(); 137 | $result = $this->$method(); 138 | 139 | if ( ! $result) { 140 | return \false; 141 | } 142 | 143 | $this->callback('_afterSave'); 144 | 145 | return \true; 146 | } 147 | 148 | /** 149 | * Retorna el nombre del metodo a llamar durante un save (create o update). 150 | * 151 | * @return string 152 | */ 153 | protected function saveMethod(): string 154 | { 155 | return $this->hasPK() ? 'update' : 'create'; 156 | } 157 | 158 | /** 159 | * Eliminar registro por pk. 160 | * 161 | * @param string $pk valor para clave primaria 162 | * 163 | * @return bool 164 | */ 165 | public static function delete($pk): bool 166 | { 167 | $source = static::getSource(); 168 | $pkField = static::$pk; 169 | // use pdo->execute() 170 | return static::query("DELETE FROM $source WHERE $pkField = ?", [$pk])->rowCount() > 0; 171 | } 172 | 173 | /** 174 | * Buscar por clave primaria. 175 | * 176 | * @param string $pk valor para clave primaria 177 | * @param string $fields campos que se desean obtener separados por coma 178 | * 179 | * @return self|false 180 | */ 181 | public static function get(string $pk, string $fields = '*') 182 | { 183 | $sql = "SELECT $fields FROM ".static::getSource().' WHERE '.static::$pk.' = ?'; 184 | 185 | return static::query($sql, [$pk])->fetch(); 186 | } 187 | 188 | /** 189 | * Obtiene todos los registros de la consulta sql. 190 | * 191 | * @param string $sql 192 | * @param array $values 193 | * 194 | * @return static[] 195 | */ 196 | public static function all(string $sql = '', array $values = []): array 197 | { 198 | if ( ! $sql) { 199 | $sql = 'SELECT * FROM '.static::getSource(); 200 | } 201 | 202 | return static::query($sql, $values)->fetchAll(); 203 | } 204 | 205 | /** 206 | * Obtiene el primer registro de la consulta sql. 207 | * 208 | * @param string $sql 209 | * @param array $values 210 | * 211 | * @return static|false 212 | */ 213 | public static function first(string $sql, array $values = [])//: static in php 8 214 | { 215 | return static::query($sql, $values)->fetch(); 216 | } 217 | 218 | /** 219 | * Filtra las consultas. 220 | * 221 | * @param string $sql 222 | * @param array $values 223 | * 224 | * @return array 225 | */ 226 | public static function filter(string $sql, array $values = []): array 227 | { 228 | $sql = "SELECT * FROM ".static::getSource()." $sql"; 229 | 230 | return static::all($sql, $values); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Metadata/Metadata.php: -------------------------------------------------------------------------------- 1 | get($key, 'ActiveRecord.Metadata'))) { 102 | return self::$instances[$key]; 103 | } 104 | 105 | $pdo = Db::get($database); 106 | 107 | $driverClass = __NAMESPACE__."\\".\ucfirst($pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)).'Metadata'; 108 | 109 | self::$instances[$key] = new $driverClass($pdo, $table, $schema); 110 | 111 | // Cachea los metadatos 112 | if (\PRODUCTION) { 113 | \Cache::driver()->save( 114 | \serialize(self::$instances[$key]), 115 | \Config::get('config.application.metadata_lifetime'), 116 | $key, 117 | 'ActiveRecord.Metadata' 118 | ); 119 | } 120 | 121 | return self::$instances[$key]; 122 | } 123 | 124 | /** 125 | * Constructor. 126 | * 127 | * @param \PDO $pdo base de datos 128 | * @param string $table tabla 129 | * @param string $schema squema 130 | */ 131 | private function __construct(\PDO $pdo, string $table, string $schema = '') 132 | { 133 | $this->fields = $this->queryFields($pdo, $table, $schema); 134 | $this->fieldsList = \array_keys($this->fields); 135 | } 136 | 137 | /** 138 | * Permite el filtrado de columna en PK, por Defecto y Autogenerado. 139 | * 140 | * @param array $meta información de la columna 141 | * @param string $field nombre de la columna 142 | */ 143 | protected function filterColumn(array $meta, string $field): void 144 | { 145 | if ($meta['Key'] === 'PRI') { 146 | $this->pk = $field; 147 | } 148 | if ($meta['Default']) { 149 | $this->withDefault[] = $field; 150 | } 151 | if ($meta['Auto']) { 152 | $this->autoFields[] = $field; 153 | } 154 | } 155 | 156 | /** 157 | * Consultar los campos de la tabla en la base de datos. 158 | * 159 | * @param \PDO $pdo base de datos 160 | * @param string $table tabla 161 | * @param string $schema squema 162 | * 163 | * @return array 164 | */ 165 | abstract protected function queryFields(\PDO $pdo, string $table, string $schema = ''): array; 166 | 167 | /** 168 | * Obtiene la descripción de los campos. 169 | * 170 | * @return string[] 171 | */ 172 | public function getFields(): array 173 | { 174 | return $this->fields; 175 | } 176 | 177 | /** 178 | * Obtiene la lista de campos. 179 | * 180 | * @return string[] 181 | */ 182 | public function getFieldsList(): array 183 | { 184 | return $this->fieldsList; 185 | } 186 | 187 | /** 188 | * Obtiene la clave primaria. 189 | * 190 | * @return string 191 | */ 192 | public function getPK(): string 193 | { 194 | return $this->pk; 195 | } 196 | 197 | /** 198 | * Obtiene los campos con valor predeterminado. 199 | * 200 | * @return string[] 201 | */ 202 | public function getWithDefault(): array 203 | { 204 | return $this->withDefault; 205 | } 206 | 207 | /** 208 | * Obtiene los campos con valor generado automatico. 209 | * 210 | * @return string[] 211 | */ 212 | public function getAutoFields(): array 213 | { 214 | return $this->autoFields; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Metadata/MysqlMetadata.php: -------------------------------------------------------------------------------- 1 | query($sql, \PDO::FETCH_OBJ); 43 | 44 | $fields = []; 45 | // TODO mejorar este código 46 | while ($value = $describe->fetch()) { 47 | $fields[$value->Field] = [ 48 | 'Type' => $value->Type, 49 | 'Null' => $value->Null !== 'NO', 50 | 'Key' => $value->Key, 51 | 'Default' => $value->Default != '', 52 | 'Auto' => $value->Extra === 'auto_increment' 53 | ]; 54 | $this->filterColumn($fields[$value->Field], $value->Field); 55 | } 56 | 57 | return $fields; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Metadata/PgsqlMetadata.php: -------------------------------------------------------------------------------- 1 | query( 43 | "SELECT 44 | c.column_name AS field, 45 | c.udt_name AS type, 46 | tc.constraint_type AS key, 47 | c.column_default AS default, 48 | c.is_nullable AS null 49 | FROM information_schema.columns c 50 | LEFT OUTER JOIN information_schema.key_column_usage cu ON ( 51 | cu.column_name = c.column_name AND cu.table_name = c.table_name AND ( 52 | SELECT COUNT(*) FROM information_schema.key_column_usage 53 | WHERE constraint_name = cu.constraint_name 54 | ) = 1) 55 | LEFT OUTER JOIN information_schema.table_constraints tc 56 | ON (cu.constraint_name = tc.constraint_name AND tc.constraint_type 57 | IN ('PRIMARY KEY', 'UNIQUE')) 58 | WHERE c.table_name = '$table' AND c.table_schema = '$schema' 59 | ;", 60 | 61 | \PDO::FETCH_OBJ 62 | ); 63 | 64 | return $this->describe($describe->fetchAll()); 65 | } 66 | 67 | /** 68 | * Genera la metadata. 69 | * 70 | * @param array $describe 71 | * 72 | * @return array 73 | */ 74 | private function describe(array $describe): array 75 | { 76 | $fields = []; 77 | // TODO mejorar este código 78 | foreach ($describe as $value) { 79 | $fields[$value->field] = [ 80 | 'Type' => $value->type, 81 | 'Null' => $value->null !== 'NO', 82 | 'Default' => $value->default != '', 83 | 'Key' => \substr($value->key, 0, 3), 84 | 'Auto' => (bool) \preg_match('/^nextval\(/', $value->default) 85 | ]; 86 | $this->filterColumn($fields[$value->field], $value->field); 87 | } 88 | 89 | return $fields; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Metadata/SqliteMetadata.php: -------------------------------------------------------------------------------- 1 | query("PRAGMA table_info($table)", \PDO::FETCH_OBJ); 42 | //var_dump($results); die(); 43 | $fields = []; 44 | foreach ($describe as $value) { 45 | $fields[$value->name] = [ 46 | 'Type' => \strtolower(\str_replace(' ', '', $value->type)), 47 | 'Null' => $value->notnull == 0, 48 | 'Default' => (bool) $value->dflt_value, 49 | 'Key' => $value->pk == 1 ? 'PRI' : '', 50 | 'Auto' => (\strtolower($value->type) == 'int' && $value->pk == 1) // using rowid 51 | ]; 52 | $this->filterColumn($fields[$value->name], $value->name); 53 | } 54 | 55 | return $fields; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Metadata/SqlsrvMetadata.php: -------------------------------------------------------------------------------- 1 | query( 43 | "SELECT 44 | c.name AS field_name, 45 | c.is_identity AS is_auto_increment, 46 | c.is_nullable, 47 | object_definition(c.default_object_id) AS default_value, 48 | t.name AS type_field 49 | FROM sys.columns c join sys.types t 50 | ON c.system_type_id = t.user_type_id 51 | WHERE object_id = object_id('$schema.$table')" 52 | ); 53 | 54 | $pk = self::pk($pdo, $table); 55 | 56 | return self::describe($describe, $pk); 57 | } 58 | 59 | /** 60 | * Optiene el PK 61 | * 62 | * @param \PDO $pdo base de datos 63 | * @param string $table tabla 64 | * @return string 65 | */ 66 | private static function pk(\PDO $pdo, string $table): string 67 | { 68 | $pk = $pdo->query("exec sp_pkeys @table_name='$table'"); 69 | $pk = $pk->fetch(\PDO::FETCH_OBJ); 70 | 71 | return $pk->COLUMN_NAME; 72 | } 73 | 74 | /** 75 | * Genera la metadata 76 | * 77 | * @param \PDOStatement $describe SQL result 78 | * @param string $pk Primary key 79 | * @return array 80 | */ 81 | protected function describe(\PDOStatement $describe, string $pk): array 82 | { 83 | // TODO Mejorar 84 | $fields = []; 85 | while ($value = $describe->fetch()) { 86 | $fields[$value->field_name] = [ 87 | 'Type' => $value->type_field, 88 | 'Null' => ($value->is_nullable), 89 | 'Key' => ($value->field_name === $pk) ? 'PRI' : '', 90 | 'Default' => \str_replace("''", "'", \trim($value->default_value, "(')")), 91 | 'Auto' => ($value->is_auto_increment) 92 | ]; 93 | $this->filterColumn($fields[$value->field_name], $value->field_name); 94 | } 95 | 96 | return $fields; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Paginator.php: -------------------------------------------------------------------------------- 1 | perPage = $perPage; 96 | $this->page = $page; 97 | 98 | /*validacion*/ 99 | $this->validPage(); 100 | 101 | $this->model = $model; 102 | 103 | // Valores para consulta 104 | $this->values = $values; 105 | 106 | $this->count = $this->countQuery($model, $sql); 107 | $this->totalPages = (int) \max(1, \ceil($this->count / $this->perPage)); 108 | $this->validCurrent(); 109 | // Establece el limit y offset 110 | $this->sql = QueryGenerator::query($model::getDriver(), 'limit', $sql, $perPage, ($page - 1) * $perPage); 111 | $this->items = $model::query($this->sql, $this->values)->fetchAll(); 112 | } 113 | 114 | /** 115 | * Permite que al usar json_encode() con una instacia de Paginator funcione correctamente 116 | * retornando los items del paginador. 117 | */ 118 | public function jsonSerialize() 119 | { 120 | return $this->items; 121 | } 122 | 123 | /** 124 | * Verifica que la pagina sea válida. 125 | */ 126 | private function validPage(): void 127 | { 128 | //Si la página o por página es menor de 1 (0 o negativo) 129 | if ($this->page < 1 || $this->perPage < 1) { 130 | throw new \RangeException("La página $this->page no existe", 404); 131 | } 132 | } 133 | 134 | /** 135 | * Valida que la página actual. 136 | */ 137 | private function validCurrent(): void 138 | { 139 | if ($this->page > $this->totalPages) { 140 | throw new \RangeException("La página $this->page no existe", 404); 141 | } 142 | } 143 | 144 | /** 145 | * (non-PHPdoc). 146 | * 147 | * @see IteratorAggregate::getIterator() 148 | */ 149 | public function getIterator(): \ArrayIterator 150 | { 151 | return new \ArrayIterator($this->items); 152 | } 153 | 154 | /** 155 | * Cuenta el número de resultados totales. 156 | * 157 | * @param string $model 158 | * @param string $sql 159 | * 160 | * @return int total de resultados 161 | */ 162 | protected function countQuery(string $model, string $sql): int 163 | { 164 | $query = $model::query("SELECT COUNT(*) AS count FROM ($sql) AS t", $this->values)->fetch(); 165 | 166 | return (int) $query->count; 167 | } 168 | 169 | /** 170 | * Total de items. 171 | * 172 | * @return int 173 | */ 174 | public function totalItems(): int 175 | { 176 | return $this->count; 177 | } 178 | 179 | /** 180 | * Total de páginas. 181 | * 182 | * @return int 183 | */ 184 | public function totalPages(): int 185 | { 186 | return $this->totalPages; 187 | } 188 | 189 | /** 190 | * Calcula el valor de la próxima página. 191 | * 192 | * @return int 193 | */ 194 | public function nextPage(): int 195 | { 196 | return $this->totalPages > $this->page ? $this->page + 1 : 0; 197 | } 198 | 199 | /** 200 | * Calcula el valor de la página anterior. 201 | * 202 | * @return int 203 | */ 204 | public function prevPage(): int 205 | { 206 | return $this->page > 1 ? $this->page - 1 : 0; 207 | } 208 | 209 | /** 210 | * Items devueltos. 211 | * 212 | * @see Countable::countable() 213 | * 214 | * @return int 215 | */ 216 | public function count(): int 217 | { 218 | return count($this->items); 219 | } 220 | 221 | /** 222 | * Página actual de paginador. 223 | * 224 | * @return int 225 | */ 226 | public function page(): int 227 | { 228 | return $this->page; 229 | } 230 | 231 | /** 232 | * Campos del objeto. 233 | * 234 | * @return string[] 235 | */ 236 | public function getFields(): array 237 | { 238 | return $this->items[0]->getFields(); 239 | } 240 | 241 | /** 242 | * Alias de Campos del objeto. 243 | * 244 | * @return string[] 245 | */ 246 | public function getAlias(): array 247 | { 248 | return $this->items[0]->getAlias(); 249 | } 250 | 251 | /** 252 | * Cantidad de items por página configurado. 253 | * 254 | * @return int 255 | */ 256 | public function getPerPage(): int 257 | { 258 | return $this->perPage; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Query/mysql_last_insert_id.php: -------------------------------------------------------------------------------- 1 | lastInsertId(); 34 | } 35 | -------------------------------------------------------------------------------- /Query/mysql_limit.php: -------------------------------------------------------------------------------- 1 | lastInsertId($seq); 39 | } 40 | -------------------------------------------------------------------------------- /Query/pgsql_limit.php: -------------------------------------------------------------------------------- 1 | lastInsertId(); 34 | } 35 | -------------------------------------------------------------------------------- /Query/sqlite_limit.php: -------------------------------------------------------------------------------- 1 | '*', 47 | 'join' => '', 48 | 'limit' => \null, 49 | 'offset' => \null, 50 | 'where' => \null, 51 | 'group' => \null, 52 | 'having' => \null, 53 | 'order' => \null 54 | ], $params); 55 | 56 | list($where, $group, $having, $order) = static::prepareParam($params); 57 | $sql = "SELECT {$params['fields']} FROM $source {$params['join']} $where $group $having $order"; 58 | 59 | if ( ! \is_null($params['limit']) || ! \is_null($params['offset'])) { 60 | $sql = self::query($type, 'limit', $sql, $params['limit'], $params['offset']); 61 | } 62 | 63 | return $sql; 64 | } 65 | 66 | /** 67 | * Permite construir el WHERE, GROUP BY, HAVING y ORDER BY de una consulta SQL 68 | * en base a los parámetros $params. 69 | * 70 | * @param array $params 71 | * @return string[] 72 | */ 73 | protected static function prepareParam(array $params) 74 | { 75 | return [ 76 | static::where($params['where']), 77 | static::group($params['group']), 78 | static::having($params['having']), 79 | static::order($params['order']) 80 | ]; 81 | } 82 | 83 | /** 84 | * Genera una sentencia where. 85 | * 86 | * @param string $where 87 | * @return string 88 | */ 89 | protected static function where($where) 90 | { 91 | return empty($where) ? '' : "WHERE $where"; 92 | } 93 | 94 | /** 95 | * Genera una sentencia GROUP. 96 | * 97 | * @param string $group 98 | * @return string 99 | */ 100 | protected static function group($group) 101 | { 102 | return empty($group) ? '' : "GROUP BY $group"; 103 | } 104 | 105 | /** 106 | * Genera una sentencia HAVING. 107 | * 108 | * @param string $having 109 | * @return string 110 | */ 111 | protected static function having($having) 112 | { 113 | return empty($having) ? '' : "HAVING $having"; 114 | } 115 | 116 | /** 117 | * Genera una sentencia ORDER BY. 118 | * 119 | * @param string $order 120 | * @return string 121 | */ 122 | protected static function order($order) 123 | { 124 | return empty($order) ? '' : "ORDER BY $order"; 125 | } 126 | 127 | /** 128 | * Construye una consulta INSERT. 129 | * 130 | * @param LiteRecord $model Modelo a actualizar 131 | * @param array $data Datos pasados a la consulta preparada 132 | * @return string 133 | */ 134 | public static function insert(LiteRecord $model, array &$data) 135 | { 136 | $meta = $model::metadata(); 137 | $data = []; 138 | $columns = []; 139 | $values = []; 140 | 141 | // Preparar consulta 142 | foreach ($meta->getFieldsList() as $field) { 143 | $columns[] = $field; 144 | static::insertField($field, $model, $data, $values); 145 | } 146 | $columns = \implode(',', $columns); 147 | $values = \implode(',', $values); 148 | $source = $model::getSource(); 149 | 150 | return "INSERT INTO $source ($columns) VALUES ($values)"; 151 | } 152 | 153 | /** 154 | * Agrega un campo a para generar una consulta preparada para un INSERT. 155 | * 156 | * @param string $field Nombre del campo 157 | * @param LiteRecord $model valor del campo 158 | * @param array $data array de datos 159 | * @param array $values array de valores 160 | * @return void 161 | */ 162 | protected static function insertField($field, LiteRecord $model, array &$data, array &$values) 163 | { 164 | //$meta = $model::metadata(); 165 | if (self::haveValue($model, $field)) { 166 | $data[":$field"] = $model->$field; 167 | $values[] = ":$field"; 168 | } else { 169 | //if (!\in_array($field, $meta->getWithDefault()) && !\in_array($field, $meta->getAutoFields())) { 170 | $values[] = 'NULL'; 171 | } 172 | } 173 | 174 | /** 175 | * Permite conocer si la columna debe definirse como nula. 176 | * 177 | * @param LiteRecord $model 178 | * @param string $field 179 | * @return bool 180 | */ 181 | protected static function haveValue(LiteRecord $model, $field) 182 | { 183 | return isset($model->$field) && $model->$field !== ''; 184 | } 185 | 186 | /** 187 | * Construye una consulta UPDATE. 188 | * 189 | * @param LiteRecord $model Modelo a actualizar 190 | * @param array $data Datos pasados a la consulta preparada 191 | * @return string 192 | */ 193 | public static function update(LiteRecord $model, array &$data) 194 | { 195 | $set = []; 196 | $pk = $model::getPK(); 197 | /*elimina la clave primaria*/ 198 | $list = \array_diff($model::metadata()->getFieldsList(), [$pk]); 199 | foreach ($list as $field) { 200 | $value = isset($model->$field) ? $model->$field : \null; 201 | static::updateField($field, $value, $data, $set); 202 | } 203 | $set = \implode(', ', $set); 204 | $source = $model::getSource(); 205 | $data[":$pk"] = $model->$pk; 206 | 207 | return "UPDATE $source SET $set WHERE $pk = :$pk"; 208 | } 209 | 210 | /** 211 | * Generate SQL for DELETE sentence. 212 | * 213 | * @param string $source source 214 | * @param string $where condition 215 | * @return string SQL 216 | */ 217 | public static function deleteAll($source, $where) 218 | { 219 | return "DELETE FROM $source ".static::where($where); 220 | } 221 | 222 | /** 223 | * Generate SQL for COUNT sentence. 224 | * 225 | * @param string $source source 226 | * @param string $where condition 227 | * @return string SQL 228 | */ 229 | public static function count($source, $where) 230 | { 231 | return "SELECT COUNT(*) AS count FROM $source ".static::where($where); 232 | } 233 | 234 | /** 235 | * Agrega un campo a para generar una consulta preparada para un UPDATE. 236 | * 237 | * @param string $field Nombre del campo 238 | * @param mixed $value valor 239 | * @param array $data array de datos 240 | * @param array $set array de valores 241 | * @return void 242 | */ 243 | protected static function updateField($field, $value, array &$data, array &$set) 244 | { 245 | if ( ! empty($value)) { 246 | $data[":$field"] = $value; 247 | $set[] = "$field = :$field"; 248 | } else { 249 | $set[] = "$field = NULL"; 250 | } 251 | } 252 | 253 | /** 254 | * Construye una consulta UPDATE. 255 | * 256 | * @todo ¿Hay que escapar los nombres de los campos? 257 | * @param string $model nombre del modelo a actualizar 258 | * @param array $fields campos a actualizar 259 | * @param array $data Datos pasados a la consulta preparada 260 | * @param string|null $where 261 | * @return string 262 | */ 263 | public static function updateAll($model, array $fields, array &$data, $where) 264 | { 265 | $set = []; 266 | //$pk = $model::getPK(); 267 | /*elimina la clave primaria*/ 268 | foreach ($fields as $field => $value) { 269 | static::updateField($field, $value, $data, $set); 270 | } 271 | $set = \implode(', ', $set); 272 | $source = $model::getSource(); 273 | $where = static::where($where); 274 | 275 | return "UPDATE $source SET $set $where"; 276 | } 277 | 278 | /** 279 | * Ejecuta una consulta. 280 | * 281 | * @thow KumbiaException 282 | * @param string $type tipo de driver 283 | * @param string $query_function nombre de funcion 284 | * @return mixed 285 | */ 286 | public static function query($type, $query_function) 287 | { 288 | $query_function = "{$type}_{$query_function}"; 289 | 290 | require_once __DIR__."/Query/{$query_function}.php"; 291 | 292 | $args = \array_slice(\func_get_args(), 2); 293 | 294 | return \call_user_func_array(__NAMESPACE__."\\Query\\$query_function", $args); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | ![KumbiaPHP](http://proto.kumbiaphp.com/img/kumbiaphp.png) 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/badges/quality-score.png?s=f7230602070a9e9605d46544197bcdac46166612)](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/badges/coverage.png?s=58997633701e84050c0ebd5334f3eb1bb8b7ad42)](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/) 5 | [![Build Status](https://travis-ci.org/KumbiaPHP/ActiveRecord.png?branch=master)](https://travis-ci.org/KumbiaPHP/ActiveRecord) 6 | [![Code Climate](https://codeclimate.com/github/KumbiaPHP/ActiveRecord/badges/gpa.svg)](https://codeclimate.com/github/KumbiaPHP/ActiveRecord) 7 | 8 | ENGLISH - [SPANISH](/README.md) 9 | 10 | # ActiveRecord 11 | 12 | New ActiveRecord in development 13 | 14 | Don't use in production 15 | 16 | ## Install with composer in KumbiaPHP 17 | 18 | Requires KumbiaPHP > 0.9RC 19 | 20 | * Create file ***composer.json*** in to project root: 21 | 22 | ```yml 23 | --project 24 | | 25 | |--vendor 26 | |--default 27 | |--core 28 | |--composer.json This is our file 29 | ``` 30 | 31 | * Add the next lines: 32 | 33 | ```json 34 | { 35 | "require": { 36 | "kumbia/activerecord" : "dev-master" 37 | } 38 | } 39 | ``` 40 | 41 | * Execute command **composer install** 42 | 43 | * Continue with steps number 2 and 3 of the next section. 44 | 45 | ## Install in KumbiaPHP 46 | 47 | Requires KumbiaPHP > 0.9RC 48 | 49 | 1. Copy folder ***lib/Kumbia*** in vendor. (vendor/Kumbia/ActiveRecord/..) 50 | 51 | 2. Copy [config_databases.php](/config_databases.php) in ***app/config/databases.php*** and set configuration 52 | 53 | 3. Add in ***app/libs/*** : [lite_record.php](#literecord) and/or [act_record.php](#actrecord) 54 | 55 | 56 | ### LiteRecord 57 | 58 | For those who prefer SQL and the advantages of an ORM it includes a mini ActiveRecord 59 | 60 | ```php 61 | data = People::all(); 135 | } 136 | 137 | public function find($id) { 138 | $this->data = People::get($id); 139 | } 140 | } 141 | ``` 142 | 143 | ### Using LiteRecord methods 144 | 145 | #### Filtering data 146 | 147 | ```php 148 | //get all as array of records 149 | $rows = People::all(); 150 | echo $row[0]->name; 151 | 152 | //get by primary key as record 153 | $row = People::get($peopleId); 154 | echo $row->name; 155 | 156 | //filter as array of records 157 | $rows = People::filter("WHERE name LIKE ?", [$peopleName]); 158 | echo $rows[0]->name; 159 | 160 | //filter by sql as record 161 | $row = People::first("SELECT * FROM people WHERE name = :name", [":name" => $peopleName]); 162 | echo $row->name; 163 | 164 | //filter by sql as array of records 165 | $rows = People::all("SELECT * FROM people WHERE hire_date >= ?", [$hireDate]); 166 | echo $rows[0]->name; 167 | ``` 168 | 169 | #### DML / Insert, update, delete 170 | ```php 171 | //adding a new record 172 | $peopleObj = new People(); 173 | $peopleObj->create([ 174 | 'name' => 'Edgard Baptista', 175 | 'job_title' => 'Accountant', 176 | 'hire_date' => date('Y-m-d'), 177 | 'active' => 1 178 | ]); //returns True or False on success or fail 179 | 180 | //adding a new record alternative 181 | //please prefer this method by simplicity. 182 | //save executes create method when primary key is missing 183 | //and update ones when it exists 184 | $peopleObj = new People(); 185 | $peopleObj->save([ 186 | 'name' => 'Edgard Baptista', 187 | 'job_title' => 'Accountant', 188 | 'hire_date' => date('Y-m-d'), 189 | 'active' => 1 190 | ]); //returns True or False on success or fail 191 | 192 | //adding a new record alternative //shorthand method 193 | //passing the data when instantiate the class 194 | $peopleObj = new People([ 195 | 'name' => 'Edgard Baptista', 196 | 'job_title' => 'Accountant', 197 | 'hire_date' => date('Y-m-d'), 198 | 'active' => 1 199 | ]); 200 | $peopleObj->save(); //returns True or False on success or fail 201 | 202 | //updating a record 203 | //first find the record to update 204 | $peopleObj = People::get($peopleId); 205 | 206 | $peopleObj->update([ 207 | 'name' => 'Edgard Baptista Jr', 208 | 'active' => 0 209 | ]); //returns True or False on success or fail 210 | 211 | //updating a record alternative 212 | //first find the record to update 213 | $peopleObj = People::get($peopleId); 214 | 215 | $peopleObj->save([ 216 | 'name' => 'Edgard Baptista Jr', 217 | 'active' => 0 218 | ]); //returns True or False on success or fail 219 | 220 | 221 | //deleting a record by primary key 222 | People::delete($peopleId); 223 | 224 | ``` 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![KumbiaPHP](https://proto.kumbiaphp.com/img/kumbiaphp.svg) 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/badges/quality-score.png?s=f7230602070a9e9605d46544197bcdac46166612)](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/badges/coverage.png?s=58997633701e84050c0ebd5334f3eb1bb8b7ad42)](https://scrutinizer-ci.com/g/KumbiaPHP/ActiveRecord/) 5 | [![Build Status](https://travis-ci.org/KumbiaPHP/ActiveRecord.png?branch=master)](https://travis-ci.org/KumbiaPHP/ActiveRecord) 6 | [![Code Climate](https://codeclimate.com/github/KumbiaPHP/ActiveRecord/badges/gpa.svg)](https://codeclimate.com/github/KumbiaPHP/ActiveRecord) 7 | 8 | ESPAÑOL - [ENGLISH](/README.en.md) 9 | 10 | # ActiveRecord 11 | 12 | Nuevo ActiveRecord en desarrollo. 13 | 14 | No usar en producción 15 | 16 | ## Instalar con composer en KumbiaPHP 17 | 18 | Necesita KumbiaPHP > 0.9RC 19 | 20 | * Crear el archivo ***composer.json*** en la raiz del proyecto: 21 | 22 | ```yml 23 | --proyecto 24 | | 25 | |--vendor 26 | |--default 27 | |--core 28 | |--composer.json Acá va nuestro archivo 29 | ``` 30 | 31 | * Añadir el siguiente código: 32 | 33 | ```json 34 | { 35 | "require": { 36 | "kumbia/activerecord" : "dev-master" 37 | } 38 | } 39 | ``` 40 | 41 | * Ejecutar el comando **composer install** 42 | 43 | * Seguir los pasos 2 y 3 de la siguiente sección. 44 | 45 | ## Instalar en KumbiaPHP 46 | 47 | Necesita KumbiaPHP > 0.9RC 48 | 49 | 1. Copiar [config/config_databases.php](config/config_databases.php) en app/config/databases.php y configurar 50 | 51 | 2. (Opcional) Añadir en app/libs/ : [lite_record.php](#literecord) y/o [act_record.php](#actrecord) 52 | 53 | 54 | ### LiteRecord 55 | 56 | Para los que prefieren SQL y las ventajas de un ORM, incluye un mini ActiveRecord 57 | 58 | ```php 59 | data = Personas::all(); 140 | } 141 | 142 | public function find($id) { 143 | $this->data = Personas::get($id); 144 | } 145 | } 146 | ``` 147 | ### Uso de métodos en LiteRecord 148 | 149 | #### Filtrar datos 150 | 151 | ```php 152 | //obtener todos los registros como array 153 | $filas = Personas::all(); 154 | echo $filas[0]->nombre; 155 | 156 | //obtener un registro por su clave primaria 157 | $fila = Personas::get($personaId); 158 | echo $fila->nombre; 159 | 160 | //obtener los registros como array según el filtro 161 | $filas = Personas::filter("WHERE nombre LIKE ?", [$nombrePersona]); 162 | echo $filas[0]->nombre; 163 | 164 | //obtener registro según sql 165 | $fila = Personas::first("SELECT * FROM personas WHERE nombre = :nombre", [":nombre" => $nombrePersona]); 166 | echo $fila->nombre; 167 | 168 | //obtener array de registros según sql 169 | $filas = Personas::all("SELECT * FROM personas WHERE fecha_contrato >= ?", [$fechaContrato]); 170 | echo $filas[0]->nombre; 171 | ``` 172 | 173 | #### DML / Crear, actualizar, borrar 174 | ```php 175 | //creando un nuevo registro 176 | $personaObj = new Personas(); 177 | $personaObj->create([ 178 | 'nombre' => 'Edgard Baptista', 179 | 'cargo' => 'Contador', 180 | 'fecha_contrato' => date('Y-m-d'), 181 | 'activo' => 1 182 | ]); //retorna True o False si hay éxito o error respectivamente 183 | 184 | //creando un nuevo registro //alternativa 185 | //por favor, prefiera este método por su simplicidad. 186 | //save ejecuta el método create cuando falta la clave primaria y 187 | //el de actualización cuando existe 188 | $personaObj = new Personas(); 189 | $personaObj->save([ 190 | 'nombre' => 'Edgard Baptista', 191 | 'cargo' => 'Contador', 192 | 'fecha_contrato' => date('Y-m-d'), 193 | 'activo' => 1 194 | ]); //retorna True o False si hay éxito o error respectivamente 195 | 196 | //creando un nuevo registro //alternativa //método abreviado 197 | //pasamos los datos cuando se instancia la clase 198 | $personaObj = new Personas([ 199 | 'nombre' => 'Edgard Baptista', 200 | 'cargo' => 'Contador', 201 | 'fecha_contrato' => date('Y-m-d'), 202 | 'activo' => 1 203 | ]); 204 | $personaObj->save(); //retorna True o False si hay éxito o error respectivamente 205 | 206 | //actualizar un registro 207 | //primero buscar el registro que se quiere actualizar 208 | $personaObj = Personas::get($personaId); 209 | 210 | $personaObj->update([ 211 | 'nombre' => 'Edgard Baptista', 212 | 'activo' => 0 213 | ]); //retorna True o False si hay éxito o error respectivamente 214 | 215 | //actualizar un registro //alternativa 216 | //primero buscar el registro que se quiere actualizar 217 | $personaObj = Personas::get($personaId); 218 | 219 | $personaObj->save([ 220 | 'nombre' => 'Edgard Baptista', 221 | 'activo' => 0 222 | ]); //retorna True o False si hay éxito o error respectivamente 223 | 224 | 225 | //borrar un registro usando su clave primaria 226 | Personas::delete($personaId); 227 | 228 | ``` 229 | -------------------------------------------------------------------------------- /To-do.md: -------------------------------------------------------------------------------- 1 | Cambios (tachado lo que ya está listo) 2 | ======= 3 | 4 | Clase Metadata 5 | -------------- 6 | ~~Para manejar toda la metadata, su cache, info de bd, alias,...~~ 7 | 8 | ~~Tendrá adapters para las diferentes bd (aunque tendriamos que probar getColumnMeta()~~ 9 | 10 | Clase Validate 11 | -------------- 12 | Clase externa para validar cualquier modelo, aunque no extienda de ActiveRecord 13 | 14 | Clase DB 15 | -------------- 16 | En el config: 17 | 18 | ~~enviar directo el dsn, y no crearlo en la clase (ya que usa siempre PDO)~~ 19 | 20 | ~~Mejor mirar el tipo de base de datos una vez conectado usando ATTR_DRIVER_NAME~~ 21 | 22 | ~~Posibilidad de pasar via config, parámetros opcionales a la conexión PDO~~ 23 | 24 | ~~usar php para app/config/databases.php~~ 25 | 26 | Base de datos por defecto 27 | ------------------- 28 | ~~Por defecto, usará default (no es necesario mirar el config.ini)~~ 29 | 30 | Es el mismo trabajo, cambiar el config.ini o el databases.ini 31 | 32 | Para cambiarla directamente en app/libs/ActRecord.php o en el modelo, en getDatabase(). 33 | 34 | Varios 35 | ------ 36 | Para sanitizar usará el quote() de PDO. Que no es necesario al usar consultas preparadas 37 | 38 | Para saber el tipo de base de datos usará: $name = $conn->getAttribute(PDO::ATTR_DRIVER_NAME); 39 | 40 | ~~Query listo para consultas preparadas de un golpe (posible opción de especificar el fetch)~~ 41 | 42 | ~~$this->query($sql, $data)~~ 43 | 44 | ~~No usar las constantes APP_PATH ni CORE PATH para desacoplar la lib~~ 45 | 46 | Clases: LiteRecord (básico sólo SQL), ActRecord (con generador de consultas, extenderá SqlRecord) y ActiveRecord (compatible con el actual, extenderá SqlRecord) 47 | 48 | ~~No usar la clase Util, para desacoplar mejor.~~ 49 | 50 | En producción usará cache de metadata indefinido, posible uso de APC si esta disponible, sino nixfile 51 | 52 | Añadir método para limpiar la cache de metadata 53 | 54 | Metadata info de la bd, version, driver, .... 55 | 56 | Métodos firstByXxxx() y allByXxxx() en LiteRecord 57 | 58 | Posibilidad de añadir prefijo al getTable() -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kumbia/activerecord", 3 | "type": "library", 4 | "description": "Fast ActiveRecord", 5 | "keywords": [ 6 | "ActiveRecord", 7 | "database", 8 | "ORM" 9 | ], 10 | "homepage": "https://github.com/KumbiaPHP/ActiveRecord", 11 | "license": "BSD-3-Clause", 12 | "support": { 13 | "issues": "https://github.com/KumbiaPHP/ActiveRecord/issues", 14 | "slack": "https://slack.kumbiaphp.com" 15 | }, 16 | "authors": [ 17 | { 18 | "name": "Joan Miquel", 19 | "email": "joanhey@kumbiaphp.com", 20 | "role": "Developer" 21 | }, 22 | { 23 | "name": "Alberto Berrotan", 24 | "email": "ashrey@kumbiaphp.com", 25 | "role": "Developer" 26 | }, 27 | { 28 | "name": "Emilio", 29 | "email": "emiliorst@kumbiaphp.com", 30 | "role": "Developer" 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.4.0", 35 | "ext-pdo": "*" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Kumbia\\ActiveRecord\\": "." 40 | } 41 | }, 42 | "config": { 43 | "optimize-autoloader": true, 44 | "sort-packages": true 45 | }, 46 | "funding": [ 47 | { 48 | "type": "Open Collective", 49 | "url": "https://opencollective.com/kumbiaphp" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /config/config_databases.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'dsn' => 'mysql:host=127.0.0.1;dbname=midatabase;charset=utf8', 9 | 'username' => 'user', 10 | 'password' => 'pass', 11 | 'params' => [ 12 | //PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', //UTF8 en PHP < 5.3.6 13 | \PDO::ATTR_PERSISTENT => \true, //conexión persistente 14 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 15 | ] 16 | ], 17 | //Conexión a sqlite ejemplo 18 | 'database2' => [ 19 | 'dsn' => 'sqlite:'.APP_PATH.'/temp/mydb.sq3', 20 | 'username' => \null, 21 | 'password' => \null, 22 | ], 23 | //Conexión a ODBC ejemplo 24 | 'database3' => [ 25 | 'dsn' => 'odbc:testdb', 26 | 'username' => \null, 27 | 'password' => \null, 28 | 'params' => [ 29 | \PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY, 30 | \PDO::ATTR_CASE => \PDO::CASE_LOWER, 31 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 32 | ] 33 | ], 34 | //Conexión a MSSQL 35 | 'database4' => [ 36 | 'dsn' => 'sqlsrv:Server=mihost;Database=midatabase;', 37 | 'username' => 'miusername', 38 | 'password' => 'mipassword' 39 | /*'params' => [ 40 | PDO::ATTR_PERSISTENT => true, //conexión persistente 41 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 42 | ]*/ 43 | ], 44 | //Conexión a Oracle 45 | 'oracle' => [ 46 | 'dsn' => 'oci:dbname=//localhost:1521/midatabase', 47 | 'username' => 'username', 48 | 'password' => 'password', 49 | 'params' => [ 50 | \PDO::ATTR_PERSISTENT => \true, //conexión persistente 51 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, 52 | \PDO::ATTR_AUTOCOMMIT => 1, 53 | \PDO::ATTR_CASE => \PDO::CASE_LOWER 54 | ] 55 | ] 56 | //Más conexiones 57 | ]; 58 | -------------------------------------------------------------------------------- /tests/ActiveRecord/DbTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('PDO', $instance); 17 | } 18 | 19 | /** 20 | * @requires extension pdo_mysql 21 | */ 22 | public function testGet() 23 | { 24 | $instance = Db::get('mysql'); 25 | 26 | $instance2 = Db::get('mysql'); 27 | 28 | $this->assertEquals($instance, $instance2); 29 | } 30 | 31 | public function testGetThatDontExistInConfig() 32 | { 33 | $this->expectException(RuntimeException::class); 34 | $this->expectExceptionMessageRegExp('/^No existen datos de conexión para la bd/'); 35 | 36 | $instance = Db::get('no_exist'); 37 | } 38 | 39 | public function testGetConfigWithoutPassword() 40 | { 41 | $this->expectException(RuntimeException::class); 42 | $this->expectExceptionMessageRegExp('/No se pudo realizar la conexión con/'); 43 | 44 | $instance = Db::get('no_password'); 45 | } 46 | 47 | public function testSetConfig() 48 | { 49 | $config = ['dynamic' => [ 50 | 'dsn' => 'pgsql:dbname=kumbia_test;host=localhost', 51 | 'username' => 'postgres', 52 | 'password' => '', 53 | 'params' => [ 54 | \PDO::ATTR_PERSISTENT => \true, //conexión persistente 55 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 56 | ] 57 | ] 58 | ]; 59 | 60 | Db::setConfig($config); 61 | $instance = Db::get('dynamic'); 62 | 63 | $this->assertInstanceOf('PDO', $instance); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/ActiveRecord/Metadata/MetadataTest.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'Type' => 'int(11)', 18 | 'Null' => false, 19 | 'Default' => false, 20 | 'Key' => 'PRI', 21 | 'Auto' => true, 22 | ], 23 | 'nombre' => [ 24 | 'Type' => 'varchar(50)', 25 | 'Null' => false, 26 | 'Default' => false, 27 | 'Key' => '', 28 | 'Auto' => false, 29 | ], 30 | 'email' => [ 31 | 'Type' => 'varchar(100)', 32 | 'Null' => false, 33 | 'Default' => false, 34 | 'Key' => '', 35 | 'Auto' => false, 36 | ], 37 | 'activo' => [ 38 | 'Type' => 'smallint(1)', 39 | 'Null' => true, 40 | 'Default' => true, 41 | 'Key' => '', 42 | 'Auto' => false 43 | ] 44 | ]; 45 | 46 | 47 | 48 | public function setUp(): void 49 | { 50 | $this->tableName = getenv('metadata_table'); 51 | $this->schemaName = getenv('metadata_schema'); 52 | } 53 | 54 | protected function getMetadata(): Metadata 55 | { 56 | return Metadata::get($this->dbName, $this->tableName, $this->schemaName); 57 | } 58 | 59 | public function testInstanceOfDriverDb() 60 | { 61 | $metadata = $this->getMetadata(); 62 | $dbDriverClass = \ucfirst($this->dbName).'Metadata'; 63 | 64 | $this->assertInstanceOf('\\Kumbia\\ActiveRecord\\Metadata\\'.$dbDriverClass, $metadata); 65 | } 66 | 67 | public function testGetPK() 68 | { 69 | $pk = $this->getMetadata()->getPK(); 70 | 71 | $this->assertEquals('id', $pk); 72 | } 73 | 74 | public function testGetWithDefault() 75 | { 76 | $withDefault = $this->getMetadata()->getWithDefault(); 77 | 78 | $this->assertEquals(['activo'], $withDefault); 79 | } 80 | 81 | 82 | public function testGetFields() 83 | { 84 | $fields = $this->getMetadata()->getFields(); 85 | 86 | $this->assertEquals($this->expectedGetFields, $fields); 87 | } 88 | 89 | public function testGetFieldsList() 90 | { 91 | $fields = $this->getMetadata()->getFieldsList(); 92 | 93 | $this->assertEquals(['id', 'nombre', 'email', 'activo'], $fields); 94 | } 95 | 96 | public function testGetAutoFields() 97 | { 98 | $fields = $this->getMetadata()->getAutoFields(); 99 | 100 | $this->assertEquals(['id'], $fields); 101 | } 102 | } 103 | //TODO add validation when don't connect to bd and don't get the metadata 104 | // now fail silently 105 | -------------------------------------------------------------------------------- /tests/ActiveRecord/Metadata/MysqlMetadataTest.php: -------------------------------------------------------------------------------- 1 | query(' 19 | CREATE TABLE IF NOT EXISTS kumbia_test.test ( 20 | id INT(11) NOT NULL AUTO_INCREMENT, 21 | nombre varchar(50) NOT NULL , 22 | email varchar(100) NOT NULL , 23 | activo smallint(1) NULL DEFAULT 1 , 24 | PRIMARY KEY (id) );' 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/ActiveRecord/Metadata/PgsqlMetadataTest.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'Type' => 'int4', // int 17 | 'Null' => false, 18 | 'Default' => true, //false 19 | 'Key' => 'PRI', 20 | 'Auto' => true, 21 | ], 22 | 'nombre' => [ 23 | 'Type' => 'varchar', 24 | 'Null' => false, 25 | 'Default' => false, 26 | 'Key' => '', 27 | 'Auto' => false, 28 | ], 29 | 'email' => [ 30 | 'Type' => 'varchar', 31 | 'Null' => false, 32 | 'Default' => false, 33 | 'Key' => '', 34 | 'Auto' => false, 35 | ], 36 | 'activo' => [ 37 | 'Type' => 'int2', // smallint(1) 38 | 'Null' => true, 39 | 'Default' => true, 40 | 'Key' => '', 41 | 'Auto' => false 42 | ] 43 | ]; 44 | 45 | /** 46 | * @beforeClass 47 | */ 48 | public static function setUpCreateTable() 49 | { 50 | Db::get('pgsql')->query(' 51 | CREATE TABLE IF NOT EXISTS test ( 52 | id SERIAL PRIMARY KEY, 53 | nombre varchar(50) NOT NULL , 54 | email varchar(100) NOT NULL , 55 | activo smallint NULL DEFAULT 1 56 | );' 57 | ); 58 | } 59 | 60 | //TODO Fix it to delete it 61 | public function testGetWithDefault() 62 | { 63 | $withDefault = $this->getMetadata()->getWithDefault(); 64 | 65 | $this->assertEquals(['id', 'activo'], $withDefault); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/ActiveRecord/Metadata/SqliteMetadataTest.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'Type' => 'int', //int(11) 16 | 'Null' => false, 17 | 'Default' => false, 18 | 'Key' => 'PRI', 19 | 'Auto' => true, 20 | ], 21 | 'nombre' => [ 22 | 'Type' => 'varchar(50)', 23 | 'Null' => false, 24 | 'Default' => false, 25 | 'Key' => '', 26 | 'Auto' => false, 27 | ], 28 | 'email' => [ 29 | 'Type' => 'varchar(100)', 30 | 'Null' => false, 31 | 'Default' => false, 32 | 'Key' => '', 33 | 'Auto' => false, 34 | ], 35 | 'activo' => [ 36 | 'Type' => 'smallint(1)', 37 | 'Null' => true, 38 | 'Default' => true, 39 | 'Key' => '', 40 | 'Auto' => false 41 | ] 42 | ]; 43 | 44 | /** 45 | * @beforeClass 46 | */ 47 | public static function setUpCreateTable() 48 | { 49 | Db::get('sqlite')->query(' 50 | CREATE TABLE IF NOT EXISTS test 51 | ( 52 | id int PRIMARY KEY NOT NULL, 53 | nombre VARCHAR(50) NOT NULL, 54 | email VARCHAR(100) NOT NULL, 55 | activo SMALLINT(1) DEFAULT (1) 56 | );' 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Utils/PrivateUtil.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 19 | 20 | return $method->invokeArgs($obj, $args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'dsn' => 'mysql:host=127.0.0.1;dbname=kumbia_test;charset=utf8', 7 | 'username' => 'root', 8 | 'password' => '', 9 | 'params' => [ 10 | \PDO::ATTR_PERSISTENT => \true, //conexión persistente 11 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 12 | ] 13 | ], 14 | //Pgsql 15 | 'pgsql' => [ 16 | 'dsn' => 'pgsql:dbname=kumbia_test;host=localhost', 17 | 'username' => 'postgres', 18 | 'password' => '414141', 19 | 'params' => [ 20 | \PDO::ATTR_PERSISTENT => \true, //conexión persistente 21 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 22 | ] 23 | ], 24 | //Sqlite 25 | 'sqlite' => [ 26 | 'dsn' => 'sqlite::memory:', 27 | 'username' => '', 28 | 'password' => '', 29 | ], 30 | 31 | // bad connections to tests errors 32 | 'no_dsn' => [ 33 | 'dsn' => '' 34 | ], 35 | 'no_password' => [ 36 | 'dsn' => 'pgsql:dbname=no_exist;host=localhost' 37 | ], 38 | 'bad_credentials' => [ 39 | 'dsn' => 'pgsql:dbname=no_exist;host=localhost', 40 | 'password' => 'as' 41 | ] 42 | 43 | 44 | //More connections 45 | ]; 46 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | . 12 | 13 | 14 | 15 | 16 | 17 | . 18 | 19 | ./tests 20 | ./temp 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------