🎉 initial commit
This commit is contained in:
77
classes/Application/AppEnvironment.php
Normal file
77
classes/Application/AppEnvironment.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Application;
|
||||
|
||||
use Application\Helper\ArrayPath;
|
||||
use Dotenv\Dotenv;
|
||||
use Exception;
|
||||
|
||||
class AppEnvironment
|
||||
{
|
||||
private static ?AppEnvironment $instance = null;
|
||||
|
||||
private array $environment = [];
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
* construction calls with the `new` operator.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$this->loadEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Singletons should not be cloneable.
|
||||
*/
|
||||
protected function __clone() {}
|
||||
|
||||
/**
|
||||
* Singletons should not be restorable from strings.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new Exception("Cannot unserialize a singleton.");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the static method that controls the access to the singleton
|
||||
* instance. On the first run, it creates a singleton object and places it
|
||||
* into the static field. On subsequent runs, it returns the client existing
|
||||
* object stored in the static field.
|
||||
*
|
||||
* This implementation lets you subclass the Singleton class while keeping
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static function getInstance(): AppEnvironment
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally, any singleton should define some business logic, which can be
|
||||
* executed on its instance.
|
||||
*/
|
||||
private function loadEnvironment(): void
|
||||
{
|
||||
if (!empty($this->environment)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->environment = Dotenv::createImmutable($_SERVER['DOCUMENT_ROOT'] . '/../')->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getVariable(string|array $key)
|
||||
{
|
||||
return ArrayPath::GetDataFromArray($this->environment, $key);
|
||||
}
|
||||
}
|
33
classes/Application/Breadcrumbs/Breadcrumb.php
Normal file
33
classes/Application/Breadcrumbs/Breadcrumb.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Breadcrumbs;
|
||||
|
||||
class Breadcrumb {
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @param bool $active
|
||||
* @param bool $current
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $url,
|
||||
private bool $active = true,
|
||||
private bool $current = true
|
||||
) {}
|
||||
|
||||
public function setAsNotCurrent() {
|
||||
$this->current = false;
|
||||
}
|
||||
|
||||
public function getTemplateData() {
|
||||
return array(
|
||||
'name' => $this->name,
|
||||
'url' => $this->url,
|
||||
'active' => $this->active,
|
||||
'current' => $this->current,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
42
classes/Application/Breadcrumbs/Breadcrumbs.php
Normal file
42
classes/Application/Breadcrumbs/Breadcrumbs.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Breadcrumbs;
|
||||
|
||||
class Breadcrumbs {
|
||||
|
||||
/**
|
||||
* @var Breadcrumb[]
|
||||
*/
|
||||
private array $breadcrumbs;
|
||||
|
||||
public function __construct() {
|
||||
$this->breadcrumbs[] = new Breadcrumb('Start', '/');
|
||||
}
|
||||
|
||||
public function addBreadcrumb(string $name, string $url, bool $active = true, $current = true) {
|
||||
if ($current) {
|
||||
$this->setAllNonCurrent();
|
||||
}
|
||||
|
||||
if (empty($url)) {
|
||||
$active = false;
|
||||
}
|
||||
|
||||
$this->breadcrumbs[] = new Breadcrumb($name, $url, $active, $current);
|
||||
}
|
||||
|
||||
private function setAllNonCurrent(): void {
|
||||
foreach ($this->breadcrumbs as $breadcrumb) {
|
||||
$breadcrumb->setAsNotCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
public function getTemplateData() {
|
||||
$return = array();
|
||||
foreach ($this->breadcrumbs as $breadcrumb) {
|
||||
$return[] = $breadcrumb->getTemplateData();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
139
classes/Application/Content/CockpitApi.php
Normal file
139
classes/Application/Content/CockpitApi.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Content;
|
||||
|
||||
use Application\AppEnvironment;
|
||||
use Application\Models\Article;
|
||||
use Application\Models\Site;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use JMS\Serializer\SerializerBuilder;
|
||||
|
||||
class CockpitApi
|
||||
{
|
||||
private ?Client $client = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array $variables
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getApiPath(string $type = 'item', array $variables = []): string
|
||||
{
|
||||
$path = match ($type) {
|
||||
'itemByModel' => '/content/item/{model}',
|
||||
'itemsByModel' => '/content/items/{model}',
|
||||
'itemByModelAndId' => '/content/item/{model}/{id}',
|
||||
'items' => '/content/items',
|
||||
'aggregateByModel' => '/content/aggregate/{model}',
|
||||
'treeByModel' => '/content/tree/{model}',
|
||||
'healthcheck' => '/system/healthcheck',
|
||||
default => throw new Exception("Type '$type' does not exist."),
|
||||
};
|
||||
|
||||
foreach ($variables as $search => $replace) {
|
||||
$path = str_replace('{' . $search . '}', $replace, $path);
|
||||
}
|
||||
|
||||
if (str_contains($path, '{')) {
|
||||
throw new Exception("Non replaced variable in path '$path'.");
|
||||
}
|
||||
|
||||
return AppEnvironment::getInstance()->getVariable('COCKPIT_API_PATH') . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getGlobalData(): Site
|
||||
{
|
||||
$res = $this->client->request('GET', $this->getApiPath('itemByModel', ['model' => 'site']), [
|
||||
'headers' => [
|
||||
'api-key' => AppEnvironment::getInstance()->getVariable('COCKPIT_API_KEY'),
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->deserialize($res->getBody()->getContents(), Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getArticles(): Article|array
|
||||
{
|
||||
$res = $this->client->request('GET', $this->getApiPath('itemsByModel', ['model' => 'article']), [
|
||||
'headers' => [
|
||||
'api-key' => AppEnvironment::getInstance()->getVariable('COCKPIT_API_KEY'),
|
||||
],
|
||||
'query' => [
|
||||
'filter' => null, # '{fieldA:"test"}'
|
||||
'fields' => null,
|
||||
'sort' => null,
|
||||
'limit' => 10,
|
||||
'skip' => 0,
|
||||
'populate' => 1,
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->deserialize($res->getBody()->getContents(), Article::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getArticle(string $id): Article
|
||||
{
|
||||
$res = $this->client->request('GET', $this->getApiPath('itemByModelAndId', ['model' => 'article', 'id' => $id]), [
|
||||
'headers' => [
|
||||
'api-key' => AppEnvironment::getInstance()->getVariable('COCKPIT_API_KEY'),
|
||||
],
|
||||
'query' => [
|
||||
'filter' => null, # "{_id:$id}",
|
||||
'fields' => null,
|
||||
'sort' => null,
|
||||
'limit' => 1,
|
||||
'skip' => 0,
|
||||
'populate' => 1,
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->deserialize($res->getBody()->getContents(), Article::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize JSON data into objects.
|
||||
*
|
||||
* @param string $json
|
||||
* @param string $type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function deserialize(string $json, string $type): mixed
|
||||
{
|
||||
$array = json_decode($json, true);
|
||||
|
||||
if (!isset($array['data'])) {
|
||||
return SerializerBuilder::create()->build()->deserialize($json, $type, 'json');
|
||||
}
|
||||
|
||||
$return = [];
|
||||
foreach ($array['data'] as $data) {
|
||||
$json = json_encode($data);
|
||||
dump($json);
|
||||
$return[] = SerializerBuilder::create()->build()->deserialize($json, $type, 'json');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
38
classes/Application/Controller/ArticleController.php
Normal file
38
classes/Application/Controller/ArticleController.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Controller;
|
||||
|
||||
use Application\Content\CockpitApi;
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class ArticleController extends MainController
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
protected function action(): void
|
||||
{
|
||||
$article = (new CockpitApi())->getArticle($this->getRouteParams('id'));
|
||||
|
||||
$this->pushToStash('article', [
|
||||
'subtitle' => $this->faker->sentence(2),
|
||||
'title' => $article->getTitle(),
|
||||
'lead' => $this->faker->paragraph(),
|
||||
'text' => $article->getText(),
|
||||
'image' => [
|
||||
'src' => 'https://picsum.photos/seed/' . $this->faker->randomDigit() . '/1920/1080',
|
||||
'width' => 1920,
|
||||
'height' => 1080,
|
||||
'alt' => 'lorem ipsum',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTemplate(): string
|
||||
{
|
||||
return '04_templates/t-article/t-article.twig';
|
||||
}
|
||||
}
|
34
classes/Application/Controller/HomeController.php
Normal file
34
classes/Application/Controller/HomeController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Controller;
|
||||
|
||||
use Application\Generator\Article;
|
||||
use Application\Helper\NumberHash;
|
||||
use Exception;
|
||||
|
||||
class HomeController extends MainController
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function action(): void
|
||||
{
|
||||
$this->pushToStash('articles', $this->generateArticles());
|
||||
}
|
||||
|
||||
private function generateArticles(): array
|
||||
{
|
||||
$return = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$return[] = (new Article(NumberHash::numHash("home_$i")))->getTeaserData();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function getTemplate(): string
|
||||
{
|
||||
return '04_templates/t-home/t-home.twig';
|
||||
}
|
||||
}
|
210
classes/Application/Controller/MainController.php
Normal file
210
classes/Application/Controller/MainController.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Controller;
|
||||
|
||||
use Application\Breadcrumbs\Breadcrumbs;
|
||||
use Application\Helper\ArrayPath;
|
||||
use Exception;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Faker\Factory;
|
||||
use Faker\Generator;
|
||||
|
||||
abstract class MainController
|
||||
{
|
||||
|
||||
/**
|
||||
* Path to twig template folder.
|
||||
*/
|
||||
public const DIR_TEMPLATES = __DIR__ . '/../../../templates';
|
||||
|
||||
/**
|
||||
* Path to cache folder.
|
||||
*/
|
||||
public const DIR_CACHE = __DIR__ . '/../../../cache';
|
||||
|
||||
/**
|
||||
* Array key for global stash scope.
|
||||
*/
|
||||
private const STASH_GLOBAL = 'global';
|
||||
|
||||
/**
|
||||
* Array key for local stash scope.
|
||||
*/
|
||||
private const STASH_LOCAL = 'local';
|
||||
|
||||
/**
|
||||
* Stash. This array will be sent to twig for rendering.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $stash = array();
|
||||
|
||||
/**
|
||||
* URL parameters obtained by routing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $route_parameters = array();
|
||||
|
||||
/**
|
||||
* Fake data generator.
|
||||
*
|
||||
* @var Generator
|
||||
*/
|
||||
protected Generator $faker;
|
||||
|
||||
/**
|
||||
* Breadcrumbs.
|
||||
*
|
||||
* @var Breadcrumbs
|
||||
*/
|
||||
private Breadcrumbs $breadcrumbs;
|
||||
|
||||
/**
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
* @throws LoaderError
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __invoke(array $parameters = array()): void
|
||||
{
|
||||
$this->route_parameters = $parameters;
|
||||
$this->faker = Factory::create();
|
||||
$this->faker->seed(1234567);
|
||||
$this->preAction();
|
||||
$this->action();
|
||||
$this->postAction();
|
||||
$this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getRouteParams(string|array $params)
|
||||
{
|
||||
return ArrayPath::GetDataFromArray($this->route_parameters, $params);
|
||||
}
|
||||
|
||||
protected abstract function action(): void;
|
||||
|
||||
protected abstract function getTemplate(): string;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function preAction(): void
|
||||
{
|
||||
$this->headerData();
|
||||
$this->menuData();
|
||||
$this->breadcrumbs = new Breadcrumbs();
|
||||
$this->pushToStash('foo', 'bar', self::STASH_GLOBAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function postAction(): void
|
||||
{
|
||||
$this->BreadcrumbsToStash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function menuData(): void
|
||||
{
|
||||
$this->pushToStash(
|
||||
'menu',
|
||||
[
|
||||
[
|
||||
'name' => 'Home',
|
||||
'url' => '/',
|
||||
],
|
||||
[
|
||||
'name' => 'News',
|
||||
'url' => '/news',
|
||||
],
|
||||
[
|
||||
'name' => 'Artikel',
|
||||
'url' => '/article',
|
||||
],
|
||||
],
|
||||
self::STASH_GLOBAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create header and meta-data.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function headerData(): void
|
||||
{
|
||||
$this->pushToStash(
|
||||
'meta',
|
||||
[
|
||||
'title' => $this->faker->domainName(),
|
||||
],
|
||||
self::STASH_GLOBAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function pushToStash(string $name, mixed $data, string $context = self::STASH_LOCAL): void
|
||||
{
|
||||
if (array_key_exists($context, $this->stash) and array_key_exists($name, $this->stash[$context])) {
|
||||
throw new Exception("The stash variable '$name' is already set!");
|
||||
}
|
||||
|
||||
$this->stash[$context][$name] = $data;
|
||||
}
|
||||
|
||||
protected function getBreadcrumbs()
|
||||
{
|
||||
return $this->breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SyntaxError
|
||||
* @throws RuntimeError
|
||||
* @throws LoaderError
|
||||
*/
|
||||
private function render(): void
|
||||
{
|
||||
$twig = new Environment(
|
||||
new FilesystemLoader(self::DIR_TEMPLATES),
|
||||
array(
|
||||
'cache' => self::DIR_CACHE . '/twig',
|
||||
'auto_reload' => true,
|
||||
)
|
||||
);
|
||||
|
||||
echo $twig->render(
|
||||
$this->getTemplate(),
|
||||
$this->getStash()
|
||||
);
|
||||
}
|
||||
|
||||
private function getStash(): array
|
||||
{
|
||||
return $this->stash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function BreadcrumbsToStash(): void
|
||||
{
|
||||
$this->pushToStash('breadcrumbs', $this->getBreadcrumbs()->getTemplateData(), self::STASH_GLOBAL);
|
||||
}
|
||||
|
||||
}
|
63
classes/Application/Generator/Article.php
Normal file
63
classes/Application/Generator/Article.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Generator;
|
||||
|
||||
class Article extends WebsiteObjectGenerator
|
||||
{
|
||||
public function __construct(protected readonly string $seed)
|
||||
{
|
||||
parent::__construct($seed);
|
||||
}
|
||||
|
||||
private function getHeadline(): string
|
||||
{
|
||||
return $this->getGenerator()->sentence(8);
|
||||
}
|
||||
|
||||
private function getKicker(): string
|
||||
{
|
||||
return $this->getGenerator()->word();
|
||||
}
|
||||
|
||||
private function getTeaserImage(): array
|
||||
{
|
||||
return (new Image($this->getSeed()))->getImage(640, 480);
|
||||
}
|
||||
|
||||
private function getHeroImage(): array
|
||||
{
|
||||
return (new Image($this->getSeed()))->getImage(1920, 1080);
|
||||
}
|
||||
|
||||
private function getExcerpt(): string
|
||||
{
|
||||
return implode(' ', $this->getGenerator()->sentences(5));
|
||||
}
|
||||
|
||||
private function getUrl(): string
|
||||
{
|
||||
return '/article/test-' . $this->getSeed();
|
||||
}
|
||||
|
||||
public function getTeaserData(): array
|
||||
{
|
||||
return [
|
||||
'headline' => $this->getHeadline(),
|
||||
'kicker' => $this->getKicker(),
|
||||
'excerpt' => $this->getExcerpt(),
|
||||
'teaserImage' => $this->getTeaserImage(),
|
||||
'url' => $this->getUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFullData(): array
|
||||
{
|
||||
return [
|
||||
'headline' => $this->getHeadline(),
|
||||
'kicker' => $this->getKicker(),
|
||||
'excerpt' => $this->getExcerpt(),
|
||||
'heroImage' => $this->getHeroImage(),
|
||||
'url' => $this->getUrl(),
|
||||
];
|
||||
}
|
||||
}
|
23
classes/Application/Generator/Image.php
Normal file
23
classes/Application/Generator/Image.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Generator;
|
||||
|
||||
use Application\Generator\WebsiteObjectGenerator;
|
||||
|
||||
class Image extends WebsiteObjectGenerator
|
||||
{
|
||||
public function __construct(protected readonly string $seed)
|
||||
{
|
||||
parent::__construct($seed);
|
||||
}
|
||||
|
||||
public function getImage(int $width = 1280, int $height = 720): array
|
||||
{
|
||||
return [
|
||||
'src' => 'https://picsum.photos/seed/' . $this->seed . "/$width/$height",
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'alt' => $this->getGenerator()->sentence(),
|
||||
];
|
||||
}
|
||||
}
|
30
classes/Application/Generator/WebsiteObjectGenerator.php
Normal file
30
classes/Application/Generator/WebsiteObjectGenerator.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Generator;
|
||||
|
||||
use Faker\Factory;
|
||||
use Faker\Generator;
|
||||
|
||||
class WebsiteObjectGenerator
|
||||
{
|
||||
/**
|
||||
* @var Generator
|
||||
*/
|
||||
private Generator $generator;
|
||||
|
||||
public function __construct(private readonly string $seed)
|
||||
{
|
||||
$this->generator = Factory::create();
|
||||
$this->getGenerator()->seed($this->seed);
|
||||
}
|
||||
|
||||
protected function getGenerator(): Generator
|
||||
{
|
||||
return $this->generator;
|
||||
}
|
||||
|
||||
protected function getSeed(): string
|
||||
{
|
||||
return $this->seed;
|
||||
}
|
||||
}
|
82
classes/Application/Helper/ArrayPath.php
Normal file
82
classes/Application/Helper/ArrayPath.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Helper;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Return data from a nested array by providing a path. The parts of the path are seperated by commas.
|
||||
*
|
||||
* Examples: 'this.is.a.path' | 'root-node'
|
||||
*/
|
||||
class ArrayPath {
|
||||
|
||||
/**
|
||||
* Return array data by given paths.
|
||||
*
|
||||
* Accepts no key (return all data) or a string (one specific data) or array (returns multiple data).
|
||||
*
|
||||
* @throws Exception|Exception
|
||||
*/
|
||||
public static function GetDataFromArray(array $array, mixed $key = null): mixed {
|
||||
if (!$key) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (is_string($key) and strpos($key, '.')) {
|
||||
return self::getArrayDataByPath($array, $key);
|
||||
}
|
||||
|
||||
if (is_string($key) and array_key_exists($key, $array)) {
|
||||
return $array[$key];
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
return self::GetMultipleData($array, $key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a nested array by providing a path.
|
||||
*
|
||||
* @param string|array $data array to search the data on (haystack)
|
||||
* @param string|array $path path(s) with dot-seperated parts (needle(s))
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getArrayDataByPath(mixed $data, string|array $path): mixed {
|
||||
if (is_string($path)) {
|
||||
$path = explode('.', $path);
|
||||
}
|
||||
|
||||
if (count($path) === 0) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$key = array_shift($path);
|
||||
|
||||
return isset($data[$key]) ? self::getArrayDataByPath($data[$key], $path) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return multiple data by providing an array of desired keys and not just one as a string.
|
||||
*
|
||||
* @param array $data Haystack
|
||||
* @param array $key Needles
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function GetMultipleData(array $data, array $key): array {
|
||||
$return = array();
|
||||
|
||||
foreach ($key as $key_value) {
|
||||
$return[$key_value] = self::GetDataFromArray($data, $key_value);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
28
classes/Application/Helper/NumberHash.php
Normal file
28
classes/Application/Helper/NumberHash.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Helper;
|
||||
|
||||
class NumberHash
|
||||
{
|
||||
/**
|
||||
* Return a number only hash
|
||||
*
|
||||
* @see https://stackoverflow.com/a/23679870/175071
|
||||
*
|
||||
* @param string $string string to hash
|
||||
* @param null|int $length optional specific length
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function numHash(string $string, ?int $length = null): int
|
||||
{
|
||||
$stringHash = md5($string, true);
|
||||
$numberHash = unpack('N2', $stringHash);
|
||||
$hash = $numberHash[1] . $numberHash[2];
|
||||
if ($length) {
|
||||
$hash = substr($hash, 0, $length);
|
||||
}
|
||||
|
||||
return (int)$hash;
|
||||
}
|
||||
}
|
51
classes/Application/Models/Article.php
Normal file
51
classes/Application/Models/Article.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Models;
|
||||
|
||||
use JMS\Serializer\Annotation\SerializedName;
|
||||
use JMS\Serializer\Annotation\Type;
|
||||
|
||||
class Article
|
||||
{
|
||||
use Meta;
|
||||
|
||||
#[SerializedName('authors')]
|
||||
#[Type('array<' . Author::class . '>')]
|
||||
protected array $authors;
|
||||
|
||||
#[SerializedName('headline')]
|
||||
#[Type('string')]
|
||||
protected string $headline;
|
||||
|
||||
#[SerializedName('excerpt')]
|
||||
#[Type('string')]
|
||||
protected string $excerpt;
|
||||
|
||||
#[SerializedName('image')]
|
||||
#[Type(Image::class)]
|
||||
protected Image $image;
|
||||
|
||||
#[SerializedName('text')]
|
||||
#[Type('string')]
|
||||
protected string $text;
|
||||
|
||||
/**
|
||||
* Return the title / headline.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return $this->headline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text / article body text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getText(): string {
|
||||
return $this->text;
|
||||
}
|
||||
}
|
17
classes/Application/Models/Author.php
Normal file
17
classes/Application/Models/Author.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Models;
|
||||
|
||||
use JMS\Serializer\Annotation\SerializedName;
|
||||
use JMS\Serializer\Annotation\Type;
|
||||
|
||||
class Author
|
||||
{
|
||||
use Meta;
|
||||
|
||||
#[SerializedName('name')]
|
||||
#[Type('string')]
|
||||
private string $name;
|
||||
}
|
65
classes/Application/Models/Image.php
Normal file
65
classes/Application/Models/Image.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Models;
|
||||
|
||||
use JMS\Serializer\Annotation\SerializedName;
|
||||
use JMS\Serializer\Annotation\Type;
|
||||
|
||||
class Image
|
||||
{
|
||||
use Meta;
|
||||
|
||||
#[SerializedName('path')]
|
||||
#[Type('string')]
|
||||
private string $path;
|
||||
|
||||
#[SerializedName('title')]
|
||||
#[Type('string')]
|
||||
private string $title;
|
||||
|
||||
#[SerializedName('mime')]
|
||||
#[Type('string')]
|
||||
private string $mime;
|
||||
|
||||
#[SerializedName('type')]
|
||||
#[Type('string')]
|
||||
private string $type;
|
||||
|
||||
#[SerializedName('description')]
|
||||
#[Type('string')]
|
||||
private string $description;
|
||||
|
||||
#[SerializedName('tags')]
|
||||
#[Type('array')]
|
||||
private array $tags;
|
||||
|
||||
#[SerializedName('size')]
|
||||
#[Type('string')]
|
||||
private string $size;
|
||||
|
||||
#[SerializedName('colors')]
|
||||
#[Type('array')]
|
||||
private array $colors;
|
||||
|
||||
#[SerializedName('width')]
|
||||
#[Type('string')]
|
||||
private string $width;
|
||||
|
||||
#[SerializedName('height')]
|
||||
#[Type('string')]
|
||||
private string $height;
|
||||
|
||||
#[SerializedName('altText')]
|
||||
#[Type('string')]
|
||||
private string $altText;
|
||||
|
||||
#[SerializedName('thumbhash')]
|
||||
#[Type('string')]
|
||||
private string $thumbhash;
|
||||
|
||||
#[SerializedName('folder')]
|
||||
#[Type('string')]
|
||||
private string $folder;
|
||||
}
|
38
classes/Application/Models/Meta.php
Normal file
38
classes/Application/Models/Meta.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Application\Models;
|
||||
|
||||
use DateTime;
|
||||
use JMS\Serializer\Annotation\SerializedName;
|
||||
use JMS\Serializer\Annotation\Type;
|
||||
|
||||
trait Meta
|
||||
{
|
||||
#[SerializedName('_model')]
|
||||
#[Type('string')]
|
||||
private ?string $metaModel;
|
||||
|
||||
#[SerializedName('_state')]
|
||||
#[Type('int')]
|
||||
private ?int $metaState;
|
||||
|
||||
#[SerializedName('_modified')]
|
||||
#[Type('DateTime<"U">')]
|
||||
private ?DateTime $metaModified;
|
||||
|
||||
#[SerializedName('_mby')]
|
||||
#[Type('string')]
|
||||
private ?string $metaMby;
|
||||
|
||||
#[SerializedName('_created')]
|
||||
#[Type('DateTime<"U">')]
|
||||
private ?DateTime $metaCreated;
|
||||
|
||||
#[SerializedName('_cby')]
|
||||
#[Type('string')]
|
||||
private ?string $metaCby;
|
||||
|
||||
#[SerializedName('_id')]
|
||||
#[Type('string')]
|
||||
private ?string $metaId;
|
||||
}
|
22
classes/Application/Models/Site.php
Normal file
22
classes/Application/Models/Site.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Models;
|
||||
|
||||
use JMS\Serializer\Annotation\SerializedName;
|
||||
use JMS\Serializer\Annotation\Type;
|
||||
|
||||
class Site
|
||||
{
|
||||
use Meta;
|
||||
|
||||
#[SerializedName('title')]
|
||||
#[Type('string')]
|
||||
private ?string $title;
|
||||
|
||||
public function getSiteTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
}
|
75
classes/Application/Router.php
Normal file
75
classes/Application/Router.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Application;
|
||||
|
||||
use Application\Controller\HomeController;
|
||||
use Application\Controller\ArticleController;
|
||||
use Bramus\Router\Router as BramusRouter;
|
||||
|
||||
/**
|
||||
* Simple router setup based on bramus/router.
|
||||
*
|
||||
* @link https://github.com/bramus/router
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
|
||||
private BramusRouter $router;
|
||||
|
||||
/**
|
||||
* Constructor, setup router and routes
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->setupRouter();
|
||||
$this->setupRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the router.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setupRouter(): void
|
||||
{
|
||||
$this->router = new BramusRouter();
|
||||
$this->router->setNamespace('\Application\Controller');
|
||||
}
|
||||
|
||||
/**
|
||||
* Route-configuration will be done here.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setupRoutes(): void
|
||||
{
|
||||
$this->router->match('GET', '/', function () {
|
||||
(new HomeController())();
|
||||
});
|
||||
$this->router->match('GET', '/messages', function () {
|
||||
echo "<div>Lorem Ipsum Dolor</div>";
|
||||
});
|
||||
$this->router->match('GET', '/style.css', function () {
|
||||
(new ScssCompiler())->compile();
|
||||
});
|
||||
$this->router->match('GET', '/debugbar/styles.css', function () {
|
||||
#DebugBar::getInstance()->dumpCssAssets();
|
||||
});
|
||||
$this->router->match('GET', '/debugbar/javascript.js', function () {
|
||||
#DebugBar::getInstance()->dumpJsAssets();
|
||||
});
|
||||
$this->router->match('GET', '/article/(\w+)-(\w+)', function ($name, $id) {
|
||||
(new ArticleController())(['name' => $name, 'id' => $id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the routing and thus call the controllers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function serve(): void
|
||||
{
|
||||
$this->router->run();
|
||||
}
|
||||
}
|
31
classes/Application/ScssCompiler.php
Normal file
31
classes/Application/ScssCompiler.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Application;
|
||||
|
||||
use Application\Controller\MainController;
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
use ScssPhp\ScssPhp\Exception\SassException;
|
||||
use ScssPhp\ScssPhp\OutputStyle;
|
||||
use ScssPhp\Server\Server;
|
||||
use ScssPhp\Server\ServerException;
|
||||
|
||||
class ScssCompiler
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws ServerException
|
||||
* @throws SassException
|
||||
*/
|
||||
public function compile(): void
|
||||
{
|
||||
$scss = new Compiler();
|
||||
$scss->setImportPaths(MainController::DIR_TEMPLATES);
|
||||
$scss->setOutputStyle(OutputStyle::EXPANDED);
|
||||
$scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
|
||||
|
||||
$_GET['p'] = 'main.scss';
|
||||
|
||||
$server = new Server(MainController::DIR_TEMPLATES, MainController::DIR_CACHE . '/scss', $scss);
|
||||
$server->serve();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user