diff --git a/.gitignore b/.gitignore index 17c0368..b29d097 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,4 @@ -# ####################################################################### -# Gitignore -# Add Some Common OS and Code Editor -# ####################################################################### - -# -# ================================= -# Gitignore For Mac -# ================================= -# - -# Apple Default hidden -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails +# mac Thumbnails ._* # Files that might appear in the root of a volume @@ -29,33 +9,6 @@ Icon .Trashes .VolumeIcon.icns -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# -# ================================= -# Gitignore For Windows -# ================================= -# - -# Windows image file caches -Thumbs.db -thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# System Volumes Information used on file shares -System Volume Information/ - # Windows Installer files *.cab *.msi @@ -107,50 +60,16 @@ sftp-config.json *.iml ## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries -# .idea/shelf - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml +.idea/*.xml +.idea/*/ ## File-based project format: *.ipr *.iws -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - # JIRA plugin atlassian-ide-plugin.xml -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - # Gitignore For Composer # ================================= # @@ -173,5 +92,15 @@ vendor # Ignore Visual Studio code .vscode -/Modules/*/ -/Storage/logs/*.* +# ON LOCAL DEV +# ================================= +# + +# My Modules +Modules/*/ +# My Extension +Extensions/*/ +# My Config +/Config/Config.php +# My Private Directory +/Private diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5a587f4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +sudo: false + +language: php + +php: + - 7.0 + - 7.1 + +before_script: + - composer install -o + +script: ./vendor/bin/phpcs + +matrix: + fast_finish: true diff --git a/App/.htaccess b/App/.htaccess new file mode 100644 index 0000000..7ea9a3c --- /dev/null +++ b/App/.htaccess @@ -0,0 +1,3 @@ +# Deny access from all + +Deny From All diff --git a/App/Classes/Abstracts/BaseRouteGroup.php b/App/Classes/Abstracts/BaseRouteGroup.php new file mode 100644 index 0000000..3b7dd3f --- /dev/null +++ b/App/Classes/Abstracts/BaseRouteGroup.php @@ -0,0 +1,33 @@ +container = $container; + } + + /** + * Invoker For Route Group + * + * @return mixed + */ + abstract public function __invoke(); +} diff --git a/App/Classes/Abstracts/BaseRouteSegment.php b/App/Classes/Abstracts/BaseRouteSegment.php new file mode 100644 index 0000000..2077edd --- /dev/null +++ b/App/Classes/Abstracts/BaseRouteSegment.php @@ -0,0 +1,42 @@ +container = $container; + } + + /** + * Base Invoker + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $params slim routes parameters + * @return ResponseInterface + */ + abstract public function __invoke( + RequestInterface $request, + ResponseInterface $response, + array $params = [] + ) : ResponseInterface; +} diff --git a/App/Classes/Abstracts/EmbeddedSystem.php b/App/Classes/Abstracts/EmbeddedSystem.php new file mode 100644 index 0000000..6cf09a0 --- /dev/null +++ b/App/Classes/Abstracts/EmbeddedSystem.php @@ -0,0 +1,207 @@ +embedded_container = $container; + $this->getEmbeddedName(); + } + + /** + * Get EmbeddedSystem Info + * + * @return array + */ + public function getEmbeddedInfo() : array + { + return [ + EmbeddedSystem::NAME => $this->getEmbeddedName(), + EmbeddedSystem::VERSION => $this->getEmbeddedVersion(), + EmbeddedSystem::URI => $this->getEmbeddedUri(), + EmbeddedSystem::AUTHOR => $this->getEmbeddedAuthor(), + EmbeddedSystem::AUTHOR_URI => $this->getEmbeddedAuthorUri(), + EmbeddedSystem::DESCRIPTION => $this->getEmbeddedDescription(), + EmbeddedSystem::CLASS_NAME => get_class($this), + EmbeddedSystem::FILE_PATH => $this->getEmbeddedRealPath(), + ]; + } + + /** + * Get Reflection + * + * @return \ReflectionClass + */ + final protected function getEmbeddedReflection() : \ReflectionClass + { + if (! $this->privateEmbeddedReflectionClass instanceof \ReflectionClass) { + $this->privateEmbeddedReflectionClass = new \ReflectionClass($this); + } + + return $this->privateEmbeddedReflectionClass; + } + + /** + * Get Path + * + * @return string + */ + final public function getEmbeddedRealPath() : string + { + return $this->getEmbeddedReflection()->getFileName(); + } + + /** + * Get Name Space + * + * @return string + */ + final public function getEmbeddedNameSpace() : string + { + return $this->getEmbeddedReflection()->getNamespaceName(); + } + + /** + * Get ShortName of Class + * + * @return string + */ + final public function getEmbeddedShortName() : string + { + return $this->getEmbeddedReflection()->getShortName(); + } + + /** + * {@inheritdoc} + */ + public function getEmbeddedName() : string + { + if (!is_string($this->embedded_name) + || trim($this->embedded_name) == '' + ) { + $this->embedded_name = $this->getEmbeddedReflection()->getName(); + } + + return (string) $this->embedded_name; + } + + /** + * {@inheritdoc} + */ + public function getEmbeddedAuthor() : string + { + return (string) $this->embedded_author; + } + + /** + * {@inheritdoc} + */ + public function getEmbeddedVersion() : string + { + return (string) $this->embedded_version; + } + + /** + * Get EmbeddedSystem URL + * + * @return string + */ + public function getEmbeddedUri(): string + { + return (string) $this->embedded_uri; + } + + /** + * Get EmbeddedSystem Author + * + * @return string + */ + public function getEmbeddedAuthorUri(): string + { + return (string) $this->embedded_author_uri; + } + + /** + * Get Description of EmbeddedSystem + * + * @return string + */ + public function getEmbeddedDescription(): string + { + return (string) $this->embedded_description; + } +} diff --git a/App/Classes/Abstracts/ResponseGeneratorAbstract.php b/App/Classes/Abstracts/ResponseGeneratorAbstract.php new file mode 100644 index 0000000..093e00a --- /dev/null +++ b/App/Classes/Abstracts/ResponseGeneratorAbstract.php @@ -0,0 +1,460 @@ +setRequest($request); + $this->setResponse($response); + $this->setStatusCode($response->getStatusCode()); + } + + /** + * Set Data + * + * @param mixed $data + * @return static + */ + public function setData($data) + { + $this->data = $data; + return $this; + } + + /** + * Get data + * + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * @return int + */ + public function getEncoding(): int + { + return $this->encoding; + } + + /** + * @param int $encoding + * @return static + */ + public function setEncoding(int $encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Set Character Set + * + * @param string $data + * @return static + */ + public function setCharset($data) + { + if (is_string($data)) { + $data = strtolower(trim($data)); + if ($data == '') { + $data = null; + } elseif (preg_match('/([^0-9]*)[\-]([0-9]+)?$/', $data, $match)) { + // sanitize to default utf8 + $data = $match[0] . '-' . (!empty($match[1]) ? $match[1] : '8'); + } + } + + $this->charset = $data; + return $this; + } + + /** + * Get character set + * + * @return string + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Maps a file extensions to a mimeType. + * + * @param $extension string The file extension. + * + * @return string|null + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ + public static function getMimeTypeFromExtension(string $extension) + { + static $mimeTypes = [ + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimeTypes[$extension]) + ? $mimeTypes[$extension] + : null; + } + + /** + * Generate content type + * + * @uses $this->getMimeTypeFromExtension() + * @return string + */ + protected function fixMimeType() + { + $this->mimeType = !is_string($this->mimeType) || trim($this->mimeType) == '' + ? 'text/html' + : strtolower(trim($this->mimeType)); + + if ($this->recheckMimeType || strpos($this->mimeType, '/') === false) { + $selectedContentTypes = array_filter(explode(',', $this->mimeType)); + if (count($selectedContentTypes)) { + $this->mimeType = current($selectedContentTypes); + } + if (preg_match( + '/(?:(?:[^/]*)(?:\\\+|\/+))?(html?|javascript|calendar|css|plain)/', + $this->mimeType, + $match + ) && !empty($match[1]) + ) { + $this->mimeType = $this->mimeType == 'htm' + ? 'html' + : $this->mimeType; + $this->mimeType = "text/{$match[1]}"; + } elseif (strpos($this->mimeType, 'ico') !== false || strpos($this->mimeType, 'icns') !== false) { + $this->mimeType = 'image/x-icon'; + $charset = null; + } elseif (strpos($this->mimeType, 'sgm') !== false) { + $this->mimeType = 'text/sgml'; + } elseif (preg_match( + '/(?:(?:[^/]*)(?:\\\+|\/+))?(ja?son|xml|ogg|pdf|postscript|zip|ttf2?)/', + $this->mimeType, + $match + ) && !empty($match[1]) + ) { + if ($match[1] == 'jason') { + $match[1] = 'json'; + } elseif ($match[1] == 'ttf2' || $match[1] == 'ttf') { + $match[1] = 'x-font-ttf'; + } + $this->mimeType = 'application/' . $match[1]; + } elseif (preg_match( + '/(?:(?:[^/]*)(?:\\\+|\/+))?(jpe?g?|png|w?bmp|gif|pbm|tif(?:f+)?|png|ppm|ras|xbm|xpm|xwd)/', + $this->mimeType, + $match + ) + && !empty($match[1]) + ) { + if ($match[1] == 'wbmp') { + $match[1] = 'bmp'; + } elseif (strpos($match[1], 'tif') !== false) { + $match[1] = 'tiff'; + } + $this->mimeType = $this->getMimeTypeFromExtension($match[1]); + } else { + $mimeType = null; + $this->mimeType = preg_replace('/(\\\|\/)+/', '/', trim($this->mimeType)); + if (preg_match('/([^\]*)(?:\/(.+(\+.+)?)/?', $this->mimeType, $match) && !empty($match)) { + if (!empty($match[3])) { + $mimeType = $this->getMimeTypeFromExtension($match[3]); + } + if (!$mimeType && !empty($match[2])) { + $mimeType = $this->getMimeTypeFromExtension($match[2]); + } + if (!$mimeType) { + $mimeType = $this->getMimeTypeFromExtension($match[1]); + } + } + if (!$mimeType) { + if (preg_match('/te?xt|plain|ini/', $this->mimeType)) { + $mimeType = 'txt'; + } + $mimeType = $this->getMimeTypeFromExtension($mimeType); + // fallback to default `text/html` + if (!$mimeType) { + $mimeType = 'text/html'; + } + $this->mimeType = $mimeType; + } + } + } + + return $this->mimeType; + } + + /** + * @return string + */ + public function getContentType() : string + { + $charset = $this->getCharset(); + return $this->getMimeType() . ($charset ? ';charset=' . $charset : ''); + } + + /** + * Get Mime Type + * + * @return string + */ + public function getMimeType() + { + return $this->fixMimeType(); + } + + /** + * Set Mime Type + * + * @param string $mimeType + * @return static + */ + public function setMimeType(string $mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * Set Response Status + * + * @param int $status + * @return static + */ + public function setStatusCode($status) + { + if ($this->response->withStatus($status)->getReasonPhrase() == '') { + throw new \InvalidArgumentException( + 'Invalid response code given.', + E_USER_ERROR + ); + } + + $this->statusCode = abs($status); + return $this; + } + + /** + * Get Status Code + * + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Generate + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return static + */ + public static function generate(RequestInterface $request, ResponseInterface $response) + { + return new static($request, $response); + } + + /** + * Serve The response + * + * @return ResponseInterface + */ + abstract public function serve() : ResponseInterface; + + /** + * @return RequestInterface + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Set Override Request + * + * @param RequestInterface $request + * @return static + */ + public function setRequest(RequestInterface $request) + { + $this->request = $request; + return $this; + } + + /** + * @return ResponseInterface + */ + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * Set Override Response + * + * @param ResponseInterface $response + * @return static + */ + public function setResponse(ResponseInterface $response) + { + $this->response = $response; + return $this; + } +} diff --git a/App/Classes/Application.php b/App/Classes/Application.php new file mode 100644 index 0000000..5f38b59 --- /dev/null +++ b/App/Classes/Application.php @@ -0,0 +1,392 @@ +appDirectory = dirname(__DIR__); + $this->componentDirectory = $this->getAppDirectory('Components'); + $this->containerDirectory = $this->getAppDirectory('Containers'); + $this->rootDirectory = dirname($this->appDirectory); + if (!defined('WEB_ROOT')) { + define('WEB_ROOT', dirname($_SERVER['SCRIPT_FILENAME'])); + } + $this->webRootDirectory = $this->getFixPath(WEB_ROOT, false); + } + + /** + * Returning Directory Separator + * + * @return string + */ + public function getDS() + { + return $this->ds; + } + + /** + * Fix Path Separator + * + * @param string $path + * @param bool $useCleanPrefix + * @return string + */ + public function getFixPath(string $path = '', $useCleanPrefix = false) : string + { + /** + * Trimming path string + */ + if (($path = trim($path)) == '') { + return $path; + } + + $path = preg_replace('`(\/|\\\)+`', $this->getDS(), $path); + if ($useCleanPrefix) { + $path = $this->getDS() . ltrim($path, $this->getDS()); + } + + return $path; + } + + /** + * Get Root Directory + * + * @param string $path + * @return string + */ + public function getRootDirectory(string $path = '') : string + { + return $this->rootDirectory . $this->getFixPath($path, true); + } + + /** + * Get Web Root Directory + * + * @param string $path + * @return string + */ + public function getWebRootDirectory(string $path = '') : string + { + return $this->webRootDirectory . $this->getFixPath($path, true); + } + + /** + * Get Application Directory + * + * @param string $path + * @return string + */ + public function getAppDirectory(string $path = '') : string + { + return $this->appDirectory . $this->getFixPath($path, true); + } + + /** + * Get Application Directory + * + * @param string $path + * @return string + */ + public function getContainerDirectory(string $path = '') : string + { + return $this->containerDirectory . $this->getFixPath($path, true); + } + + /** + * Get Application Component Directory + * @param string $path + * @return string + */ + public function getComponentDirectory(string $path = '') : string + { + return $this->componentDirectory . $this->getFixPath($path, true); + } + + /** + * Include Scope + * + * @param-read string $file + * @return mixed + * @throws FileNotFoundException + */ + public function includeScope() + { + if (func_num_args() < 1) { + throw new InvalidArgumentException( + 'Argument 1 could not be empty.', + E_USER_ERROR + ); + } + + if (!is_string(func_get_arg(0))) { + throw new InvalidArgumentException( + sprintf( + 'Argument 1 must be as a string %s given.', + gettype(func_get_arg(0)) + ), + E_USER_ERROR + ); + } + + if (!($path = stream_resolve_include_path(func_get_arg(0)))) { + throw new FileNotFoundException( + func_get_arg(0) + ); + } + + /** + * closure include of scope to prevent access @uses Application + * bind to @uses Arguments + * if inside of include call $this it wil be access as @uses Arguments object + * @uses Application::APP_KEY to access application instance + * eg : + * $this->get(Application::APP_KEY) + */ + $args = func_get_args(); + $args[self::APP_KEY] =& $this; + $fn = (function () { + /** @var Arguments $this */ + /** @noinspection PhpIncludeInspection */ + return include $this[0]; + })->bindTo(new Arguments($args)); + + return $fn(); + } + + /** + * Include Scope + * + * @param-read string $file + * @return mixed + * @throws FileNotFoundException + */ + public function includeScopeOnce() + { + if (func_num_args() < 1) { + throw new InvalidArgumentException( + 'Argument 1 could not be empty.', + E_USER_ERROR + ); + } + + if (!is_string(func_get_arg(0))) { + throw new InvalidArgumentException( + sprintf( + 'Argument 1 must be as a string %s given.', + gettype(func_get_arg(0)) + ), + E_USER_ERROR + ); + } + + if (!($path = stream_resolve_include_path(func_get_arg(0)))) { + throw new FileNotFoundException( + func_get_arg(0) + ); + } + + /** + * closure include of scope to prevent access @uses Application + * bind to @uses Arguments + * if inside of include call $this it wil be access as @uses Arguments object + * @uses Application::APP_KEY to access application instance + * eg : + * $this->get(Application::APP_KEY) + */ + $args = func_get_args(); + $args[self::APP_KEY] =& $this; + $fn = (function () { + /** @var Arguments $this */ + /** @noinspection PhpIncludeInspection */ + return include_once $this[0]; + })->bindTo(new Arguments($args)); + + return $fn(); + } + + /** + * Get Run Slim Instance + * + * @return App + */ + public function &getSlim() + { + return $this->slim; + } + + /** + * Run The application + * + * @param array $config + * @return ResponseInterface + */ + public function process(array $config) : ResponseInterface + { + if ($this->hasRun) { + throw new LogicException( + 'Application has been run! Please does not re run the application procedure.', + E_ERROR + ); + } + + // must be call it first + $this->slim = $this->includeScope( + $this->getComponentDirectory('ApplicationSlimObject.php'), + $config + ); + + // call container + $this->includeScope($this->getComponentDirectory('ApplicationContainer.php')); + // call middleware + $this->includeScope($this->getComponentDirectory('ApplicationMiddleware.php')); + // determine & call routes + $this->includeScope($this->getComponentDirectory('ApplicationRoutes.php')); + + return $this->slim->run(true); + } + + /** + * Get Application + * + * @param string $name + * @return mixed + * @throws Error + */ + public function getContainer($name) + { + if (!$this->hasContainer($name)) { + settype($name, 'string'); + throw new Error( + sprintf( + "Application container %s is not exists!", + $name + ) + ); + } + + return $this->getSlim()->getContainer()->get($name); + } + + /** + * if application exists + * + * @param string $name + * @return bool + */ + public function hasContainer($name) + { + if (!is_string($name)) { + return false; + } + + $slim = $this->getSlim(); + if ($slim instanceof App) { + $container = $slim->getContainer(); + return $container->has($name); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return $this->hasContainer($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->getContainer($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + if ($this->getSlim() && $this->getSlim() instanceof App) { + $container = $this->getSlim()->getContainer(); + $container[$offset] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + if ($this->getSlim() && $this->getSlim() instanceof App) { + $container = $this->getSlim()->getContainer(); + unset($container[$offset]); + } + } +} diff --git a/App/Classes/Arguments.php b/App/Classes/Arguments.php new file mode 100644 index 0000000..9d325fd --- /dev/null +++ b/App/Classes/Arguments.php @@ -0,0 +1,113 @@ +arguments =& $arguments; + } + + /** + * Get Arguments + * + * @param int|string|float $offset + * @param null $default + * @return mixed|null + */ + public function &get($offset, $default = null) + { + if ($this->has($offset)) { + return $this->arguments[$offset]; + } + + return $default; + } + + /** + * Check if has Arguments + * + * @param int|mixed|float $offset + * @return bool + */ + public function has($offset) + { + return (array_key_exists($offset, $this->arguments)); + } + + /** + * Set Arguments + * + * @param int|string|float $offset + * @param mixed $value + */ + public function set($offset, $value) + { + $this->arguments[$offset] = $value; + } + + /** + * Remove/Unset Arguments + * + * @param int|string|float $offset + */ + public function remove($offset) + { + unset($this->arguments[$offset]); + } + + /** + * Count Arguments + * + * @return int + */ + public function count() : int + { + return count($this->arguments); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return $this->has($offset); + } +} diff --git a/App/Classes/AutoLoaderClass.php b/App/Classes/AutoLoaderClass.php new file mode 100644 index 0000000..c7465a9 --- /dev/null +++ b/App/Classes/AutoLoaderClass.php @@ -0,0 +1,353 @@ +nameSpace = $this->resolveNameSpace($nameSpace); + $this->baseDirectory = (array) $directory; + } + + /** + * @return array + */ + public static function getReferences() : array + { + return self::$references; + } + + /** + * @return string[] class map with lower case key + */ + public function getClassMap() : array + { + return $this->classMap; + } + + /** + * Resolve Name Space + * + * @param string $string + * @return string + */ + protected function resolveNameSpace(string $string) : string + { + return preg_replace( + '`(\\\)+`', + '\\', + trim($string, '\\') + ); + } + + /** + * Resolve Name Space To Lower + * + * @param string $string + * @return string + */ + protected function resolveNameSpaceLower($string) : string + { + return $this->toLower($this->resolveNameSpace($string)); + } + + /** + * To Lower Case + * + * @param string $string + * @return string + */ + protected function toLower($string) : string + { + // sanity case insensitive class + return strtolower($string); + } + + /** + * Push Into Reference + * + * @param string $group the group / Name Space + * @param string $class the class Name + * @param string $file the absolute file + * @return bool|string + */ + private function pushReference(string $group, string $class, string $file) + { + if ($file = stream_resolve_include_path($file)) { + // references name space + $nameSpace = $this->resolveNameSpace($group); + $group = $this->toLower($nameSpace); + if ($this->hasGroupReference($group)) { + self::$references[$group] = []; + } + $class = $this->resolveNameSpaceLower($class); + $this->classMap[$class] = $file; + return self::$references[$group][$class] = $file; + } + + return false; + } + + /** + * Load Class + * + * @param string $class + * @return bool + */ + public function load(string $class) : bool + { + if (!is_string($class)) { + return false; + } + + if ($file = $this->findFileFor($class)) { + // prevent multiple call of class + if (class_exists($class)) { + return true; + } + + IncludeFileOnce($file); + return true; + } + + $class = ltrim($class, '\\'); + // counting missed + $this->missedClass[$class] = isset($this->missedClass[$class]) + ? $this->missedClass[$class]++ + : 1; + + return false; + } + + /** + * Create Instance + * + * @param string $nameSpace + * @param string|array $directory + * @return AutoLoaderClass + */ + public static function create(string $nameSpace, string $directory) : AutoLoaderClass + { + return new static($nameSpace, $directory); + } + + /** + * Create Object & Register + * + * @param string $nameSpace the name space + * @param string|array $directory directory to scan + * @return bool + */ + public static function createRegister(string $nameSpace, string $directory) : bool + { + return (new static($nameSpace, $directory))->register(); + } + + /** + * @param array $nameSpaceAndDirectory + */ + public static function createRegisterArray(array $nameSpaceAndDirectory) + { + foreach ($nameSpaceAndDirectory as $key => $item) { + self::createRegister($key, $item); + } + } + + /** + * Register Auto load + * + * @param bool $prepend + * @return bool + */ + public function register(bool $prepend = false) : bool + { + return spl_autoload_register($this, true, $prepend); + } + + /** + * Un-Registers this instance as an auto loader. + */ + public function unRegister() + { + spl_autoload_unregister($this); + } + + /** + * Get File For Class + * + * @param string $Class + * @return bool|string + */ + protected function findFileFor(string $Class) + { + $Class = $this->resolveNameSpace($Class); + $class = $this->toLower($Class); + if ($class) { + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if (isset(self::$classMapReference[$class])) { + return self::$classMapReference[$class]; + } + } + + if (false !== $file = $this->getClassReference($class)) { + return $file; + } + + $file = false; + $namespace= $this->resolveNameSpaceLower($this->nameSpace); + foreach ($this->baseDirectory as $directory) { + if (!is_string($directory) || ! ($directory = realpath($directory))) { + continue; + } + + if (0 === $pos = strpos($class, $namespace)) { + $class = substr($Class, strlen($namespace)+1); + } + + $file = $this->pushReference( + $this->nameSpace, + $class, + $directory . DIRECTORY_SEPARATOR . $class . '.php' + ); + if ($file) { + self::$classMapReference[$this->resolveNameSpaceLower($class)] = $file; + break; + } + } + + return $file; + } + + /** + * Split Name Space Key + * + * @param string $class + * @return array + */ + protected function splitClassNameSpace(string $class) : array + { + $class = $this->resolveNameSpace($class); + $nameSpaceArray = explode('\\', $class); + $nameSpace = ''; + $class = end($nameSpaceArray); + array_shift($nameSpaceArray); + if (count($nameSpaceArray) > 1) { + $nameSpace = implode('\\', $nameSpaceArray); + } + return [ + self::NAME_SPACE_KEY => $nameSpace, + self::CLASS_NAME_KEY => $class, + ]; + } + + /** + * Get Reference + * @param string $class + * @return bool|array + */ + protected function getClassReference(string $class) + { + // check if not as a class + if (!$class || substr($class, -1) == '\\') { + return false; + } + + $splitClass = $this->splitClassNameSpace($class); + $group = $this->getGroupReference($splitClass[self::NAME_SPACE_KEY]); + + return $group && isset($group[$splitClass[self::CLASS_NAME_KEY]]) + ? $group[$splitClass[self::CLASS_NAME_KEY]] + : false; + } + + /** + * Getting References + * + * @param string $group + * @return bool|mixed + */ + protected function getGroupReference(string $group) + { + if ($this->hasGroupReference($group)) { + return AutoLoaderClass::$references[$this->resolveNameSpace($group)]; + } + + return false; + } + + /** + * Has Group reference + * + * @param string $group + * @return bool + */ + protected function hasGroupReference(string $group) : bool + { + $group = $this->resolveNameSpaceLower($group); + return isset(self::$references[$group]); + } + + /** + * @param $class + */ + public function __invoke(string $class) + { + call_user_func_array([$this, 'load'], func_get_args()); + } +} + +/** + * @param string $file + */ +function IncludeFileOnce(string $file) +{ + /** @noinspection PhpIncludeInspection */ + include_once $file; +} diff --git a/App/Classes/ComposerLoaderPSR4.php b/App/Classes/ComposerLoaderPSR4.php new file mode 100644 index 0000000..b38946b --- /dev/null +++ b/App/Classes/ComposerLoaderPSR4.php @@ -0,0 +1,118 @@ +classLoader = $classLoader ?: new ClassLoader(); + } + + /** + * Add PSR4 + * + * @param array $nameSpacePath key as NameSpace & value as paths + * @return ComposerLoaderPSR4 + * @throws InvalidArgumentException + */ + public function addArray(array $nameSpacePath) : ComposerLoaderPSR4 + { + foreach ($nameSpacePath as $nameSpace => $paths) { + // if there was has a slash fix to backslash + if (strpos($nameSpace, '/') !== false) { + $nameSpace = preg_replace('(\\\|\/)', '\\', $nameSpace); + } + // Trim & add su-fix back slash that make Name Space Valid + $nameSpace = trim($nameSpace, '\\') . '\\'; + if (!is_string($paths) && ! is_array($paths)) { + throw new InvalidArgumentException( + sprintf( + "Invalid paths for Name Space %s. Paths must be as a string or array.", + $nameSpace + ), + E_USER_ERROR + ); + } elseif (is_array($paths)) { + foreach ($paths as $keyPath => $path) { + if (!is_string($path)) { + throw new InvalidArgumentException( + sprintf( + "Invalid path value for Name Space %s in key %s. Path must be as a string.", + $nameSpace, + $keyPath + ), + E_USER_ERROR + ); + } + } + } + + $this->classLoader->addPsr4( + $nameSpace, + (array) $paths + ); + } + + return $this; + } + + /** + * Add Path + * + * @param string $prefix + * @param string|array $paths + * @return ComposerLoaderPSR4 + */ + public function add(string $prefix, $paths) : ComposerLoaderPSR4 + { + return $this->addArray([$prefix => $paths]); + } + + /** + * Register Auto loader + * + * @param bool $prepend + */ + public function register($prepend = false) + { + $this->classLoader->register($prepend); + } + + /** + * UnRegister The Loader + */ + public function unRegister() + { + $this->classLoader->unregister(); + } + + /** + * @param array $path + * @param ClassLoader|null $classLoader + * @return ComposerLoaderPSR4 + */ + public static function create(array $path, ClassLoader $classLoader = null) : ComposerLoaderPSR4 + { + $object = new static($classLoader ?: new ClassLoader()); + $object->addArray($path); + return $object; + } +} diff --git a/App/Classes/Config.php b/App/Classes/Config.php new file mode 100644 index 0000000..ad5ed4a --- /dev/null +++ b/App/Classes/Config.php @@ -0,0 +1,258 @@ +collection = new CollectionFetch($setting); + $this->originalCollection = clone $this->collection; + } + + /** + * Get Config + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function get($key = null, $default = null) + { + if (!func_num_args()) { + return $this->collection->all(); + } + + return $this->collection->fetch($key, $default); + } + + /** + * Offset Exists + * + * @param string $key + * @return bool + */ + public function exist($key) : bool + { + return $this->get($key, false) !== false + && $this->get($key, true) !== true; + } + + /** + * Reset Collection to default + */ + public function resetToDefault() + { + $this->lastCollection = $this->collection; + $this->collection = clone $this->originalCollection; + } + + /** + * @return CollectionFetch + */ + public function getCurrentCollection() : CollectionFetch + { + return $this->collection; + } + + /** + * @return CollectionFetch + */ + public function getDefaultCollection() : CollectionFetch + { + return $this->originalCollection; + } + + /** + * @return CollectionFetch + */ + public function getLastCollection() : CollectionFetch + { + return $this->lastCollection ?: $this->collection; + } + + /** + * @param array|string $key string + * @param mixed $values the value key name + */ + public function set($key, $values = null) + { + if (is_array($key)) { + $this->collection->replace($key); + return; + } + + if (!is_string($key) && !is_numeric($key)) { + throw new InvalidArgumentException( + 'Invalid key name given! Key config must be as a string!' + ); + } + + if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $key, $matches)) > 1) { + // Does the index contain array notation + $the_key = null; + $matches[0] = array_reverse($matches[0]); + $old_key = null; + for ($i = 0; $i < $count; $i++) { + $key = trim($matches[0][$i], '[]'); + // Empty notation will return the value as array + if ($key === '') { + $the_key[] = $the_key?: $values; + if (count($the_key) > 1) { + unset($the_key[key($the_key)]); + } + continue; + } + if (!isset($the_key)) { + $the_key[$key] = $values; + continue; + } + + $the_key[$key] = $the_key; + if (count($the_key) > 1) { + unset($the_key[key($the_key)]); + } + } + + $key = key($the_key); + $values = is_array($this->collection[$key]) + ? array_merge($this->collection[$key], $the_key[$key]) + : $the_key; + unset($the_key); + } + + $this->collection->set($key, $values); + } + + /** + * Remove Key from nested selector + * + * @param string $key + */ + public function remove($key) + { + if (!is_string($key) && !is_numeric($key)) { + throw new InvalidArgumentException( + 'Invalid key name given! Key config to remove must be as a string!' + ); + } + + if (!$this->exist($key)) { + return; + } + + if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $key, $matches)) > 1) { + $firstKey = reset($matches[0]); + $keyName = $firstKey; + $tmp = $this->collection[$keyName]; + if (!is_array($tmp)) { + return; + } + array_shift($matches[0]); + $unsetPosition = 0; + foreach ($matches[0] as $keyNum => $keyName) { + $keyName = trim($keyName, '[]'); + if ($unsetPosition <> $keyNum && + (!is_array($tmp) || ! array_key_exists($keyName, $tmp)) + ) { + return; + } + + $unsetPosition++; + $tmp = $tmp[$keyName]; + } + + $tmp = $this->collection[$firstKey]; + $currentUnsetPosition = 0; + // binding anonymous function to handle array reference + $recursiveUnset = function ( + &$array, + $unwanted_key + ) use ( + $unsetPosition, + &$currentUnsetPosition, + &$recursiveUnset +) { + $currentUnsetPosition++; + if ($unsetPosition !== $currentUnsetPosition) { + if (array_key_exists($unwanted_key, $array)) { + unset($array[$unwanted_key]); + } + // stop + return; + } + foreach ($array as &$value) { + if (is_array($value)) { + $recursiveUnset($value, $unwanted_key); + } + } + }; + + // call closure to binding reference + $recursiveUnset($tmp, $keyName); + $this->collection[$firstKey] = $tmp; + unset($tmp); + return; // stop + } + + unset($this->collection[$key]); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) : bool + { + return $this->exist($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + return $this->set($offset, $value); + } + + /** + * @param int|string $offset + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } +} diff --git a/App/Classes/Database.php b/App/Classes/Database.php new file mode 100644 index 0000000..f0adb84 --- /dev/null +++ b/App/Classes/Database.php @@ -0,0 +1,865 @@ + @uses Connection + * + * @method void beginTransaction() + * + * @method void close() + * @method void commit() + * @method bool connect() + * @method mixed convertToDatabaseValue(mixed $value, $type) + * @method mixed convertToPHPValue(mixed $value, $type) + * @method QueryBuilder createQueryBuilder() + * @method void createSavepoint(string $savepoint) + * + * @method int delete(string $tableExpression, array $identifier, array $types = []) + * + * @method int errorCode() + * @method array errorInfo() + * @method void exec(string $statement) + * @method Statement executeQuery(string $query, array $params = [], array $types = [], QueryCacheProfile $qcp = null) + * @method ResultStatement executeCacheQuery(string $query, $params, $types, QueryCacheProfile $qcp) + * @method int executeUpdate(string $query, array $params = [], array $types = []) + * + * @method int insert(string $tableExpression, array $data, array $types = []) + * @method bool isAutoCommit() + * @method bool isConnected() + * @method bool isRollbackOnly() + * @method bool isTransactionActive() + * + * @method array fetchAssoc(string $statement, array $params = [], array $types = []) + * @method array fetchArray(string $statement, array $params = [], array $types = []) + * @method array fetchColumn(string $statement, array $params = [], array $types = []) + * @method array fetchAll(string $sql, array $params = array(), $types = array()) + * + * @method Configuration getConfiguration() + * @method Driver getDriver() + * @method string getDatabase() + * @method AbstractPlatform getDatabasePlatform() + * @method EventManager getEventManager() + * @method ExpressionBuilder getExpressionBuilder() + * @method string getHost() + * @method array getParams() + * @method string|null getPassword() + * @method mixed getPort() + * @method AbstractSchemaManager getSchemaManager() + * @method int getTransactionIsolation() + * @method int getTransactionNestingLevel() + * @method string|null getUsername() + * @method Connection getWrappedConnection() + * + * @method string lastInsertId(string|null $seqName) + * + * @method bool ping() + * @method Statement prepare(string $statement) + * @method array project(string $query, array $params, \Closure $function) + * + * @method void releaseSavepoint(string $savePoint) + * @method array resolveParams(array $params, array $types) + * @method bool|void rollBack() + * @method void rollbackSavepoint(string $savePoint) + * + * @method void setAutoCommit(bool $autoCommit) + * @method void setFetchMode(int $fetchMode) + * @method void setNestTransactionsWithSavePoints(bool $nestTransactionsWithSavePoints) + * @method void setRollbackOnly() + * @method int setTransactionIsolation(int $level) + * + * @method void transactional(\Closure $func) + * + * @method int update(string $tableExpression, array $data, array $identifier, array $types = []) + * + * @method string quote(mixed $input, int $type = \PDO::PARAM_STR) + * @method string quoteIdentifier(string $str) + * + * @uses \PDO::ATTR_DEFAULT_FETCH_MODE for (19) + * @method Statement query(string $sql, int $mode = 19, mixed $additionalArg = null, array $constructorArgs = []) + */ +class Database +{ + /** + * @see Connection::TRANSACTION_READ_UNCOMMITTED + */ + const TRANSACTION_READ_UNCOMMITTED = Connection::TRANSACTION_READ_UNCOMMITTED; + + /** + * @see Connection::TRANSACTION_READ_COMMITTED + */ + const TRANSACTION_READ_COMMITTED = Connection::TRANSACTION_READ_COMMITTED; + + /** + * @see Connection::TRANSACTION_REPEATABLE_READ + */ + const TRANSACTION_REPEATABLE_READ = Connection::TRANSACTION_REPEATABLE_READ; + + /** + * @see Connection::TRANSACTION_SERIALIZABLE + */ + const TRANSACTION_SERIALIZABLE = Connection::TRANSACTION_SERIALIZABLE; + + /** + * @see Connection::PARAM_INT_ARRAY + */ + const PARAM_INT_ARRAY = Connection::PARAM_INT_ARRAY; + + /** + * @see Connection::PARAM_STR_ARRAY + */ + const PARAM_STR_ARRAY = Connection::PARAM_STR_ARRAY; + + /** + * @see Connection::ARRAY_PARAM_OFFSET + */ + const ARRAY_PARAM_OFFSET = Connection::ARRAY_PARAM_OFFSET; + + /** + * @var Connection + */ + protected $currentConnection; + + /** + * @var string + */ + protected $currentSelectedDriver; + + /** + * @var array + */ + protected $currentUserParams = [ + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', // 'utf_general_ci' + ]; + + /** + * @var string + */ + protected $currentTablePrefix = ''; + + /** + * Column Quote Identifier + * + * @var string + */ + protected $currentQuoteIdentifier = '"'; + + /** + * Database constructor. + * @param array $configs database Configuration + * @throws DBALException + */ + public function __construct(array $configs) + { + /** + * Merge User Param + */ + $this->currentUserParams = array_merge($this->currentUserParams, $configs); + if (empty($this->currentUserParams['driver']) + && isset($this->currentUserParams['port']) + && $this->currentUserParams['port'] == 3306 + ) { + $this->currentUserParams['driver'] = 'mysql'; + } + + if (!isset($this->currentUserParams['driver'])) { + throw new DBALException('Driver must be declare.', E_USER_ERROR); + } + if (! is_string($this->currentUserParams['driver'])) { + throw new DBALException('Driver must as a string.', E_USER_ERROR); + } + + if (isset($this->currentUserParams['port'])) { + if ($this->currentUserParams['port']) { + if (!is_numeric($this->currentUserParams['port'])) { + throw new DBALException('Invalid database port.', E_USER_ERROR); + } + } else { + unset($this->currentUserParams['port']); + } + } + + /** + * Sanitize Selected Driver + */ + $this->currentSelectedDriver = $this + ->sanitizeSelectedAvailableDriver($this->currentUserParams['driver']); + if (!$this->currentSelectedDriver) { + throw new DBALException('Selected driver unavailable.', E_USER_ERROR); + } + + if (isset($this->currentUserParams['prefix']) && is_string($this->currentUserParams['prefix'])) { + $this->currentUserParams['prefix'] = trim($this->currentUserParams['prefix']); + $this->currentTablePrefix = (string) $this->currentUserParams['prefix']; + } + if (isset($this->currentUserParams['dbname']) && !isset($this->currentUserParams['name'])) { + $this->currentUserParams['name'] = $this->currentUserParams['dbname']; + } + + if ((!isset($this->currentUserParams['name']) || !is_string($this->currentUserParams['name'])) + && $this->currentSelectedDriver != 'pdo_sqlite' + ) { + throw new DBALException('Invalid Database Name.', E_USER_ERROR); + } + + if ($this->currentSelectedDriver == 'pdo_sqlite' && !isset($this->currentUserParams['path'])) { + if (!isset($this->currentUserParams['name']) || !is_string($this->currentUserParams['name'])) { + throw new DBALException('SQLite database path must be not empty.', E_USER_ERROR); + } + $this->currentUserParams['path'] = $this->currentUserParams['name']; + } + + $this->currentUserParams['dbname'] = $this->currentUserParams['name']; + unset($this->currentUserParams['name']); + + if (is_string($this->currentUserParams['charset']) + && strpos($this->currentUserParams['charset'], '-') + ) { + $this->currentUserParams['charset'] = str_replace( + '-', + '', + trim(strtolower($this->currentUserParams['charset'])) + ); + } + + if (!is_string($this->currentUserParams['charset']) + || trim($this->currentUserParams['charset']) == '' + ) { + $charset = 'utf8'; + if (isset($this->currentUserParams['collate'])) { + $collate = $this->currentUserParams['collate']; + if (!is_string($collate)) { + $collate = 'utf8_unicode_ci'; + } + $collate = preg_replace('`(\-|\_)+`', '_', $collate); + $collate = trim(strtolower($collate)); + $this->currentUserParams['collate'] = $collate; + $collateArray = explode('_', $collate); + $charset = reset($collateArray); + } + + $this->currentUserParams['charset'] = $charset; + } + + /** + * create new parameters + */ + $this->currentUserParams['driver'] = $this->currentSelectedDriver; + + /** + * Create New Connection + */ + $this->currentConnection = DriverManager::getConnection($this->currentUserParams); + + /** + * Set Quote Identifier + */ + $this->currentQuoteIdentifier = $this-> + currentConnection + ->getDatabasePlatform() + ->getIdentifierQuoteCharacter(); + } + + /** + * Getting Doctrine Connection + * + * @return Connection + */ + public function getConnection() : Connection + { + return $this->currentConnection; + } + + /** + * Get Table Prefix + * + * @return string + */ + public function getTablePrefix() : string + { + return $this->currentTablePrefix; + } + + /** + * Get identifier + * + * @return string + */ + public function getQuoteIdentifier() : string + { + return $this->currentQuoteIdentifier; + } + + /** + * Get user params + * + * @return array + */ + public function getUserParams() : array + { + return $this->currentUserParams; + } + + /** + * Get Connection params + * + * @return array + */ + public function getConnectionParams() : array + { + return $this->getParams(); + } + + /** + * Check Database driver available for Doctrine + * and choose the best driver of sqlsrv an oci + * + * @param string $driverName + * @final + * @return bool|string return lowercase an fix database driver for Connection + */ + final public function sanitizeSelectedAvailableDriver(string $driverName) + { + if (is_string($driverName) && trim($driverName)) { + $driverName = trim(strtolower($driverName)); + /** + * switch to Doctrine fixed db + * Aliases + */ + $driverSchemeAliases = [ + 'db2' => 'ibm_db2', + 'drizzle' => 'drizzle_pdo_mysql', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason + 'postgre' => 'pdo_pgsql', + 'postgre_sql'=> 'pdo_pgsql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + 'oci' => 'oci8', + 'pdo_oci' => 'oci8', # recommendation pdo_oci uses oci8 + 'pdo_sqlsrv' => 'sqlsrv', #recommendation pdo_sqlsrv uses sqlsrv + ]; + if (isset($driverSchemeAliases[$driverName])) { + $driverName = $driverSchemeAliases[$driverName]; + } + + if (in_array($driverName, DriverManager::getAvailableDrivers())) { + return $driverName; + } + } + + return false; + } + + /** + * Trimming table for safe usage + * + * @param mixed $table + * @return mixed + */ + public function trimTableSelector($table) + { + if (is_array($table)) { + foreach ($table as $key => $value) { + $table[$key] = $this->trimTableSelector($value); + } + return $table; + } elseif (is_object($table)) { + foreach (get_object_vars($table) as $key => $value) { + $table->{$key} = $this->trimTableSelector($value); + } + return $table; + } + if (is_string($table)) { + $tableArray = explode('.', $table); + foreach ($tableArray as $key => $value) { + $tableArray[$key] = trim( + trim( + trim($value), + $this->currentQuoteIdentifier + ) + ); + } + $table = implode('.', $tableArray); + } + + return $table; + } + + /** + * Alternative multi variable type quoted identifier + * + * @param mixed $quoteStr + * @return mixed + * @throws \InvalidArgumentException + */ + public function quoteIdentifiers($quoteStr) + { + if ($quoteStr instanceof \Closure || is_resource($quoteStr)) { + throw new \InvalidArgumentException( + "Invalid value to be quote, quote value could not be instance of `Closure` or as a `Resource`", + E_USER_ERROR + ); + } + + $quoteStr = $this->trimTableSelector($quoteStr); + if (is_array($quoteStr)) { + foreach ($quoteStr as $key => $value) { + $quoteStr[$key] = $this->quoteIdentifiers($value); + } + return $quoteStr; + } elseif (is_object($quoteStr)) { + foreach (get_object_vars($quoteStr) as $key => $value) { + $quoteStr->{$key} = $this->quoteIdentifiers($value); + } + return $quoteStr; + } + + $return = $this->quoteIdentifier($quoteStr); + + return $return; + } + + /** + * Alternative multi variable type quote string + * Nested quotable + * + * @param mixed $quoteStr + * @param int $type + * @return array|mixed|string + */ + public function quotes($quoteStr, $type = \PDO::PARAM_STR) + { + if ($quoteStr instanceof \Closure || is_resource($quoteStr)) { + throw new \InvalidArgumentException( + "Invalid value to be quote, quote value could not be instance of `Closure` or as a `Resource`", + E_USER_ERROR + ); + } + + $quoteStr = $this->trimTableSelector($quoteStr); + if (is_array($quoteStr)) { + foreach ($quoteStr as $key => $value) { + $quoteStr[$key] = $this->quotes($value, $type); + } + return $quoteStr; + } elseif (is_object($quoteStr)) { + foreach (get_object_vars($quoteStr) as $key => $value) { + $quoteStr->{$key} = $this->quotes($value, $type); + } + return $quoteStr; + } + + return $this->quote($quoteStr); + } + + /** + * Prefix CallBack + * + * @access private + * @param string $table the table + * @return string + */ + private function prefixTableCallback(string $table) : string + { + $prefix = $this->getTablePrefix(); + if (!empty($prefix) && is_string($prefix) && trim($prefix)) { + $table = (strpos($table, $prefix) === 0) + ? $table + : $prefix.$table; + } + + return $table; + } + + /** + * Prefixing table with predefined table prefix on configuration + * + * @param mixed $table + * @param bool $use_identifier + * @return array|null|string + */ + public function prefixTables($table, bool $use_identifier = false) + { + if ($table instanceof \Closure || is_resource($table)) { + throw new \InvalidArgumentException( + "Invalid value to be quote, table value could not be instance of `Closure` or as a `Resource`", + E_USER_ERROR + ); + } + + $prefix = $this->getTablePrefix(); + if (is_array($table)) { + foreach ($table as $key => $value) { + $table[$key] = $this->prefixTables($value, $use_identifier); + } + return $table; + } + if (is_object($table)) { + foreach (get_object_vars($table) as $key => $value) { + $table->{$key} = $this->prefixTables($value, $use_identifier); + } + return $table; + } + if (!is_string($table)) { + return null; + } + if (strpos($table, $this->currentQuoteIdentifier) !== false) { + $use_identifier = true; + } + if (!empty($prefix) && is_string($prefix) && trim($prefix)) { + $tableArray = explode('.', $table); + $tableArray = $this->trimTableSelector($tableArray); + if (count($tableArray) > 1) { + $connectionParams = $this->getConnectionParams(); + if (isset($connectionParams['db.name']) && $tableArray[0] == $connectionParams['db.name']) { + $tableArray[1] = $this->prefixTableCallback($tableArray); + } + if ($use_identifier) { + return $this->currentQuoteIdentifier + . implode("{$this->currentQuoteIdentifier}.{$this->currentQuoteIdentifier}", $tableArray) + . $this->currentQuoteIdentifier; + } else { + return implode(".", $tableArray); + } + } else { + $table = $this->prefixTableCallback($tableArray[0]); + } + } + + return $use_identifier + ? $this->currentQuoteIdentifier.$table.$this->currentQuoteIdentifier + : $table; + } + + /** + * @uses Database::prefixTables() + * + * @param mixed $tables + * @param bool $use_identifier + * @return mixed + */ + public function prefix($tables, bool $use_identifier = false) + { + return $this->prefixTables($tables, $use_identifier); + } + + /** + * Compile Bindings + * Take From CI 3 Database Query Builder, default string Binding use Question mark ( ? ) + * + * @param string $sql sql statement + * @param array $binds array of bind data + * @return mixed + */ + public function compileBindsQuestionMark(string $sql, $binds = null) + { + if (empty($binds) || strpos($sql, '?') === false) { + return $sql; + } elseif (! is_array($binds)) { + $binds = [$binds]; + $bind_count = 1; + } else { + // Make sure we're using numeric keys + $binds = array_values($binds); + $bind_count = count($binds); + } + // Make sure not to replace a chunk inside a string that happens to match the bind marker + if ($countMatches = preg_match_all("/'[^']*'/i", $sql, $matches)) { + $countMatches = preg_match_all( + '/\?/i', # regex + str_replace( + $matches[0], + str_replace('?', str_repeat(' ', 1), $matches[0]), + $sql, + $countMatches + ), + $matches, # matches + PREG_OFFSET_CAPTURE + ); + // Bind values' count must match the count of markers in the query + if ($bind_count !== $countMatches) { + return false; + } + } elseif (($countMatches = preg_match_all('/\?/i', $sql, $matches, PREG_OFFSET_CAPTURE)) !== $bind_count) { + return $sql; + } + + do { + $countMatches--; + $escapedValue = is_int($binds[$countMatches]) + ? $binds[$countMatches] + : $this->quote($binds[$countMatches]); + if (is_array($escapedValue)) { + $escapedValue = '('.implode(',', $escapedValue).')'; + } + $sql = substr_replace($sql, $escapedValue, $matches[0][$countMatches][1], 1); + } while ($countMatches !== 0); + + return $sql; + } + + /** + * Query using binding optionals statements + * + * @uses compileBindsQuestionMark + * @param string $sql + * @param mixed $statement array|string|null + * @return Statement + * @throws DBALException + */ + public function queryBind(string $sql, $statement = null) + { + $sql = $this->compileBindsQuestionMark($sql, $statement); + if ($sql === false) { + throw new DBALException( + sprintf( + 'Invalid statement binding count with sql query : %s', + $sql + ), + E_USER_WARNING + ); + } + + return $this->query($sql); + } + + /** + * -------------------------------------------------------------- + * SCHEMA + * + * Lists common additional Methods just for check & lists only + * to use more - please @uses Database::getSchemaManager() + * + * @see AbstractSchemaManager + * + * ------------------------------------------------------------ */ + + /** + * Check Table Maybe Invalid + * + * @param string $tableName + * @return string + * @throws \InvalidArgumentException + */ + protected function tableMaybeInvalid($tableName) : string + { + if (!is_string($tableName)) { + throw new \InvalidArgumentException( + 'Invalid table name type. Table name must be as string', + E_USER_ERROR + ); + } + + $tableName = trim($tableName); + if ($tableName == '') { + throw new \InvalidArgumentException( + 'Invalid parameter table name. Table name could not be empty.', + E_USER_ERROR + ); + } + + return $tableName; + } + + /** + * Get List Available Databases + * + * @return array + */ + public function listDatabases() : array + { + return $this->getSchemaManager()->listDatabases(); + } + + /** + * Returns a list of all namespaces in the current database. + * + * @return array + */ + public function listNamespaceNames() : array + { + return $this->getSchemaManager()->listDatabases(); + } + + /** + * Lists the available sequences for this connection. + * + * @return Sequence[] + */ + public function listSequences() : array + { + return $this->getSchemaManager()->listSequences(); + } + + /** + * Get Doctrine Column of table + * + * @param string $tableName + * @return Column[] + */ + public function listTableColumns(string $tableName) : array + { + $tableName = $this->tableMaybeInvalid($tableName); + return $this + ->getSchemaManager() + ->listTableColumns($tableName); + } + + /** + * Lists the indexes for a given table returning an array of Index instances. + * + * Keys of the portable indexes list are all lower-cased. + * + * @param string $tableName The name of the table. + * + * @return Index[] + */ + public function listTableIndexes(string $tableName) : array + { + $tableName = $this->tableMaybeInvalid($tableName); + return $this + ->getSchemaManager() + ->listTableIndexes($tableName); + } + + /** + * Check if table is Exists + * + * @param string $tables + * @return bool + * @throws \InvalidArgumentException + */ + public function tablesExist($tables) + { + if (! is_string($tables) && !is_array($tables)) { + throw new \InvalidArgumentException( + 'Invalid table name type. Table name must be as string or array', + E_USER_ERROR + ); + } + + $tables = $this->prefixTables($tables); + ! is_array($tables) && $tables = [$tables]; + return $this + ->getSchemaManager() + ->tablesExist($tables); + } + + /** + * Returns a list of all tables in the current Database Connection. + * + * @return array + */ + public function listTableNames() + { + return $this + ->getSchemaManager() + ->listTableNames(); + } + + /** + * Get List Table + * + * @return Table[] + */ + public function listTables() + { + return $this + ->getSchemaManager() + ->listTables(); + } + + /** + * Get Object Doctrine Table from Table Name + * + * @param string $tableName + * + * @return Table + */ + public function listTableDetails(string $tableName) + { + $tableName = $this->tableMaybeInvalid($tableName); + return $this->getSchemaManager()->listTableDetails($tableName); + } + + /** + * Lists the views this connection has. + * + * @return View[] + */ + public function listViews() + { + return $this->getSchemaManager()->listViews(); + } + + /** + * Lists the foreign keys for the given table. + * + * @param string $tableName The name of the table. + * + * @return ForeignKeyConstraint[] + */ + public function listTableForeignKeys(string $tableName) + { + $tableName = $this->tableMaybeInvalid($tableName); + return $this->getSchemaManager()->listTableForeignKeys($tableName); + } + + + /** + * Magic Method __call - calling arguments for backward compatibility + * + * @uses Connection + * + * @param string $method method object : + * @see Connection + * @param array $arguments the arguments list + * @return mixed + * @throws DBALException + */ + public function __call(string $method, array $arguments) + { + /** + * check if method exists on connection @see Connection ! + */ + if (method_exists($this->getConnection(), $method)) { + return call_user_func_array([$this->getConnection(), $method], $arguments); + } + throw new DBALException( + sprintf( + "Call to undefined Method %s", + $method + ), + E_USER_ERROR + ); + } +} diff --git a/App/Classes/Exceptions/EmbeddedNotFoundException.php b/App/Classes/Exceptions/EmbeddedNotFoundException.php new file mode 100644 index 0000000..a4b0b37 --- /dev/null +++ b/App/Classes/Exceptions/EmbeddedNotFoundException.php @@ -0,0 +1,10 @@ +path = $path; + if (func_num_args() > 1) { + $this->message = $message; + } else { + $this->message = "Invalid path of {$path}"; + } + } + + /** + * @return string + */ + public function getPath() : string + { + return $this->path; + } +} diff --git a/App/Classes/Extension.php b/App/Classes/Extension.php new file mode 100644 index 0000000..80fcbc3 --- /dev/null +++ b/App/Classes/Extension.php @@ -0,0 +1,13 @@ +filters = new Collection(); + $this->merged = new Collection(); + $this->current = new Collection(); + } + + /** + * Create Unique ID if function is not string + * + * @param callable $function function to call + * + * @access private + * @return string|bool + */ + final private function uniqueId($function) + { + if (is_string($function)) { + return $function; + } + + if (is_object($function)) { + // Closures are currently implemented as objects + $function = [ $function, '' ]; + } elseif (!is_array($function)) { + $function = [ $function ]; + } + + $function = array_values($function); + if (is_object($function[0])) { + return \spl_object_hash($function[0]) . $function[1]; + } elseif (count($function) > 1 || is_string($function[0])) { + // call as static + return $function[0] . '::' . $function[1]; + } + + // unexpected result + return null; + } + + /** + * Sanitize Key + * + * @param string $keyName + * @return bool|string + */ + protected function sanitizeKeyName($keyName) + { + return is_string($keyName) && trim($keyName) != '' + ? trim($keyName) + : false; + } + + /** + * Add Hooks Function it just like a WordPress add_action() / add_filter() hooks + * + * @param string $hookName Hook Name + * @param Callable $callable Callable + * @param integer $priority priority + * @param integer $acceptedArguments num count of accepted args / parameter + * + * @return boolean + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function add( + $hookName, + callable $callable, + $priority = 10, + $acceptedArguments = 1 + ) { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName) { + throw new \InvalidArgumentException( + 'Invalid Hook Name Specified', + E_USER_ERROR + ); + } + + $id = $this->uniqueId($callable); + if ($id === null) { + throw new \RuntimeException( + sprintf( + 'Invalid callable specified on hook name %s', + $hookName + ), + E_USER_ERROR + ); + } + $priority = !is_numeric($priority) + ? 10 + : abs(intval($priority)); + + if (!$this->filters->has($hookName)) { + $this->filters[$hookName] = new Collection(); + } + if (!$this->filters[$hookName]->has($priority)) { + $this->filters[$hookName]->set($priority, new Collection()); + } + + $this + ->filters + ->get($hookName) + ->get($priority) + ->set( + $id, + [ + self::KEY_FUNCTION => $callable, + self::KEY_ACCEPTED_ARGS => $acceptedArguments + ] + ); + + // remove merged hook + $this->merged->remove($hookName); + return true; + } + + /** + * Check if hook name exists + * + * @param string $hookName Hook name + * @param string|mixed $functionToCheck Specially Functions on Hook + * + * @return boolean|int + */ + public function exists($hookName, $functionToCheck = false) + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName || ! $this->filters->has($hookName)) { + return false; + } + + // Don't reset the internal array pointer + $has = $this->filters[$hookName]->isEmpty(); + // Make sure at least one priority has a filter callback + if ($has) { + $exists = false; + foreach ($this->filters[$hookName] as $callbacks) { + if (! empty($callbacks)) { + $exists = true; + break; + } + } + + if (! $exists) { + $has = false; + } + } + + // recheck + if ($functionToCheck === false || $has === false) { + return $has; + } + + if (! $id = $this->uniqueId($functionToCheck)) { + return false; + } + + foreach ($this->filters[$hookName] as $priority) { + if ($priority->has($id)) { + return $priority; + } + } + + return false; + } + + /** + * Applying Hooks for replaceable and returning as $value param + * + * @param string $hookName Hook Name replaceable + * @param mixed $value returning value + * + * @return mixed + */ + public function apply($hookName, $value) + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName) { + throw new \InvalidArgumentException( + 'Invalid Hook Name Specified', + E_USER_ERROR + ); + } + + if (!$this->filters->has($hookName)) { + return $value; + } + + // add increment data + $this->current->increment($hookName); + + /** + * Sorting + */ + if ($this->merged->has($hookName)) { + $this->filters[$hookName]->kSort(); + $this->merged->set($hookName, true); + } + + // reset sorting position + $this->filters[$hookName]->reset(); + $args = func_get_args(); + do { + foreach ($this->filters[$hookName]->current() as $collection) { + if (!is_null($collection[self::KEY_FUNCTION])) { + $args[1] = $value; + $value = call_user_func_array( + $collection[self::KEY_FUNCTION], + array_slice( + $args, + 1, + (int) $collection[self::KEY_ACCEPTED_ARGS] + ) + ); + } + } + } while ($this->filters[$hookName]->next() !== false); + + $this->current->pop(); + + return $value; + } + /** + * Call hook from existing declared hook record + * + * @param string $hookName Hook Name + * @param string $arg the arguments for next parameter + * + * @return boolean + */ + public function call($hookName, $arg = '') + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName) { + return false; + } + + if (! isset($this->actions[$hookName])) { + $this->actions[$hookName] = 1; + } else { + $this->actions[$hookName]++; + } + + if (! $this->filters->has($hookName)) { + return null; + } + + $this->current->increment($hookName); + + $args = []; + if (is_array($arg) && 1 == count($arg) && isset($arg[0]) && is_object($arg[0])) { + $args[] =& $arg[0]; + } else { + $args[] = $arg; + } + + for ($a = 2, $num = func_num_args(); $a < $num; $a++) { + $args[] = func_get_arg($a); + } + + // Sort + if (! $this->merged->has($hookName)) { + $this->filters[$hookName]->kSort(); + $this->merged->set($hookName, true); + } + $this->filters[$hookName]->reset(); + do { + foreach ($this->filters[$hookName]->current() as $collection) { + if (!is_null($collection[self::KEY_FUNCTION])) { + call_user_func_array( + $collection[self::KEY_FUNCTION], + array_slice( + $args, + 0, + (int) $collection[self::KEY_ACCEPTED_ARGS] + ) + ); + } + } + } while ($this->filters[$hookName]->next() !== false); + + $this->current->pop(); + return true; + } + + /** + * Replace Hooks Function, this will replace all existing hooks + * + * @param string $hookName Hook Name + * @param string $functionToReplace Function to replace + * @param Callable $callable Callable + * @param integer $priority priority + * @param integer $acceptedArguments num count of accepted args / parameter + * @param boolean $create true if want to create new if not exists + * + * @return boolean + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function replace( + $hookName, + $functionToReplace, + $callable, + $priority = 10, + $acceptedArguments = 1, + $create = true + ) { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName) { + throw new \InvalidArgumentException( + "Invalid Hook Name Specified", + E_USER_ERROR + ); + } + + if (!is_callable($callable)) { + throw new \RuntimeException( + "Invalid Hook Callable Specified", + E_USER_ERROR + ); + } + + if (($has = $this->exists($hookName, $functionToReplace)) || $create) { + $has && $this->remove($hookName, $functionToReplace); + // add hooks first + return $this->add($hookName, $callable, $priority, $acceptedArguments); + } + + return false; + } + + /** + * Removing Hook (remove single hook) + * + * @param string $hookName Hook Name + * @param string $functionToRemove functions that to remove from determine $hookName + * @param integer $priority priority + * + * @return boolean + */ + public function remove($hookName, $functionToRemove, $priority = 10) + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName) { + return false; + } + + $functionToRemove = $this->uniqueId($functionToRemove); + $r = $this->filters[$hookName]->get($priority); + $r = $r ? $r->has($functionToRemove) : false; + if ($r === true) { + $this->filters[$hookName][$priority]->remove($functionToRemove); + if ($this->filters[$hookName][$priority]->isEmpty()) { + $this->filters[$hookName]->remove($priority); + } + if ($this->filters[$hookName]->isEmpty()) { + $this->filters[$hookName]->clear(); + } + $this->merged->remove($hookName); + } + + return $r; + } + + /** + * Remove all of the hooks from a filter. + * + * @param string $hookName The filter to remove hooks from. + * @param int|bool $priority Optional. The priority number to remove. Default false. + * + * @return boolean + */ + public function removeAll($hookName, $priority = false) + { + if (isset($this->filters[$hookName])) { + if (false === $priority || $priority === null) { + $this->filters[$hookName]->clear(); + } elseif ($this->filters[$hookName]->has($priority)) { + $this->filters[$hookName][$priority]->clear(); + } + } + + $this->merged->remove($hookName); + return true; + } + + /** + * Current position + * + * @return string functions + */ + public function current() + { + return $this->current->end(); + } + + /** + * Count all existences Hook + * + * @param string $hookName Hook name + * + * @return integer Hooks Count + */ + public function count($hookName) + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName || ! $this->filters->has($hookName)) { + return false; + } + return $this->filters[$hookName]->count(); + } + + /** + * Check if hook has doing + * + * @param string $hookName Hook name + * + * @return boolean true if has doing + */ + public function hasDoing($hookName = null) + { + if (null === $hookName) { + return ! empty($this->current); + } + + $hookName = $this->sanitizeKeyName($hookName); + return $hookName && $this->current->has($hookName); + } + + /** + * Check if action hook as execute + * + * @param string $hookName Hook Name + * + * @return integer Count of hook action if has did action + */ + public function hasCalled($hookName) + { + $hookName = $this->sanitizeKeyName($hookName); + if (!$hookName || ! isset($this->actions[$hookName])) { + return 0; + } + + return $this->actions[$hookName]; + } +} diff --git a/App/Classes/HttpHandler/Error.php b/App/Classes/HttpHandler/Error.php new file mode 100644 index 0000000..1ff6b80 --- /dev/null +++ b/App/Classes/HttpHandler/Error.php @@ -0,0 +1,55 @@ +app = $app; + parent::__construct($displayErrorDetails); + } + + /** + * {@inheritdoc} + */ + public function __invoke( + ServerRequestInterface $request, + ResponseInterface $response, + \Exception $exception + ) { + if ($this->app instanceof Application) { + /** @var Logger $log */ + $log = $this->app[CONTAINER_LOG]; + $log->error( + $exception->getMessage(), + [ + 'file' => $exception->getFile(), + 'code' => $exception->getCode(), + 'line' => $exception->getLine() + ] + ); + } + + return parent::__invoke($request, $response, $exception); + } +} diff --git a/App/Classes/HttpHandler/PhpError.php b/App/Classes/HttpHandler/PhpError.php new file mode 100644 index 0000000..99251cc --- /dev/null +++ b/App/Classes/HttpHandler/PhpError.php @@ -0,0 +1,55 @@ +app = $app; + parent::__construct($displayErrorDetails); + } + + /** + * {@inheritdoc} + */ + public function __invoke( + ServerRequestInterface $request, + ResponseInterface $response, + \Throwable $error + ) { + if ($this->app instanceof Application) { + /** @var Logger $log */ + $log = $this->app[CONTAINER_LOG]; + $log->error( + $error->getMessage(), + [ + 'file' => $error->getFile(), + 'code' => $error->getCode(), + 'line' => $error->getLine() + ] + ); + } + + return parent::__invoke($request, $response, $error); + } +} diff --git a/App/Classes/Interfaces/BaseRouteInterface.php b/App/Classes/Interfaces/BaseRouteInterface.php new file mode 100644 index 0000000..b4886d0 --- /dev/null +++ b/App/Classes/Interfaces/BaseRouteInterface.php @@ -0,0 +1,13 @@ +setMimeType('text/html'); + } + + /** + * {@inheritdoc} + */ + public function serve(): ResponseInterface + { + $body = new Body(fopen('php://temp', 'r+')); + $data = $this->getData(); + settype($data, 'string'); + $body->write($data); + + $response = $this + ->getResponse() + ->withBody($body) + ->withStatus($this->getStatusCode()) + ->withHeader('Content-Type', $this->getContentType()); + + if ($response->hasHeader('Content-Length')) { + $response = $response->withHeader( + 'Content-Length', + $response->getBody()->getSize() + ); + } + + return $response; + } +} diff --git a/App/Classes/ReponseGenerator/Json.php b/App/Classes/ReponseGenerator/Json.php new file mode 100644 index 0000000..336badd --- /dev/null +++ b/App/Classes/ReponseGenerator/Json.php @@ -0,0 +1,46 @@ +setMimeType('application/json'); + } + + /** + * {@inheritdoc} + */ + public function serve(): ResponseInterface + { + /** @var Response $response */ + $response = $this->getResponse(); + $response = $response->withJson( + $this->getData(), + $this->getStatusCode(), + $this->getEncoding() + ); + + if ($response->hasHeader('Content-Length')) { + $response = $response->withHeader( + 'Content-Length', + $response->getBody()->getSize() + ); + } + + return $response; + } +} diff --git a/App/Classes/ReponseGenerator/Text.php b/App/Classes/ReponseGenerator/Text.php new file mode 100644 index 0000000..0b00e54 --- /dev/null +++ b/App/Classes/ReponseGenerator/Text.php @@ -0,0 +1,21 @@ +setMimeType('text/plain'); + } +} diff --git a/App/Classes/ReponseGenerator/Xml.php b/App/Classes/ReponseGenerator/Xml.php new file mode 100644 index 0000000..cc64eef --- /dev/null +++ b/App/Classes/ReponseGenerator/Xml.php @@ -0,0 +1,57 @@ +setMimeType('application/xml'); + } + + /** + * {@inheritdoc} + */ + public function serve(): ResponseInterface + { + $body = new Body(fopen('php://temp', 'r+')); + $body->write( + $this->generateXML($this->getCharset(), (array) $this->getData()) + ); + $response = $this + ->getResponse() + ->withBody($body) + ->withHeader('Content-Type', $this->getContentType()); + if ($this->getStatusCode()) { + $response = $response->withStatus($this->getStatusCode()); + } + /** @var Response $response */ + if ($response->hasHeader('Content-Length')) { + $response = $response->withHeader( + 'Content-Length', + $response->getBody()->getSize() + ); + } + + return $response; + } +} diff --git a/App/Classes/Session.php b/App/Classes/Session.php new file mode 100644 index 0000000..da836ef --- /dev/null +++ b/App/Classes/Session.php @@ -0,0 +1,280 @@ +newInstance($_COOKIE); + } + + $this->session =& $session; + $this->setSegmentName(__CLASS__); + } + + /** + * This Method also change Segments + * + * @param string $name + */ + public function setSegmentName($name) + { + if (!is_string($name)) { + throw new \InvalidArgumentException( + sprintf( + 'Session Segment Name must be as a string %s given.', + gettype($name) + ), + E_USER_ERROR + ); + } + + $this->segmentName = $name; + } + + /** + * Create Instance Session + * + * @param string|null $name + * @param AuraSession|null $session + * @return Session + * @throws \InvalidArgumentException + */ + public static function &createWithName($name = null, AuraSession $session = null) + { + $session = ! is_null($session) ? new static : new static($session); + if (is_null($name)) { + return $session; + } + + $session->setSegmentName($name); + return $session; + } + + /** + * Get object Session + * + * @return AuraSession + */ + public function &getSession() + { + return $this->session; + } + + /** + * Start Or Resume Session + * + * @return bool + */ + public function startOrResume() + { + if (!$this->session->isStarted()) { + return $this->session->start(); + } + + return $this->session->resume(); + } + + /** + * Get the Session segment + * + * @return Segment + */ + public function getSegment() + { + $segment = $this->getSession()->getSegment($this->getSegmentName()); + return $segment; + } + + /** + * Get session stored Name Value + * + * @return string + */ + public function getSegmentName() + { + return $this->segmentName; + } + + /** + * Get C.S.R.F from Aura session + * + * @return CSRFToken + */ + public function getCSRFToken() + { + return $this->getSession()->getCsrfToken(); + } + + /** + * Getting C.S.R.F token values + * + * @return string + */ + public function getCSRFTokenValue() + { + return $this->getCSRFToken()->getValue(); + } + + /** + * validate token set + * + * @param string $value + * @return bool + */ + public function validateToken($value) + { + if (!is_string($value)) { + return false; + } + + return $this->getCSRFToken()->isValid($value); + } + + /** + * @param string $keyName + * @param mixed $value + */ + public function flash($keyName, $value) + { + $this->setFlash($keyName, $value); + } + + /** + * @param string $keyName + * @param mixed $value + */ + public function flashNow($keyName, $value) + { + $this->setFlashNow($keyName, $value); + } + + /** + * Check whether Session is exists or not on segment + * + * @param string $keyName + * @return bool + */ + public function exist($keyName) + { + # double check + return + $this->get($keyName, true) !== true + && $this->get($keyName, false) !== false; + } + + /** + * Remove Session from segment + * + * @param string $keyName + */ + public function remove($keyName) + { + if ($this->exist($keyName)) { + unset($_SESSION[$this->getSegmentName()][$keyName]); + } + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return $this->exist($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * Magic Method Call for BackWards Compatibility + * + * @uses Segment + * @param string $name + * @param array $arguments + * @return mixed + */ + public function __call($name, array $arguments) + { + $return = call_user_func_array( + [$this->getSegment(), $name], + $arguments + ); + return $return; + } +} diff --git a/App/Classes/Utilities/DatabaseSchemeTableDefinition.php b/App/Classes/Utilities/DatabaseSchemeTableDefinition.php new file mode 100644 index 0000000..dd3bcd2 --- /dev/null +++ b/App/Classes/Utilities/DatabaseSchemeTableDefinition.php @@ -0,0 +1,496 @@ + [ + * "id" => [ + * // Type Map + * "bigint", + * // Options @see \Doctrine\DBAL\Schema\Column + * [ + * "autoincrement" => true, + * "length" => 10, + * "index" => true, + * ] + * ], + * "username" => [ + * "string", + * [] + * ], + * ] + * ]; + */ +class DatabaseSchemeTableDefinition +{ + const NAME_TYPE = 'type'; + const NAME_OPTIONS = 'options'; + const NAME_PROPERTIES = 'properties'; + + /** + * @var array + */ + protected $originalScheme; + + /** + * @var array + */ + protected $schemes = []; + + /** + * @var array + */ + protected $invalidScheme = []; + + /** + * @var bool + */ + protected $hasBuild = false; + + /** + * @var Table[] + */ + protected $cachedTable; + + /** + * DatabaseSchemeUtilities constructor. + * @param array $schemes + */ + public function __construct(array $schemes) + { + $this->originalScheme = $schemes; + } + + /** + * Build Scheme + */ + protected function protectedBuildSchemes() + { + if ($this->hasBuild) { + return; + } + + $this->hasBuild = true; + /** + * List Available Properties + * + * @var array + */ + $availableOptions = [ + 'column' => [ + 'autoincrement' => 'boolean', + 'length' => 'integer', + 'precision' => 'integer', + 'scale' => 'integer', + 'unsigned' => 'boolean', + 'fixed' => 'boolean', + 'notnull' => 'boolean', + 'default' => 'mixed', + 'comment' => 'string', + ], + 'table_set' => [ + 'primarykey' => 'mixed', + ], + 'table_add' => [ + 'index' => 'mixed', + 'uniqueindex' => 'mixed', + 'unique' => 'mixed', + 'foreignkey' => 'array', + 'foreignkeyconstraint' => 'array', + ], + ]; + foreach ($this->originalScheme as $table => $definitions) { + if (!is_array($definitions)) { + $this->invalidScheme[$table] = true; + continue; + } + + foreach ($definitions as $column => $definition) { + if (isset($this->invalidScheme[$table])) { + break; + } + + $columnType = $this->resolveTypeMap(reset($definition)); + if (!$columnType) { + $this->invalidScheme[$table] = true; + break; + } + + $options = (array) next($definition); + $properties = []; + foreach ($options as $optionKey => $value) { + unset($options[$optionKey]); + if (is_numeric($optionKey)) { + continue; + } + $optionKey = strtolower($optionKey); + if (isset($availableOptions['column'][$optionKey])) { + $type = $availableOptions['column'][$optionKey]; + if ($type !== 'mixed') { + $oldValue = $value; + if (!settype($value, $type)) { + $value = $oldValue; + } + } + + $options[$optionKey] = $value; + continue; + } + if (isset($availableOptions['table_set'][$optionKey])) { + if (!is_string($value)) { + $value = (bool) $value; + } + if ($value === false) { + continue; + } + $properties['set'][$optionKey][$column] = $value === '' ? true : $value; + } elseif (isset($availableOptions['table_add'][$optionKey])) { + if ($optionKey == 'unique') { + $optionKey = 'uniqueindex'; + } elseif ($optionKey == 'foreignkeyconstraint') { + $optionKey = 'foreignkeyconstraint'; + } + $type = $availableOptions['table_add'][$optionKey]; + if ($type === 'array') { + if (gettype($value) !== 'array') { + continue; + } + if (!empty($value)) { + $properties['add'][$optionKey][$column] = $value; + } + continue; + } + + if ($optionKey == 'uniqueindex') { + $value = ! is_string($value) ? $column . '_' . (string) $value : $value; + $value = preg_replace('/[^a-z0-9\_]/i', '_', $value); + } + + if (!is_string($value)) { + $value = (bool) $value; + } + + $properties['add'][$optionKey][$column] = $value; + } + } + + if (isset($options['autoincrement']) && ! isset($properties['set']['primarykey'][$column])) { + $properties['set']['primarykey'][$column] = true; + } + + $definitions[$column] = [ + static::NAME_TYPE => $columnType, + static::NAME_OPTIONS => $options, + static::NAME_PROPERTIES => $properties + ]; + } + + if (!isset($this->invalidScheme[$table])) { + $this->schemes[$table] = $definitions; + } + } + } + + /** + * Get Build Schemes + * + * @return array + */ + public function getSchemes() + { + $this->protectedBuildSchemes(); + + return $this->schemes; + } + + /** + * Get Invalid Scheme + * + * @return array + */ + public function getInvalidSchemes() + { + $this->protectedBuildSchemes(); + return $this->invalidScheme; + } + + /** + * Convert Into Column + * + * @return array|Table[] + */ + public function getTablesFromSchemes() + { + if (!isset($this->cachedTable)) { + $this->cachedTable = []; + foreach ($this->getSchemes() as $tableName => $columns) { + $table = new Table( + $tableName + ); + foreach ($columns as $columnName => $definitions) { + $table->addColumn( + $columnName, + $definitions[static::NAME_TYPE], + $definitions[static::NAME_OPTIONS] + ); + if (isset($definitions[static::NAME_PROPERTIES]['set'])) { + foreach ($definitions[static::NAME_PROPERTIES]['set'] as $context => $value) { + foreach ($value as $newContext => $realValue) { + $args = [ [$newContext], (is_string($realValue) ? $realValue : false)]; + call_user_func_array( + [ + $table, + "set{$context}" + ], + $args + ); + } + } + } + if (($definitions[static::NAME_PROPERTIES]['add'])) { + foreach ($definitions[static::NAME_PROPERTIES]['add'] as $context => $value) { + foreach ($value as $newContext => $realValue) { + // use for Foreign Key + if (is_array($realValue)) { + $args = [$newContext, $realValue]; + $context = 'foreignkeyconstraint'; + } else { + $args = [ [$newContext], (is_string($realValue) ? $realValue : false)]; + } + call_user_func_array( + [ + $table, + "add{$context}" + ], + $args + ); + } + } + } + } + + $this->cachedTable[$tableName] = $table; + } + } + + return $this->cachedTable; + } + + /** + * As Table Use Database + * + * @param Database $database + * @return array|Table[] + */ + public function getTablesFromSchemesWith(Database $database) : array + { + $tables = $this->getTablesFromSchemes(); + foreach ($tables as $tableName => $table) { + $tables[$tableName] = new Table( + $database->prefixTables($table->getName()), + $table->getColumns(), + $table->getIndexes(), + $table->getForeignKeys(), + 0, + $table->getOptions() + ); + } + + return $tables; + } + + /** + * Get Scheme With Given Params + * + * @return Schema + */ + public function getSchemaTablesWith() : Schema + { + $database = null; + $schemaConfig = new SchemaConfig(); + $sequences = []; + $nameSpace = []; + foreach (func_get_args() as $key => $value) { + if ($value instanceof Database) { + $database = $value; + continue; + } + if ($value instanceof SchemaConfig) { + $schemaConfig = $value; + continue; + } + if (is_array($value) && count($value) > 0) { + $isSequenceValid = true; + $isNameSpaceValid = true; + foreach ($value as $seq) { + if (is_object($value) && ! $seq instanceof Sequence) { + $isSequenceValid = false; + if (!$isNameSpaceValid) { + break; + } + continue; + } + + if (!is_string($seq)) { + $isNameSpaceValid = false; + if (!$isSequenceValid) { + break; + } + } + } + + $sequences = $isSequenceValid ? $value : $sequences; + $nameSpace = $isNameSpaceValid ? $value : $nameSpace; + } + } + $tables = $database + ? $this->getTablesFromSchemesWith($database) + : $this->getTablesFromSchemes(); + return new Schema( + $tables, + $sequences, + $schemaConfig, + $nameSpace + ); + } + + /** + * Get SQL Migration Of Query as Array + * + * @param Database $database + * + * @return array|string[] + */ + public function getSQLWithDatabase(Database $database) : array + { + /** + * @var Schema $schema + */ + $schema = call_user_func_array( + [$this, 'getSchemaTablesWith'], + func_get_args() + ); + + /** + * @var Database $database + */ + return $schema->getMigrateFromSql( + $database->getSchemaManager()->createSchema(), + $database->getDatabasePlatform() + ); + } + + /** + * Get Array QUERY SQL From Create Table + * + * @param Database $database + * @return array + */ + public function getSQLCreateTableWithDatabase(Database $database) : array + { + /** + * @var Schema $schema + */ + $schema = call_user_func_array( + [$this, 'getSchemaTablesWith'], + func_get_args() + ); + + return $schema->getMigrateFromSql( + new Schema(), + $database->getDatabasePlatform() + ); + } + + /** + * Execute Query From Given Tables + * + * @param Database $database + * @return int + * @throws \Exception + */ + public function executeSQLWith(Database $database) : int + { + /** + * array + */ + $migration = call_user_func_array( + [$this, 'getSQLWithDatabase'], + func_get_args() + ); + + $count = 0; + if (!empty($migration)) { + $database->beginTransaction(); + try { + foreach ($migration as $query) { + $stmt = $database->prepare($query); + $stmt->execute(); + $stmt->closeCursor(); + $count++; + } + $database->rollBack(); + } catch (\Exception $exception) { + $database->rollBack(); + throw $exception; + } + } + + return $count; + } + + /** + * Additional Mapping Type Fix + * + * @param string $type + * @return string|null null if not match + */ + public static function resolveTypeMap($type) + { + if (!is_string($type)) { + return null; + } + + $type = strtolower($type); + preg_match( + '# + (?Pbin) + | (?Pdatetimez|timez) + | (?Pgu|id) + | (?Pdate|stamp) + | (?P