mirror of
https://github.com/standardebooks/web.git
synced 2025-07-13 01:52:02 -04:00
Add database and ORM scaffolding
This commit is contained in:
parent
42ba2f8680
commit
9d923605d8
8 changed files with 278 additions and 0 deletions
7
lib/Artwork.php
Normal file
7
lib/Artwork.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
|
||||||
|
class Artwork extends PropertiesBase{
|
||||||
|
public $ArtworkId;
|
||||||
|
public $Title;
|
||||||
|
protected $DisplayTitle = null;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
class Collection{
|
class Collection{
|
||||||
public $Name;
|
public $Name;
|
||||||
public $Url;
|
public $Url;
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
use function Safe\define;
|
use function Safe\define;
|
||||||
use function Safe\strtotime;
|
use function Safe\strtotime;
|
||||||
|
|
||||||
|
const SITE_STATUS_LIVE = 'live';
|
||||||
|
const SITE_STATUS_DEV = 'dev';
|
||||||
|
define('SITE_STATUS', getenv('SITE_STATUS') ?: SITE_STATUS_DEV); // Set in the PHP FPM pool configuration. Have to use define() and not const so we can use a function.
|
||||||
|
|
||||||
|
const DATABASE_DEFAULT_DATABASE = 'se';
|
||||||
|
const DATABASE_DEFAULT_HOST = 'localhost';
|
||||||
|
|
||||||
const EBOOKS_PER_PAGE = 12;
|
const EBOOKS_PER_PAGE = 12;
|
||||||
const SORT_NEWEST = 'newest';
|
const SORT_NEWEST = 'newest';
|
||||||
const SORT_AUTHOR_ALPHA = 'author-alpha';
|
const SORT_AUTHOR_ALPHA = 'author-alpha';
|
||||||
|
|
|
@ -24,3 +24,5 @@ set_exception_handler(function(Throwable $ex): void{
|
||||||
|
|
||||||
throw $ex; // Send the exception back to PHP for its usual logging routine.
|
throw $ex; // Send the exception back to PHP for its usual logging routine.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$GLOBALS['DbConnection'] = new DbConnection(DATABASE_DEFAULT_DATABASE, DATABASE_DEFAULT_HOST);
|
||||||
|
|
15
lib/Db.php
Normal file
15
lib/Db.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?
|
||||||
|
|
||||||
|
class Db{
|
||||||
|
public static function GetLastInsertedId(){
|
||||||
|
return $GLOBALS['DbConnection']->GetLastInsertedId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function Query(string $query, $args = []){
|
||||||
|
if(!is_array($args)){
|
||||||
|
$args = [$args];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $GLOBALS['DbConnection']->Query($query, $args);
|
||||||
|
}
|
||||||
|
}
|
183
lib/DbConnection.php
Normal file
183
lib/DbConnection.php
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
<?
|
||||||
|
use function Safe\preg_match;
|
||||||
|
|
||||||
|
class DbConnection{
|
||||||
|
private $_link = null;
|
||||||
|
public $IsConnected = false;
|
||||||
|
public $QueryCount = 0;
|
||||||
|
|
||||||
|
public function __construct(?string $defaultDatabase = null, string $host = 'localhost', ?string $user = null, string$password = '', bool $forceUtf8 = true, bool $require = true){
|
||||||
|
if($user === null){
|
||||||
|
// Get the user running the script for local socket login
|
||||||
|
$user = posix_getpwuid(posix_geteuid())['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$connectionString = 'mysql:';
|
||||||
|
|
||||||
|
try{
|
||||||
|
if(stripos($host, ':') !== false){
|
||||||
|
$port = null;
|
||||||
|
preg_match('/([^:]*):([0-9]+)/ius', $host, $matches);
|
||||||
|
$host = $matches[1];
|
||||||
|
if(sizeof($matches) > 2){
|
||||||
|
$port = $matches[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
$connectionString .= 'host=' . $host;
|
||||||
|
|
||||||
|
if($port !== null){
|
||||||
|
$connectionString .= ';port=' . $port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$connectionString .= 'host=' . $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($defaultDatabase !== null){
|
||||||
|
$connectionString .= ';dbname=' . $defaultDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have to use ATTR_EMULATE_PREPARES = true, otherwise transactions don't work for some reason.
|
||||||
|
$params = [\PDO::ATTR_EMULATE_PREPARES => true, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_PERSISTENT => false];
|
||||||
|
|
||||||
|
if($forceUtf8){
|
||||||
|
$params[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'set names utf8mb4 collate utf8mb4_unicode_ci;';
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't use persistent connections (connection pooling) because we would have race condition problems with last_insert_id()
|
||||||
|
$this->_link = new \PDO($connectionString, $user, $password, $params);
|
||||||
|
|
||||||
|
$this->IsConnected = true;
|
||||||
|
}
|
||||||
|
catch(Exception $ex){
|
||||||
|
if(SITE_STATUS == SITE_STATUS_DEV){
|
||||||
|
var_dump($ex);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Logger::WriteErrorLogEntry('Error connecting to ' . $connectionString . '. Exception: ' . vds($ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
if($require){
|
||||||
|
print("Something crazy happened in our database. <a href=\"/contact/\">Drop us a line</a> and let us know exactly what you were doing and we'll take a look.\n<br />The more detail you can provide, the better!");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs: string $sql = the SQL query to execute
|
||||||
|
// array $params = an array of parameters to bind to the SQL statement
|
||||||
|
// Returns: a resource record or null on error
|
||||||
|
public function Query(string $sql, array $params = []){
|
||||||
|
if(!$this->IsConnected){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->QueryCount++;
|
||||||
|
$result = [];
|
||||||
|
$preparedSql = $sql;
|
||||||
|
|
||||||
|
$handle = $this->_link->prepare($preparedSql);
|
||||||
|
if(!is_array($params)){
|
||||||
|
$params = [$params];
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = 0;
|
||||||
|
foreach($params as $parameter){
|
||||||
|
$name++;
|
||||||
|
|
||||||
|
if(is_a($parameter, 'DateTime')){
|
||||||
|
$parameter = $parameter->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL strict mode requires 0 or 1 instead of true or false
|
||||||
|
// Can't use PDO::PARAM_BOOL, it just doesn't work
|
||||||
|
if(is_bool($parameter)){
|
||||||
|
if($parameter){
|
||||||
|
$parameter = 1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$parameter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_int($parameter)){
|
||||||
|
$handle->bindValue($name, $parameter, PDO::PARAM_INT);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$handle->bindValue($name, $parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$deadlockRetries = 0;
|
||||||
|
$done = false;
|
||||||
|
while(!$done){
|
||||||
|
try{
|
||||||
|
$result = $this->ExecuteQuery($handle, $preparedSql, $params);
|
||||||
|
$done = true;
|
||||||
|
}
|
||||||
|
catch(\PDOException $ex){
|
||||||
|
if($ex->errorInfo[1] == 1213 && $deadlockRetries < 3){ // InnoDB deadlock, this is normal and happens occasionally. All we have to do is retry the query.
|
||||||
|
$deadlockRetries++;
|
||||||
|
|
||||||
|
usleep(500000 * $deadlockRetries); // Give the deadlock some time to clear up. Start at .5 seconds
|
||||||
|
}
|
||||||
|
elseif(stripos($ex->getMessage(), '1064 offset out of bounds') !== false){
|
||||||
|
$done = true;
|
||||||
|
// We reach here if Sphinx tries to get a record past its page limit. Just silently do nothing.
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$done = true;
|
||||||
|
if(SITE_STATUS == SITE_STATUS_DEV){
|
||||||
|
print($sql);
|
||||||
|
throw($ex);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Logger::WriteErrorLogEntry($ex->getMessage());
|
||||||
|
Logger::WriteErrorLogEntry($preparedSql);
|
||||||
|
Logger::WriteErrorLogEntry(vds($params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one rowset is returned, change the result object
|
||||||
|
if(sizeof($result) == 1){
|
||||||
|
$result = $result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ExecuteQuery($handle, string $preparedSql, array $params){
|
||||||
|
$handle->execute();
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
do{
|
||||||
|
try{
|
||||||
|
if($handle->columnCount() > 0){
|
||||||
|
$result[] = $handle->fetchAll(\PDO::FETCH_OBJ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(\PDOException $ex){
|
||||||
|
// HY000 is thrown when there is no result set, e.g. for an update operation.
|
||||||
|
// If anything besides that is thrown, then send it up the stack
|
||||||
|
if($ex->errorInfo[0] != "HY000"){
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}while($handle->nextRowset());
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the last AUTO-INCREMENT id
|
||||||
|
public function GetLastInsertedId(){
|
||||||
|
$id = $this->_link->lastInsertId();
|
||||||
|
|
||||||
|
if($id == 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
}
|
30
lib/OrmBase.php
Normal file
30
lib/OrmBase.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?
|
||||||
|
use function Safe\substr;
|
||||||
|
|
||||||
|
abstract class OrmBase{
|
||||||
|
public static function FillObject($object, $row){
|
||||||
|
foreach($row as $property => $value){
|
||||||
|
if(substr($property, strlen($property) - 9) == 'Timestamp'){
|
||||||
|
if($value !== null){
|
||||||
|
$object->$property = new DateTime($value, new DateTimeZone('UTC'));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$object->$property = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif(substr($property, strlen($property) - 5) == 'Cache'){
|
||||||
|
$property = substr($property, 0, strlen($property) - 5);
|
||||||
|
$object->$property = $value;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$object->$property = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function FromRow($row){
|
||||||
|
return self::FillObject(new static(), $row);
|
||||||
|
}
|
||||||
|
}
|
33
lib/PropertiesBase.php
Normal file
33
lib/PropertiesBase.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?
|
||||||
|
|
||||||
|
abstract class PropertiesBase extends OrmBase{
|
||||||
|
public function __get($var){
|
||||||
|
$function = 'Get' . $var;
|
||||||
|
|
||||||
|
if(method_exists($this, $function)){
|
||||||
|
return $this->$function();
|
||||||
|
}
|
||||||
|
elseif(substr($var, 0, 7) == 'Display'){
|
||||||
|
// If we're asked for a DisplayXXX property and the getter doesn't exist, format as escaped HTML.
|
||||||
|
if($this->$var === null){
|
||||||
|
$target = substr($var, 7, strlen($var));
|
||||||
|
$this->$var = Formatter::ToPlainText($this->$target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->$var;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return $this->$var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __set($var, $val){
|
||||||
|
$function = 'Set' . $var;
|
||||||
|
if(method_exists($this, $function)){
|
||||||
|
$this->$function($val);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->$var = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue