Browse Source

Expanded ORM

master
therselman 7 years ago
parent
commit
ad0979cb3c
  1. 91
      src/Date.php
  2. 84
      src/ORM/Collection.php
  3. 8
      src/ORM/ComplexMap.php
  4. 11
      src/ORM/ComplexMapper.php
  5. 11
      src/ORM/DbDriver.php
  6. 30
      src/ORM/Entity.php
  7. 97
      src/ORM/EntityMap.php
  8. 102
      src/ORM/Mapper.php
  9. 13
      src/ORM/Repository.php
  10. 16
      src/ORM/SimpleMap.php
  11. 16
      src/ORM/SimpleMapper.php
  12. 132
      src/Request.php
  13. 2
      src/Response.php

91
src/Date.php

@ -43,7 +43,7 @@ class Date implements ArrayAccess, IteratorAggregate
* *
* @param \DateTime|string|null $date * @param \DateTime|string|null $date
*/ */
public function __construct($date) public function __construct($date = 'now')
{ {
if (is_string($date)) if (is_string($date))
{ {
@ -57,6 +57,10 @@ class Date implements ArrayAccess, IteratorAggregate
{ {
$this->date = new \DateTime((string) $date, self::$utc); $this->date = new \DateTime((string) $date, self::$utc);
} }
else if ($date === null)
{
$this->date = new \DateTime('now', self::$utc);
}
else if (is_array($date)) else if (is_array($date))
{ {
throw new InvalidArgumentException('Array constructor not implemented'); throw new InvalidArgumentException('Array constructor not implemented');
@ -114,6 +118,33 @@ class Date implements ArrayAccess, IteratorAggregate
} }
/**
* Returns the difference between two dates in days
*/
public function days($date = null)
{
return is_string($date) ? (new \DateTime($date, self::$utc))->diff($this->date)->days : $date->diff($this->date)->days;
}
/**
* Returns the difference between two dates
*
* @link http://php.net/manual/en/datetime.diff.php
*
*/
public function diff($date = 'now')
{
if ($date instanceof static)
return $this->date->diff($date->date);
else if ($date instanceof \DateTime)
return $this->date->diff($date);
else if (is_string($date))
return $this->date->diff(new \DateTime($date, self::$utc));
// return is_string($date) ? (new \DateTime($date, self::$utc))->diff($this->date)->days : $date->diff($this->date)->days;
}
/** /**
* Wrapper around \DateTime->setDate() for the current date * Wrapper around \DateTime->setDate() for the current date
* *
@ -725,16 +756,33 @@ class Date implements ArrayAccess, IteratorAggregate
case 'yearweek': break; // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek MySQL: Returns year and week for a date. The year in the result may be different from the year in the date argument for the first and the last week of the year. case 'yearweek': break; // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek MySQL: Returns year and week for a date. The year in the result may be different from the year in the date argument for the first and the last week of the year.
case 'date': return $this->date->format('Y-m-d'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date MySQL: Extracts the date part of the date or datetime expression expr. case 'date': return $this->date->format('Y-m-d'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date MySQL: Extracts the date part of the date or datetime expression expr.
throw new \InvalidArgumentException('TODO: Property offsetGet["' . $name . '"] not implemented yet'); case 'yesterday': return (clone $this)->modify('-1 day');
case 'tomorrow': return (clone $this)->modify('+1 day');
case 'prevday': return (clone $this)->modify('-1 day');
case 'nextday': return (clone $this)->modify('+1 day');
case 'prev_day': return (clone $this)->modify('-1 day');
case 'next_day': return (clone $this)->modify('+1 day');
case 'prevweek': return (clone $this)->modify('-7 days');
case 'nextweek': return (clone $this)->modify('+7 days');
case 'prev_week': return (clone $this)->modify('-7 days');
case 'next_week': return (clone $this)->modify('+7 days');
case 'prevmonth': return (clone $this)->modify('-1 month');
case 'nextmonth': return (clone $this)->modify('+1 month');
case 'prev_month': return (clone $this)->modify('-1 month');
case 'next_month': return (clone $this)->modify('+1 month');
case 'prevyear': return (clone $this)->modify('-1 year');
case 'nextyear': return (clone $this)->modify('+1 year');
case 'prev_year': return (clone $this)->modify('-1 year');
case 'next_year': return (clone $this)->modify('+1 year');
// strtolower($name) versions // strtolower($name) versions
case 'year': return $this->date->format('Y'); case 'year': return $this->date->format('Y');
case 'month': return $this->date->format('m'); case 'month': return $this->date->format('m');
case 'day': return $this->date->format('d'); case 'day': return $this->date->format('d');
case 'hour': return $this->date->format('G'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour MySQL: Returns the hour for time. The range of the return value is 0 to 23 for time-of-day values. However, the range of TIME values actually is much larger, so HOUR can return values greater than 23. case 'hour': return $this->date->format('G'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour MySQL: Returns the hour for time. The range of the return value is 0 to 23 for time-of-day values. However, the range of TIME values actually is much larger, so HOUR can return values greater than 23.
case 'minute': return $this->date->format('i'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_minute MySQL: Returns the minute for time, in the range 0 to 59. case 'minute': return $this->date->format('i'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_minute MySQL: Returns the minute for time, in the range 0 to 59.
case 'second': return $this->date->format('s'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour MySQL: Returns the second for time, in the range 0 to 59. case 'second': return $this->date->format('s'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour MySQL: Returns the second for time, in the range 0 to 59.
} }
throw new \InvalidArgumentException('Property offsetGet["' . $name . '"] does not exist!'); throw new \InvalidArgumentException('Property offsetGet["' . $name . '"] does not exist!');
@ -925,9 +973,27 @@ class Date implements ArrayAccess, IteratorAggregate
case 'yearweek': break; // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek MySQL: Returns year and week for a date. The year in the result may be different from the year in the date argument for the first and the last week of the year. case 'yearweek': break; // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_yearweek MySQL: Returns year and week for a date. The year in the result may be different from the year in the date argument for the first and the last week of the year.
case 'date': return $this->date->format('Y-m-d'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date MySQL: Extracts the date part of the date or datetime expression expr. case 'date': return $this->date->format('Y-m-d'); // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date MySQL: Extracts the date part of the date or datetime expression expr.
case 'age': return $this->date->format('Y-m-d'); case 'age': return (new \DateTime('now', self::$utc))->diff($this->date)->y;
case 'days': return (new \DateTime('now', self::$utc))->diff($this->date)->days;
throw new \InvalidArgumentException('TODO: Property ->' . $name . ' not implemented yet');
case 'yesterday': return (clone $this)->modify('-1 day');
case 'tomorrow': return (clone $this)->modify('+1 day');
case 'prevday': return (clone $this)->modify('-1 day');
case 'nextday': return (clone $this)->modify('+1 day');
case 'prev_day': return (clone $this)->modify('-1 day');
case 'next_day': return (clone $this)->modify('+1 day');
case 'prevweek': return (clone $this)->modify('-7 days');
case 'nextweek': return (clone $this)->modify('+7 days');
case 'prev_week': return (clone $this)->modify('-7 days');
case 'next_week': return (clone $this)->modify('+7 days');
case 'prevmonth': return (clone $this)->modify('-1 month');
case 'nextmonth': return (clone $this)->modify('+1 month');
case 'prev_month': return (clone $this)->modify('-1 month');
case 'next_month': return (clone $this)->modify('+1 month');
case 'prevyear': return (clone $this)->modify('-1 year');
case 'nextyear': return (clone $this)->modify('+1 year');
case 'prev_year': return (clone $this)->modify('-1 year');
case 'next_year': return (clone $this)->modify('+1 year');
// strtolower($name) versions // strtolower($name) versions
case 'year': return $this->date->format('Y'); case 'year': return $this->date->format('Y');
@ -956,9 +1022,6 @@ class Date implements ArrayAccess, IteratorAggregate
case 'timestamp': case 'timestamp':
$this->date->setTimestamp($value); $this->date->setTimestamp($value);
break; break;
case 'day':
case 'day':
} }
throw new \InvalidArgumentException('Property ->' . $name . ' cannot be set!'); throw new \InvalidArgumentException('Property ->' . $name . ' cannot be set!');

84
src/ORM/Collection.php

@ -4,17 +4,22 @@
* Collection class with dynamic members * Collection class with dynamic members
* Similar functionality to an Array or Dictionary class * Similar functionality to an Array or Dictionary class
* Typically holding a collection/array of Entity members * Typically holding a collection/array of Entity members
* This class is not particularly useful compared to a standard array, *
* but it's used to `extend` the functionality of standard arrays.
* *
* @link https://laravel.com/docs/5.4/eloquent-collections#available-methods * @link https://laravel.com/docs/5.4/eloquent-collections#available-methods
* *
* @link http://php.net/manual/en/class.iterator.php
* @link http://php.net/manual/en/class.arrayaccess.php
* @link http://php.net/manual/en/class.countable.php
* @link http://php.net/manual/en/class.iteratoraggregate.php
* @link http://php.net/manual/en/class.jsonserializable.php
*
*
* @author Trevor Herselman <therselman@gmail.com> * @author Trevor Herselman <therselman@gmail.com>
*/ */
namespace Twister\ORM; namespace Twister\ORM;
class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggregate //, JsonSerializable http://php.net/JsonSerializable class Collection implements \Iterator, \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
{ {
protected $members = null; protected $members = null;
@ -25,7 +30,7 @@ class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggreg
/** /**
* Countable interface *
*/ */
public function find(...$params) public function find(...$params)
{ {
@ -315,54 +320,6 @@ class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggreg
} }
/**
* Get an iterator for the members.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->members);
}
/**
* Get member by id/index
*
* Note: This is not very useful, because most members will be indexed by integer.
*
* @param string|int $idx
* @return mixed
*/
public function __get($idx)
{
return $this->members[$idx];
}
/**
* Set member by id/index
*
* @param string|int $idx
* @param mixed $value
* @return void
*/
public function __set($idx, $value)
{
$this->members[$idx] = $value;
}
function __isset($idx)
{
return isset($this->members[$idx]);
}
function __unset($idx)
{
unset($this->members[$idx]);
}
/** /**
* Get the collection of items as a plain array. * Get the collection of items as a plain array.
* *
@ -391,11 +348,11 @@ class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggreg
/** /**
* * JsonSerializable interface
*/ */
public function toJson() public function jsonSerialize()
{ {
return json_encode($this->members); return $this->members;
} }
@ -405,6 +362,21 @@ class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggreg
} }
/**
* Get an external iterator to the members.
*
* IteratorAggregate interface
*
* @link http://php.net/manual/en/class.iteratoraggregate.php
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->members);
}
/** /**
* Iterator interface * Iterator interface
*/ */

8
src/ORM/ComplexMap.php

@ -0,0 +1,8 @@
<?php
namespace Twister\ORM;
abstract class ComplexMap
{
}

11
src/ORM/ComplexMapper.php

@ -0,0 +1,11 @@
<?php
namespace Twister\ORM;
class ComplexMapper
{
function __construct(ComplexMap $map)
{
}
}

11
src/ORM/DbDriver.php

@ -0,0 +1,11 @@
<?php
namespace Twister\ORM;
/**
* Mappter = EntityMap + DbDriver
*
*/
interface DBDriver
{
}

30
src/ORM/Entity.php

@ -2,15 +2,24 @@
namespace Twister\ORM; namespace Twister\ORM;
abstract class Entity /**
* Similar Examples
* ----------------
* @link https://github.com/analogueorm/analogue/blob/5.5/src/Entity.php
*
* Interfaces
* ----------
* @link http://php.net/JsonSerializable
*/
abstract class Entity implements \JsonSerializable
{ {
protected $properties; protected $properties;
public $id;
protected $map; // AKA config; can include relations, factory methods for relations etc.
function __construct(array $properties = null) function __construct(array $properties = null)
{ {
$this->properties =& $properties; $this->properties =& $properties;
$this->id = $properties['id'] ?? null;
} }
/** /**
@ -21,9 +30,19 @@ abstract class Entity
*/ */
function __get($name) function __get($name)
{ {
if ( ! isset($this->properties[$name]))
{
if (isset($this->map[$name]))
{
}
}
return $this->properties[$name]; return $this->properties[$name];
} }
// abstract private relation($name);
// abstract private __get__();
/** /**
* Set entity field/property * Set entity field/property
* *
@ -44,4 +63,9 @@ abstract class Entity
{ {
unset($this->properties[$name]); unset($this->properties[$name]);
} }
function jsonSerialize()
{
return $this->properties;
}
} }

97
src/ORM/EntityMap.php

@ -0,0 +1,97 @@
<?php
namespace Twister\ORM;
/**
* `Maps` persistent storage to Entities
*
* Actions and responsibilities include:
* Implements field Mutators and
* Relationships & Joins
* List of Fields
* Table aliases
* Virtual / dynamic field names
*
*/
abstract class EntityMap
{
protected $fields;
protected $from;
protected $joins; // optional
protected $properties;
protected $aliases;
protected $relationships;
// abstract private __get__();
function __construct(array $properties = null)
{
$this->properties =& $properties;
}
}
/*
'aliases' => [ 'w' => 'worlds',
'u' => 'users',
'c' => 'clubs'
];
'keys' = [ [['u.id', 'w.user_id'], 'one-to-many'],
[['w.id', 'c.world_id'], 'one-to-many']
];
// dynamic optional field names
'joins' => [ 'name' => 'LEFT JOIN nation_names %1$s ON %1$s.nation_id = n.id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
],
static $joins = array( 'name' => ' LEFT JOIN nation_names %1$s ON %1$s.nation_id = n.id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
'history' => ' LEFT JOIN nation_history nh ON nh.nation_id = n.id AND "%s" BETWEEN nh.from_date AND nh.until_date',
'capital' => ' LEFT JOIN city_names %1$s ON %1$s.city_id = nh.capital_id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
'territories' => ' LEFT JOIN territory_nations tn ON tn.territory_id = t.id',
'territories-admin' => ' LEFT JOIN territory t ON t.id = tn.territory_id',
'population' => ' LEFT JOIN nation_population np ON np.nation_id = n.id AND %s BETWEEN np.from_day AND np.until_day', // This uses a 'pre-calculated' day, ie. TO_DAYS(CURDATE) ... HOWEVER, We could also add anything we wanted to the string! ie. 'IFNULL(TO_DAYS("' . $date . '"), 0)' ... WARNING: TO_DAYS("0000-00-00") is converted to NULL!
// 'profile' => '', // common +
// 'city' => ' LEFT JOIN nations n ON n.id = tn.nation_id', // shouldn't the city create the joins ???
// 'wikipedia' => ' LEFT JOIN urls %1$s ON %1$s.object = "nation" AND %1$s.object_id = n.id AND %1$s.lang = "%2$s" AND %1$s.type = "wikipedia"', // DEPRECATED!
'modified-by' => ' LEFT JOIN users muid ON muid.id = n.modified_user_id'
);
public $contexts = [ 'profile' => [],
''
];
public $virtualFields = [ 'age' => 'TIMESTAMPDIFF(YEAR, u.dob, NOW()) as age',
'style_name' => 'STYLIZE_NAME(u.fname, u.alias, u.lname, 0, 0)'
];
public static function factory()
{
}
Laravel
-------
$this->hasMany('App\User')->withTimestamps();
Analogue
--------
use Analogue\ORM\Relationships\BelongsTo;
use Analogue\ORM\Relationships\BelongsToMany;
use Analogue\ORM\Relationships\EmbedsMany;
use Analogue\ORM\Relationships\EmbedsOne;
use Analogue\ORM\Relationships\HasMany;
use Analogue\ORM\Relationships\HasManyThrough;
use Analogue\ORM\Relationships\HasOne;
use Analogue\ORM\Relationships\MorphMany;
use Analogue\ORM\Relationships\MorphOne;
use Analogue\ORM\Relationships\MorphTo;
use Analogue\ORM\Relationships\MorphToMany;
*/

102
src/ORM/Mapper.php

@ -0,0 +1,102 @@
<?php
namespace Twister\ORM;
/**
* Extended by SimpleMapper and ComplexMapper
*
* Reads & processes an EntityMap + DB Driver/Adapter
*
* Actions and responsibilities include:
* Implements field Mutators and
* Relationships & Joins
* List of Fields
* Table aliases
* Virtual / dynamic field names
*
*/
abstract class Mapper
{
protected $fields;
protected $from;
protected $joins; // optional
protected $properties;
protected $aliases;
protected $relationships;
function __construct(array $properties = null)
{
$this->properties =& $properties;
}
abstract function select();
abstract function from();
// abstract private __get__();
}
/*
'aliases' => [ 'w' => 'worlds',
'u' => 'users',
'c' => 'clubs'
];
'keys' = [ [['u.id', 'w.user_id'], 'one-to-many'],
[['w.id', 'c.world_id'], 'one-to-many']
];
// dynamic optional field names
'joins' => [ 'name' => 'LEFT JOIN nation_names %1$s ON %1$s.nation_id = n.id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
],
static $joins = array( 'name' => ' LEFT JOIN nation_names %1$s ON %1$s.nation_id = n.id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
'history' => ' LEFT JOIN nation_history nh ON nh.nation_id = n.id AND "%s" BETWEEN nh.from_date AND nh.until_date',
'capital' => ' LEFT JOIN city_names %1$s ON %1$s.city_id = nh.capital_id AND %1$s.lang = "%2$s" AND "%3$s" BETWEEN %1$s.from_date AND %1$s.until_date',
'territories' => ' LEFT JOIN territory_nations tn ON tn.territory_id = t.id',
'territories-admin' => ' LEFT JOIN territory t ON t.id = tn.territory_id',
'population' => ' LEFT JOIN nation_population np ON np.nation_id = n.id AND %s BETWEEN np.from_day AND np.until_day', // This uses a 'pre-calculated' day, ie. TO_DAYS(CURDATE) ... HOWEVER, We could also add anything we wanted to the string! ie. 'IFNULL(TO_DAYS("' . $date . '"), 0)' ... WARNING: TO_DAYS("0000-00-00") is converted to NULL!
// 'profile' => '', // common +
// 'city' => ' LEFT JOIN nations n ON n.id = tn.nation_id', // shouldn't the city create the joins ???
// 'wikipedia' => ' LEFT JOIN urls %1$s ON %1$s.object = "nation" AND %1$s.object_id = n.id AND %1$s.lang = "%2$s" AND %1$s.type = "wikipedia"', // DEPRECATED!
'modified-by' => ' LEFT JOIN users muid ON muid.id = n.modified_user_id'
);
public $contexts = [ 'profile' => [],
''
];
public $virtualFields = [ 'age' => 'TIMESTAMPDIFF(YEAR, u.dob, NOW()) as age',
'style_name' => 'STYLIZE_NAME(u.fname, u.alias, u.lname, 0, 0)'
];
public static function factory()
{
}
Laravel
-------
$this->hasMany('App\User')->withTimestamps();
Analogue
--------
use Analogue\ORM\Relationships\BelongsTo;
use Analogue\ORM\Relationships\BelongsToMany;
use Analogue\ORM\Relationships\EmbedsMany;
use Analogue\ORM\Relationships\EmbedsOne;
use Analogue\ORM\Relationships\HasMany;
use Analogue\ORM\Relationships\HasManyThrough;
use Analogue\ORM\Relationships\HasOne;
use Analogue\ORM\Relationships\MorphMany;
use Analogue\ORM\Relationships\MorphOne;
use Analogue\ORM\Relationships\MorphTo;
use Analogue\ORM\Relationships\MorphToMany;
*/

13
src/ORM/Repository.php

@ -9,9 +9,18 @@ namespace Twister\ORM;
* This class is designed for inheritance and users can subclass this class to * This class is designed for inheritance and users can subclass this class to
* write their own repositories with business-specific methods to locate entities. * write their own repositories with business-specific methods to locate entities.
* *
*
* Similar Examples
* ----------------
* @link https://github.com/analogueorm/analogue/blob/5.5/src/Repository.php
*
* Interfaces
* ----------
* @link http://php.net/JsonSerializable
*
* @author Trevor Herselman <therselman@gmail.com> * @author Trevor Herselman <therselman@gmail.com>
*/ */
class Repository abstract class Repository
{ {
/** /**
* The database connection used by the EntityManager. * The database connection used by the EntityManager.
@ -20,6 +29,8 @@ class Repository
*/ */
protected $em = null; protected $em = null;
protected $mapper = null;
public function __construct(Container $c) public function __construct(Container $c)
{ {

16
src/ORM/SimpleMap.php

@ -0,0 +1,16 @@
<?php
namespace Twister\ORM;
abstract class SimpleMap
{
function __construct()
{
}
function build()
{
return new SimpleMapper(new static());
}
}

16
src/ORM/SimpleMapper.php

@ -0,0 +1,16 @@
<?php
namespace Twister\ORM;
class SimpleMapper
{
function __construct(SimpleMap $map)
{
}
static function build(SimpleMap $map)
{
}
}

132
src/Request.php

@ -5,7 +5,6 @@ namespace Twister;
class Request class Request
{ {
private $container = null; private $container = null;
private $db = null;
public $remote_addr = null; public $remote_addr = null;
public $inet_pton = null; public $inet_pton = null;
@ -21,14 +20,8 @@ class Request
public $isIpv4 = null; public $isIpv4 = null;
public $is_ipv4 = null; public $is_ipv4 = null;
public $cc = null;
public $uri = null; public $uri = null;
public $agent_id = null;
public $forwarded_for_id = null;
public $via_id = null;
public $route = null; public $route = null;
public $routes = null; public $routes = null;
public $params = null; public $params = null;
@ -39,17 +32,12 @@ class Request
$this->db = $container->db; $this->db = $container->db;
$this->_normalize_ip_address(); $this->_normalize_ip_address();
$this->_detect_banned_ips();
$this->method = strtoupper($_SERVER['REQUEST_METHOD']); $this->method = strtoupper($_SERVER['REQUEST_METHOD']);
$this->uri = Uri::fromGlobals(); $this->uri = Uri::fromGlobals();
$this->isHttps = $this->is_https = $this->isSecure = $this->uri->isHttps(); $this->isHttps = $this->is_https = $this->isSecure = $this->uri->isHttps();
$this->cc = $this->_get_cc();
$this->_get_agent_ex();
} }
private function _normalize_ip_address() private function _normalize_ip_address()
@ -65,41 +53,6 @@ class Request
$this->isIpv4 = $this->is_ipv4 = $this->ipv4 !== false; $this->isIpv4 = $this->is_ipv4 = $this->ipv4 !== false;
} }
private function _detect_banned_ips()
{
$db = $this->db;
if ($db->lookup('SELECT SQL_CACHE 1 FROM bot_bans WHERE ip = 0x' . $this->ip2hex))
{
$db->real_query('UPDATE bot_ban_requests SET requests = requests + 1 WHERE ip = 0x' . $this->ip2hex);
$db->close();
sleep(3);
header('HTTP/1.0 403 Forbidden');
exit;
}
}
private function _get_cc()
{
return $this->db->lookup( $this->is_ipv4 ?
('SELECT SQL_CACHE cc FROM geoip2_ipv4 WHERE ' . $this->ipv4 . ' BETWEEN range_from AND range_until') :
// 'SELECT SQL_CACHE cc FROM geoip2_ipv6 WHERE CONV(HEX(LEFT(0x' . self::$ip2hex . ', 8)), 16, 10) BETWEEN range_high_from AND range_high_until AND CONV(HEX(RIGHT(0x' . self::$ip2hex . ', 8)), 16, 10) BETWEEN range_low_from AND range_low_until LIMIT 1'
('SELECT SQL_CACHE cc FROM geoip2_ipv6 WHERE 0x' . substr($this->ip2hex, 0, 16) . ' BETWEEN range_high_from AND range_high_until AND 0x' . substr($this->ip2hex, 16) . ' BETWEEN range_low_from AND range_low_until LIMIT 1')
);
}
private function _get_agent_ex() // `agent` is just a generic term for `agent`, `forwarded for` and `via`
{
$db = $this->db;
$agent = isset($_SERVER['HTTP_USER_AGENT']) ? db::varchar($_SERVER['HTTP_USER_AGENT'], 700) : null;
$ff = db::varchar(self::_normalize_forwarded_for(), 128);
$via = isset($_SERVER['HTTP_VIA']) ? db::varchar($_SERVER['HTTP_VIA'], 224) : null;
$db->real_query('LOCK TABLES request_agents WRITE, request_forwarded_for WRITE, request_via WRITE');
$this->agent_id = empty($agent) ? 0 : $this->_get_agent_ex_id($db, $agent, 'request_agents', 'agent');
$this->forwarded_for_id = empty($ff) ? 0 : $this->_get_agent_ex_id($db, $ff, 'request_forwarded_for', 'forwarded_for');
$this->via_id = empty($via) ? 0 : $this->_get_agent_ex_id($db, $via, 'request_via', 'via');
$db->real_query('UNLOCK TABLES');
}
// Combine all the possible values for 'HTTP_X_FORWARDED_FOR' together // Combine all the possible values for 'HTTP_X_FORWARDED_FOR' together
// List taken from: http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/ // List taken from: http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/
private static function _normalize_forwarded_for() private static function _normalize_forwarded_for()
@ -120,27 +73,7 @@ class Request
return preg_replace('/[, ]+/', ',', implode(',', $result)); return preg_replace('/[, ]+/', ',', implode(',', $result));
} }
private function _get_agent_ex_id($db, $value, $table, $field) public function execute_route(array $routes) // AKA dispach / dispatchRoute
{
$md5 = md5($value);
$id = $db->lookup('SELECT id FROM ' . $table . ' WHERE hash = 0x' . $md5);
if ( ! $id)
{
$id = $db->call('CALL spGetRandomID("id", "' . $table . '", 1, 0x7FFFFFFF)');
// TEMPORARY HACK, while we examine the various 'forwarded_for' values!
// I want to know which of the various `forwarded_for` combinations are used on the internet!
// So these are just `bitmasks` of various possible fields! Once we establish which ones are used, we can remove the bit fields and the $_SERVER[] array member in normalize_forwarded_for()
if ($field == 'forwarded_for')
// REMOVE THIS SECTION when our 'testing' is complete! We should determine what 'forwarded_for' server variables are used, and reduce it!
$db->real_query('INSERT IGNORE INTO request_forwarded_for (id, hash, forwarded_for, client_ip, x_forwarded_for, x_forwarded, x_cluster_client_ip, forwarded_for2, forwarded) VALUES (' . $id . ', 0x' . $md5 . ', ' . $db->escape($value) . ', ' . (int) empty($_SERVER['HTTP_CLIENT_IP']) . ', ' . (int) empty($_SERVER['HTTP_X_FORWARDED_FOR']) . ', ' . (int) empty($_SERVER['HTTP_X_FORWARDED']) . ', ' . (int) empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) . ', ' . (int) empty($_SERVER['HTTP_FORWARDED_FOR']) . ', ' . (int) empty($_SERVER['HTTP_FORWARDED']) . ')');
else
// Leave only this statement after we've tested the various 'forwarded_for' combinations!
$db->real_query('INSERT IGNORE INTO ' . $table . ' (id, hash, ' . $field . ') VALUES (' . $id . ', 0x' . $md5 . ', ' . $db->escape($value) . ')');
}
return $id;
}
public function execute_route(array $routes)
{ {
$path = $this->uri->path; $path = $this->uri->path;
$paths = explode('/', $path, 4); $paths = explode('/', $path, 4);
@ -163,7 +96,7 @@ class Request
{ {
if ( ! isset($route[0]) || strpos($route[0], $method) !== false) if ( ! isset($route[0]) || strpos($route[0], $method) !== false)
{ {
$regexp = '~^' . (strpos($route[1], '/') === 0 ? null : '/' . $paths[1] . '/') . $this->_convert_route_pattern($route[1]) . '$~'; $regexp = '~^' . (strpos($route[1], '/') === 0 ? null : '/' . $paths[1] . '/') . $this->expandRoutePattern($route[1]) . '$~';
if (preg_match($regexp, $path, $this->params) === 1) if (preg_match($regexp, $path, $this->params) === 1)
{ {
$this->route['regexp'] = $regexp; // store the `winning` regexp $this->route['regexp'] = $regexp; // store the `winning` regexp
@ -177,7 +110,13 @@ class Request
} }
else if (is_null($arr[0]) || strpos($arr[0], $method) !== false) else if (is_null($arr[0]) || strpos($arr[0], $method) !== false)
{ {
$regexp = '~^' . (empty($arr[1]) ? '/' . $paths[1] : ((strpos($arr[1], '/') === 0 ? null : '/' . $paths[1] . '/') . $this->_convert_route_pattern($arr[1]))) . '$~'; // by default if we leave the route/command null, we use /login instead of /login/ like `/admin/...` basically this works differently to the array (sub-folder) version for arrays, where we append /name/ but here we only do /name //dump($arr);
///dump($method);
//dump($paths);
//dump($this->expandRoutePattern($arr[1]));
//die();
$regexp = '~^' . (empty($arr[1]) ? '/' . $paths[1] : ((strpos($arr[1], '/') === 0 ? null : '/' . $paths[1] . '/')
. $this->expandRoutePattern($arr[1]))) . '$~'; // by default if we leave the route/command null, we use /login instead of /login/ like `/admin/...` basically this works differently to the array (sub-folder) version for arrays, where we append /name/ but here we only do /name
if (preg_match($regexp, $path, $this->params) === 1) if (preg_match($regexp, $path, $this->params) === 1)
{ {
$this->route['regexp'] = $regexp; $this->route['regexp'] = $regexp;
@ -197,18 +136,26 @@ class Request
{ {
if (($pos = strpos($controller, '::')) !== false) // controller is a static class method if (($pos = strpos($controller, '::')) !== false) // controller is a static class method
{ {
$class = substr($controller, 0, $pos); // $class = substr($controller, 0, $pos);
$method = substr($controller, $pos + 2); // $method = substr($controller, $pos + 2);
// require __DIR__ . '/../controllers/' . $class . '.php'; // require __DIR__ . '/../controllers/' . $class . '.php';
$reflection = new \ReflectionMethod($class, $method); /**
* NOTE: If you get a `ReflectionException` here; with `Class MyClass does not exist`
* Then check the previous exception message for compile time PHP errors loading the class file!
*/
try {
$reflection = new \ReflectionMethod(substr($controller, 0, $pos), substr($controller, $pos + 2));
} catch (\ReflectionException $e) {
throw $e->getPrevious();
}
return $reflection->invokeArgs(null, $this->_get_args_from_params($reflection->getParameters())); return $reflection->invokeArgs(null, $this->_get_args_from_params($reflection->getParameters()));
} }
else if (($pos = strpos($controller, '->')) !== false) // controller is an instantiatable object, usually an object extending the base Controller class else if (($pos = strpos($controller, '->')) !== false) // controller is an instantiatable object, usually an object extending the base Controller class
{ {
$class = substr($controller, 0, $pos); // $class = substr($controller, 0, $pos);
$method = substr($controller, $pos + 2); // $method = substr($controller, $pos + 2);
// require __DIR__ . '/../controllers/' . $class . '.php'; // require __DIR__ . '/../controllers/' . $class . '.php';
$reflection = new \ReflectionMethod($class, $method); $reflection = new \ReflectionMethod(substr($controller, 0, $pos), substr($controller, $pos + 2));
$obj = new $class($this->container); $obj = new $class($this->container);
return $reflection->invokeArgs($obj, $this->_get_args_from_params($reflection->getParameters())); return $reflection->invokeArgs($obj, $this->_get_args_from_params($reflection->getParameters()));
} }
@ -259,9 +206,9 @@ class Request
// This function expands routes like '/admin/article/{id}' ==> '/admin/article/(?<id>[0-9]+)' // This function expands routes like '/admin/article/{id}' ==> '/admin/article/(?<id>[0-9]+)'
// It also converts TRAILING `optional` paths to the preg equivalent: '/club/{id}[/history]' ==> '/club/(?<id>[0-9]+)(?:/history)?' // It also converts TRAILING `optional` paths to the preg equivalent: '/club/{id}[/history]' ==> '/club/(?<id>[0-9]+)(?:/history)?'
// //
private function _convert_route_pattern($route) public function expandRoutePattern($route)
{ {
if (strrpos($route, ']', -1) !== false) // check if the route ends with optional parameters (where last character in route is `]`) if (strpos($route, ']', -1) !== false) // check if the route ends with optional parameters (where last character in route is `]`)
{ {
// Check matching number of opening & closing brackets ... disabled only for performance reasons, the preg_match() will also throw an exception!?!? wtf? // Check matching number of opening & closing brackets ... disabled only for performance reasons, the preg_match() will also throw an exception!?!? wtf?
//if (substr_count($route, '[') !== substr_count($route, ']')) //if (substr_count($route, '[') !== substr_count($route, ']'))
@ -301,38 +248,21 @@ class Request
); );
} }
// Dynamic route controller::handler argument builder /**
// * Dynamic route controller::handler argument builder
// Need to somehow re-work this ... */
//
// I don't like the fact that the `$bytype` array is actually initializing the `db` and `user`!
// Should probably somehow use a `switch` statement for the `$bytype`
//
// NOTE: I cannot really make this process dynamic by searching the `container` array.
// So I can't do something like this: `if ($param->type === $container->type)` ...
// because for example; the `db` value is actually a closure because it's an `inline factory`
// It's not a `Twister\Db` ... yet! So I can't compare it to a `function (Db $db)` value!
// `Twister\Db !== Closure`
//
private function _get_args_from_params(array $params) private function _get_args_from_params(array $params)
{ {
/*
$byType = [ 'twister\container' => &$this->container,
'twister\db' => &$this->db,
// 'twister\user' => &$this->container->user,
'twister\request' => &$this
];
*/
$byType = $this->container->requestRouteParams; $byType = $this->container->requestRouteParams;
$args = []; $args = [];
foreach ($params as $param) foreach ($params as $param)
{ {
if ($param->hasType() && isset($byType[$type = strtolower($param->getType())])) if ($param->hasType() && isset($byType[$type = strtolower($param->getType())]))
$args[] = $byType[$type]; $args[] = $byType[$type];
else if (isset($_GET[$param->name]))
$args[] = $_GET[$param->name];
else if (isset($this->params[$param->name])) else if (isset($this->params[$param->name]))
$args[] = $this->params[$param->name]; $args[] = $this->params[$param->name];
else if (isset($_GET[$param->name]))
$args[] = $_GET[$param->name];
else if (isset($_POST[$param->name])) else if (isset($_POST[$param->name]))
$args[] = $_POST[$param->name]; $args[] = $_POST[$param->name];
else else
@ -403,8 +333,6 @@ class Request
} }
// eg. request::build_url(array('scheme' => null)) == //host/path // eg. request::build_url(array('scheme' => null)) == //host/path
// eg. request::build_url(array('scheme' => 'https', 'host' => 'example.com', 'path' => '/login')) == https://example.com/login // eg. request::build_url(array('scheme' => 'https', 'host' => 'example.com', 'path' => '/login')) == https://example.com/login
// eg. request::build_url(array('path' => '/login')) == http://host/login (NOTE: Any `default` (request::$query) query string will be ignored when `path` is specified!) // eg. request::build_url(array('path' => '/login')) == http://host/login (NOTE: Any `default` (request::$query) query string will be ignored when `path` is specified!)

2
src/Response.php

@ -90,7 +90,7 @@ class Response
if (isset($_SESSION)) if (isset($_SESSION))
session_write_close(); session_write_close();
$this->container->db->close(); // $this->container->db->close();
} }
/** /**

Loading…
Cancel
Save