diff --git a/src/Date.php b/src/Date.php index 746b863..16c152f 100644 --- a/src/Date.php +++ b/src/Date.php @@ -43,7 +43,7 @@ class Date implements ArrayAccess, IteratorAggregate * * @param \DateTime|string|null $date */ - public function __construct($date) + public function __construct($date = 'now') { if (is_string($date)) { @@ -57,6 +57,10 @@ class Date implements ArrayAccess, IteratorAggregate { $this->date = new \DateTime((string) $date, self::$utc); } + else if ($date === null) + { + $this->date = new \DateTime('now', self::$utc); + } else if (is_array($date)) { 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 * @@ -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 '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 - case 'year': return $this->date->format('Y'); - case 'month': return $this->date->format('m'); - case 'day': return $this->date->format('d'); + case 'year': return $this->date->format('Y'); + case 'month': return $this->date->format('m'); + 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 '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 '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 '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!'); @@ -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 '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'); - - throw new \InvalidArgumentException('TODO: Property ->' . $name . ' not implemented yet'); + case 'age': return (new \DateTime('now', self::$utc))->diff($this->date)->y; + case 'days': return (new \DateTime('now', self::$utc))->diff($this->date)->days; + + 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 case 'year': return $this->date->format('Y'); @@ -956,9 +1022,6 @@ class Date implements ArrayAccess, IteratorAggregate case 'timestamp': $this->date->setTimestamp($value); break; - - case 'day': - case 'day': } throw new \InvalidArgumentException('Property ->' . $name . ' cannot be set!'); diff --git a/src/ORM/Collection.php b/src/ORM/Collection.php index 1386d3d..a022fd8 100644 --- a/src/ORM/Collection.php +++ b/src/ORM/Collection.php @@ -4,17 +4,22 @@ * Collection class with dynamic members * Similar functionality to an Array or Dictionary class * 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 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 */ - 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; @@ -25,7 +30,7 @@ class Collection implements \Iterator, \Countable, \ArrayAccess, \IteratorAggreg /** - * Countable interface + * */ 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. * @@ -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 */ diff --git a/src/ORM/ComplexMap.php b/src/ORM/ComplexMap.php new file mode 100644 index 0000000..b4f60a4 --- /dev/null +++ b/src/ORM/ComplexMap.php @@ -0,0 +1,8 @@ +properties =& $properties; - $this->id = $properties['id'] ?? null; } /** @@ -21,9 +30,19 @@ abstract class Entity */ function __get($name) { + if ( ! isset($this->properties[$name])) + { + if (isset($this->map[$name])) + { + + } + } return $this->properties[$name]; } +// abstract private relation($name); +// abstract private __get__(); + /** * Set entity field/property * @@ -44,4 +63,9 @@ abstract class Entity { unset($this->properties[$name]); } + + function jsonSerialize() + { + return $this->properties; + } } diff --git a/src/ORM/EntityMap.php b/src/ORM/EntityMap.php new file mode 100644 index 0000000..9989cda --- /dev/null +++ b/src/ORM/EntityMap.php @@ -0,0 +1,97 @@ +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; + +*/ diff --git a/src/ORM/Mapper.php b/src/ORM/Mapper.php new file mode 100644 index 0000000..bf2721c --- /dev/null +++ b/src/ORM/Mapper.php @@ -0,0 +1,102 @@ +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; + +*/ diff --git a/src/ORM/Repository.php b/src/ORM/Repository.php index 337d499..a94d57d 100644 --- a/src/ORM/Repository.php +++ b/src/ORM/Repository.php @@ -9,9 +9,18 @@ namespace Twister\ORM; * This class is designed for inheritance and users can subclass this class to * 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 */ -class Repository +abstract class Repository { /** * The database connection used by the EntityManager. @@ -20,6 +29,8 @@ class Repository */ protected $em = null; + protected $mapper = null; + public function __construct(Container $c) { diff --git a/src/ORM/SimpleMap.php b/src/ORM/SimpleMap.php new file mode 100644 index 0000000..f8c5167 --- /dev/null +++ b/src/ORM/SimpleMap.php @@ -0,0 +1,16 @@ +db = $container->db; $this->_normalize_ip_address(); - $this->_detect_banned_ips(); $this->method = strtoupper($_SERVER['REQUEST_METHOD']); $this->uri = Uri::fromGlobals(); $this->isHttps = $this->is_https = $this->isSecure = $this->uri->isHttps(); - - $this->cc = $this->_get_cc(); - - $this->_get_agent_ex(); } private function _normalize_ip_address() @@ -65,41 +53,6 @@ class Request $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 // List taken from: http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/ private static function _normalize_forwarded_for() @@ -120,27 +73,7 @@ class Request return preg_replace('/[, ]+/', ',', implode(',', $result)); } - private function _get_agent_ex_id($db, $value, $table, $field) - { - $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) + public function execute_route(array $routes) // AKA dispach / dispatchRoute { $path = $this->uri->path; $paths = explode('/', $path, 4); @@ -163,7 +96,7 @@ class Request { 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) { $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) { - $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) { $this->route['regexp'] = $regexp; @@ -197,18 +136,26 @@ class Request { if (($pos = strpos($controller, '::')) !== false) // controller is a static class method { - $class = substr($controller, 0, $pos); - $method = substr($controller, $pos + 2); + // $class = substr($controller, 0, $pos); + // $method = substr($controller, $pos + 2); // 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())); } else if (($pos = strpos($controller, '->')) !== false) // controller is an instantiatable object, usually an object extending the base Controller class { - $class = substr($controller, 0, $pos); - $method = substr($controller, $pos + 2); + // $class = substr($controller, 0, $pos); + // $method = substr($controller, $pos + 2); // 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); 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/(?[0-9]+)' // It also converts TRAILING `optional` paths to the preg equivalent: '/club/{id}[/history]' ==> '/club/(?[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? //if (substr_count($route, '[') !== substr_count($route, ']')) @@ -301,38 +248,21 @@ class Request ); } - // 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` - // + /** + * Dynamic route controller::handler argument builder + */ 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; $args = []; foreach ($params as $param) { if ($param->hasType() && isset($byType[$type = strtolower($param->getType())])) $args[] = $byType[$type]; - else if (isset($_GET[$param->name])) - $args[] = $_GET[$param->name]; else if (isset($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])) $args[] = $_POST[$param->name]; else @@ -403,8 +333,6 @@ class Request } - - // 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('path' => '/login')) == http://host/login (NOTE: Any `default` (request::$query) query string will be ignored when `path` is specified!) diff --git a/src/Response.php b/src/Response.php index 797c166..1700822 100644 --- a/src/Response.php +++ b/src/Response.php @@ -90,7 +90,7 @@ class Response if (isset($_SESSION)) session_write_close(); - $this->container->db->close(); + // $this->container->db->close(); } /**