Browse Source

Basic structure, missing final steps to handle request

Herton 4 years ago
parent
commit
4fed480dc2

+ 129 - 0
Application.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace Goodquestiondev;
+
+use Illuminate\Support\Str;
+
+/**
+ * Simple application
+ */
+class Application
+{
+    /**
+     * The Application version.
+     *
+     * @var string
+     */
+    const VERSION = '0.1';
+
+    /**
+     * The Application instance.
+     *
+     * @var self
+     */
+    private static $instance = null;
+
+    /**
+     * The base path for the installation.
+     *
+     * @var string
+     */
+    protected $basePath;
+
+    /**
+     * The available controllers
+     *
+     * @var array
+     */
+    public $controllerUris = [];
+
+    /**
+     * Create a new application instance.
+     *
+     * @param  string|null  $basePath
+     * @return void
+     */
+    public function __construct($basePath = null)
+    {
+        if ($basePath) {
+            $this->basePath = rtrim($basePath, '\/');
+        }
+        $this->setControllerUris();
+    }
+
+    /**
+     * Create a new application instance following singleton pattern
+     *
+     * @return self
+     */
+
+    public static function getInstance()
+    {
+        if (self::$instance == null) {
+            self::$instance = new Application(dirname(__DIR__));
+        }
+
+        return self::$instance;
+        ;
+    }
+
+    /**
+     * Get the path to the public directory.
+     *
+     * @return string
+     */
+    public function publicPath()
+    {
+        return $this->basePath.DIRECTORY_SEPARATOR.'public';
+    }
+
+    /**
+     * Get all available controller URIs
+     *
+     * @return array An array of all the available controller URIs
+    */
+    public function setControllerUris() : void
+    {
+        $files = array_diff(scandir($this->basePath . '/Controllers'), ['..', '.']);
+
+        $uris = [];
+        foreach ($files as $file) {
+            $withoutExtension = Str::before($file, 'Controller.php');
+            if (!$withoutExtension) {
+                continue;
+            }
+            $uris[Str::snake($withoutExtension, '.')] = Str::before($file, '.php');
+        }
+
+        $this->controllerUris = $uris;
+    }
+
+    /**
+     * Get all available controller URIs
+     *
+     * @return array An array of all the available controller URIs
+    */
+    public function getControllerUris() : array
+    {
+        return $this->controllerUris;
+    }
+
+    /**
+     * Handles the response
+     *
+    */
+    public function respond()
+    {
+        return "<html>
+<head>
+<title>
+A Simple HTML Document
+</title>
+</head>
+<body>
+<p>This is a very simple HTML document</p>
+<p>It only has two paragraphs</p>
+</body>
+</html>";
+    }
+}

+ 7 - 0
Controllers/CategoriesPostsController.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Goodquestiondev\Controllers;
+
+class CategoriesPostsController extends Controller
+{
+}

+ 44 - 0
Controllers/Controller.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Goodquestiondev\SimpleRouterResource;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Controller
+{
+    /**
+     * Display the articles index.
+     */
+    public function index()
+    {
+        throw new \Exception('Undefined method '. __FUNCTION__);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     */
+    public function create()
+    {
+        throw new \Exception('Undefined method '. __FUNCTION__);
+    }
+
+    /**
+     * Store a newly created resource
+     *
+     * @param  Request  $request
+     */
+    public function store(Request $request)
+    {
+        throw new \Exception('Undefined method '. __FUNCTION__);
+    }
+
+    /**
+     * Display the specified resource
+     *
+     * @param  array $resource
+     */
+    public function show(array $resource)
+    {
+        throw new \Exception('Undefined method '. __FUNCTION__);
+    }
+}

+ 7 - 0
Controllers/PostsController.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Goodquestiondev\Controllers;
+
+class PostController extends Controller
+{
+}

+ 7 - 0
Controllers/UsersPostsController.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Goodquestiondev\Controllers;
+
+class UsersPostsController extends Controller
+{
+}

+ 21 - 0
Helpers.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Goodquestiondev;
+
+use Illuminate\Support\Str;
+
+class Helpers
+{
+    /**
+     * Get all files and folders from a specific folder exluding the dots
+     *
+     * @param string $path the path to the folder, no need to pass the / on the start or end as it gets stripped out
+     *
+     * @return array An array of all the files and folders from a specific local path
+    */
+    public static function getFiles(string $path) : array
+    {
+        $strippedPath = Str::of($path)->trim('/');
+        return array_diff(scandir(dirname(__DIR__) . $strippedPath), ['..', '.']);
+    }
+}

+ 78 - 0
Resource.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Goodquestiondev;
+
+use Illuminate\Support\Str;
+
+class Resource
+{
+    /**
+     * The default actions for a resourceful controller.
+     *
+     * @var array
+     */
+    public $resourceDefaults = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
+    
+    /**
+     * The list of possible uris
+     *
+     * @var array
+     */
+    public $uriList = [];
+
+
+    /**
+     * Create a new resource registrar instance.
+     *
+     * @param  string $type
+     * @param  string $uri
+     * @return void
+     */
+    public function __construct(string $uri)
+    {
+        $this->uri = $uri;
+        $this->setUriList($uri);
+    }
+
+    /**
+     * Get Possible Uris
+     *
+     * @param string $path The request path
+     *
+     * @return void
+     */
+    public function setUriList($uri)
+    {
+        $parts = explode(".", $uri);
+        
+        if (count($parts) == 1) {
+            $singular = Str::singular($parts[0]);
+            $this->uriList[] = "{$parts[0]}";
+            $this->uriList[] = "{$parts[0]}/{{$singular}}";
+            $this->uriList[] = "{$parts[0]}/create";
+            $this->uriList[] = "{$parts[0]}/{{$singular}}/edit";
+        }
+        if (count($parts) == 2) {
+            $singularOne = Str::singular($parts[0]);
+            $singularTwo = Str::singular($parts[1]);
+            $this->uriList[] = "{$parts[0]}";
+            $this->uriList[] = "{$parts[0]}/{{$singularOne}}/{$parts[1]}";
+            $this->uriList[] = "{$parts[0]}/{{$singularOne}}/{$parts[1]}/create";
+            $this->uriList[] = "{$parts[1]}/{{$singularTwo}}";
+            $this->uriList[] = "{$parts[1]}/{{$singularTwo}}/edit";
+        }
+    }
+
+    /**
+     * Get Possible Uris
+     *
+     * @param string $path The request path
+     *
+     * @return void
+     */
+    public function handleRequest($verb, $path, $uriInfo, $content)
+    {
+        $route = Route::getInstance();
+        $route->resources[$this->uri];
+    }
+}

+ 155 - 0
Route.php

@@ -0,0 +1,155 @@
+<?php
+
+namespace Goodquestiondev;
+
+use Illuminate\Support\Str;
+use Goodquestiondev\Resource;
+
+class Route
+{
+    /**
+    * The Application routers
+    *
+    * @var array
+    */
+    public $resources = [];
+
+    /**
+     * The Application instance.
+     *
+     * @var self
+     */
+    private static $instance = null;
+
+    /**
+     * The default actions for a resourceful controller.
+     *
+     * @var array
+     */
+    public $resourceDefaults = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
+
+    /**
+     * The verbs used in the resource URIs.
+     *
+     * @var array
+     */
+    public static $verbs = [
+        'create',
+        'edit',
+    ];
+
+    /**
+     * The possible resource keys
+     *
+     * @var array
+     */
+    public $resourceKeys = [];
+
+    /**
+     * The resource strings
+     *
+     * @var array
+     */
+    public $resourceStrings = [];
+
+    /**
+     * Create a new simple resource instance following singleton pattern
+     *
+     * @return self
+     */
+
+    public static function getInstance()
+    {
+        if (self::$instance == null) {
+            self::$instance = new self();
+        }
+    
+        return self::$instance;
+    }
+
+    /**
+     * Register resource
+     *
+     * @param string $uri The uri string
+     *
+     * @return void
+     */
+    public static function resource(string $uri)
+    {
+        $route = self::getInstance();
+        $resourceKeys = array_merge(explode(".", $uri), $route->resourceKeys);
+        $route->resourceKeys = array_unique($resourceKeys);
+        $route->resourceStrings[] = $uri;
+        $route->resources[$uri] = new Resource($uri);
+    }
+
+    /**
+     * Redirects the user to the correct controller and method based on the path
+     *
+     * @param string $verb The request verb
+     * @param string $path The path string
+     * @param string $content The request data content
+     *
+     * @return Resource
+     */
+    public static function getResource(string $verb, string $path, string $content)
+    {
+        $route = self::getInstance();
+        $uriInfo = $route->getUriInfo($path);
+        foreach ($route->resources as $resource) {
+            if (in_array($uriInfo['uri'], $resource->uriList)) {
+                $resource->handleRequest($verb, $path, $uriInfo, $content);
+            }
+        };
+    }
+
+
+    /**
+     * Get URI and their params
+     * E.q for path /users/bob/posts/999
+     * E.q: ['uri' => 'users/{key}posts/{key}', 'params' ['users' => 'bob', 'posts' => 999]]
+     *
+     * @param string $path The request path
+     *
+     * @return arrray
+     */
+    public function getUriInfo($path)
+    {
+        $path = Str::of($path)->ltrim('/')->rtrim('/');
+        $parts = explode("/", $path);
+        $params = [];
+        $uri = '';
+
+        foreach ($parts as $index => $part) {
+            $isResourceString = in_array($part, $this->resourceKeys);
+            if ($index === 0 && ! $isResourceString) {
+                throw new \Exception('Bad Url');
+            }
+
+            $isResourceVerb = in_array($part, self::$verbs);
+
+            if ($isResourceVerb) {
+                $uri .= $part;
+                break;
+            }
+
+            if ($isResourceString) {
+                $params[$part] = '';
+                $uri .= "$part/";
+            } else {
+                $params[array_key_last($params)] = $part;
+                $singular = Str::singular(array_key_last($params));
+                $uri .= "{{$singular}}/";
+            }
+        }
+
+        foreach ($params as $key => $value) {
+            unset($params[$key]);
+            $params[Str::singular($key)] = $value;
+        }
+        return [
+            'uri' => (string) Str::of($uri)->rtrim('/'),
+            'params' => $params
+        ];
+    }
+}

+ 16 - 1
composer.json

@@ -17,6 +17,21 @@
     ],
     "require": {
         "goodquestiondev/simple-router-resource": "dev-master",
+        "illuminate/support": "^8.7",
+        "php": "^7.3",
         "symfony/http-foundation": "^5.1"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^9"
+    },
+    "autoload": {
+        "psr-4": {
+            "Goodquestiondev\\": ""
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Goodquestiondev\\Tests\\": "tests/"
+        }
     }
-}
+}

+ 10 - 0
handle.php

@@ -0,0 +1,10 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Request;
+use Goodquestiondev\SimpleRouterResource\SimpleRoute;
+
+$request = Request::createFromGlobals();
+
+var_dump($request->getMethod(), $request->getContent(), $request->getPathInfo());
+
+var_dump(SimpleRoute::getHandler($request));

+ 14 - 1
index.php

@@ -6,6 +6,19 @@
 require_once __DIR__ . '/vendor/autoload.php';
 
 /*
-* Register The Auto Loader
+* Register The Application
+*/
+$app = Goodquestiondev\Application::getInstance();
+
+
+/*
+* Register The Available Routes
 */
 require_once __DIR__ . '/routes.php';
+
+/*
+* Handle the request
+*/
+require_once __DIR__ . '/handle.php';
+
+$app->respond();

+ 17 - 1
routes.php

@@ -1,3 +1,19 @@
 <?php
 
-echo 'hi';
+use Goodquestiondev\SimpleRouterResource\SimpleRoute as Route;
+
+/*
+|--------------------------------------------------------------------------
+| Web Routes
+|--------------------------------------------------------------------------
+|
+| Here is where you can register web routes for your application.
+|
+*/
+
+Route::resource('posts');
+Route::resource('users.posts');
+Route::resource('categories.posts');
+Route::get('/', function () {
+    return 'Hello';
+});

+ 35 - 0
tests/ResourceTest.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Goodquestiondev\Tests;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use Goodquestiondev\Route;
+use Goodquestiondev\Resource;
+use PHPUnit\Framework\TestCase;
+
+class ResourceTest extends TestCase
+{
+    public function testResource(): void
+    {
+        $resource = new Resource('categories.posts');
+        $this->assertEquals([
+            'categories',
+            'categories/{category}/posts',
+            'categories/{category}/posts/create',
+            'posts/{post}',
+            'posts/{post}/edit'
+        ], $resource->uriList);
+
+        $resource = new Resource('categories');
+        $this->assertEquals([
+            'categories',
+            'categories/{category}',
+            'categories/create',
+            'categories/{category}/edit',
+        ], $resource->uriList);
+
+        $route = Route::getInstance();
+        var_dump($route);
+    }
+}

+ 38 - 0
tests/RouteTest.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Goodquestiondev\Tests;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use PHPUnit\Framework\TestCase;
+use Goodquestiondev\Route;
+
+class RouteTest extends TestCase
+{
+    public function testRoute(): void
+    {
+        Route::resource('posts');
+        Route::resource('users.posts');
+        Route::resource('categories.posts');
+        
+        $route = Route::getInstance();
+        $uriInfo = $route->getUriInfo('/users/bob/posts/999');
+
+        $this->assertEquals('users/{user}/posts/{post}', $uriInfo['uri']);
+        $this->assertEquals(['user' => 'bob', 'post' => '999'], $uriInfo['params']);
+
+        $uriInfo = $route->getUriInfo('users/create');
+
+        $this->assertEquals('users/create', $uriInfo['uri']);
+        $this->assertEquals(['user' => ''], $uriInfo['params']);
+
+        $uriInfo = $route->getUriInfo('posts/bob/edit');
+
+        $this->assertEquals('posts/{post}/edit', $uriInfo['uri']);
+        $this->assertEquals(['post' => 'bob'], $uriInfo['params']);
+
+        $route = Route::getInstance();
+        
+        $this->assertEquals(3, count($route->resources));
+    }
+}

+ 27 - 0
tests/SimpleAppTest.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Goodquestiondev\Tests;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+use PHPUnit\Framework\TestCase;
+use Goodquestiondev\Application;
+
+class SimpleAppTest extends TestCase
+{
+    public function testApplicationPublicPath(): void
+    {
+        $app = new Application(dirname(__DIR__));
+        $this->assertEquals(dirname(__DIR__).'/public', $app->publicPath());
+    }
+
+    public function testControllerUris(): void
+    {
+        $app = new Application(dirname(__DIR__));
+        $this->assertEquals([
+            'categories.posts' => 'CategoriesPostsController',
+            'posts' => 'PostsController',
+            'users.posts' => 'UsersPostsController'
+        ], $app->controllerUris);
+    }
+}