├── .codeclimate.yml
├── LICENSE.md
├── README.md
├── build
├── split-notags.sh
└── split.sh
├── composer.json
├── src
├── Backpack
│ ├── AppBuilder.php
│ ├── Bedrock
│ │ ├── AbstractAppBuilder.php
│ │ ├── AppRoutesMapper.php
│ │ └── HttpAppBuilder.php
│ ├── Bootstrap
│ │ ├── PhpSettings.php
│ │ ├── Routing.php
│ │ └── Session.php
│ ├── Config
│ │ └── ConfigLoader.php
│ ├── ConfigLoader.php
│ └── Http
│ │ └── Request.php
├── Bedrock
│ ├── AbstractApplication.php
│ ├── AbstractBootstrapper.php
│ ├── Bootstrap
│ │ ├── BootableResolver.php
│ │ ├── Bootstrap.php
│ │ ├── CallableProcess.php
│ │ └── Exception
│ │ │ └── InvalidBootableProcessException.php
│ ├── Cli
│ │ ├── Application.php
│ │ └── Exception
│ │ │ └── InvalidCommandException.php
│ ├── Http
│ │ ├── Application.php
│ │ └── GroupManager.php
│ ├── Kernel.php
│ ├── README.md
│ └── composer.json
├── Blueprint
│ ├── Backpack
│ │ ├── AppBuilderBase.php
│ │ ├── CliAppBuilder.php
│ │ └── HttpAppBuilder.php
│ ├── Bedrock
│ │ ├── Application.php
│ │ ├── CliApplication.php
│ │ ├── HttpApplication.php
│ │ └── Kernel.php
│ ├── Collection
│ │ ├── Collection.php
│ │ ├── Dictionary.php
│ │ ├── ObjectAccess.php
│ │ └── Structure.php
│ ├── Common
│ │ ├── Arrayable.php
│ │ ├── Bootable.php
│ │ ├── Initializable.php
│ │ ├── Outputable.php
│ │ ├── Renderable.php
│ │ ├── ResourceLoader.php
│ │ ├── ResourceProcessor.php
│ │ ├── ResourceResolver.php
│ │ └── Validator.php
│ ├── Config
│ │ ├── Config.php
│ │ ├── ConfigException.php
│ │ ├── ConfigFactory.php
│ │ └── Stream.php
│ ├── Http
│ │ ├── ResponseEmitter.php
│ │ ├── ResponseMiddleware.php
│ │ ├── Route.php
│ │ └── Stack.php
│ ├── README.md
│ └── composer.json
├── Collection
│ ├── Collection.php
│ ├── CollectionFlattener.php
│ ├── DotNotationCollection.php
│ ├── Exception
│ │ └── ImmutableException.php
│ ├── ImmutablePropertiesBag.php
│ ├── PropertiesBag.php
│ ├── README.md
│ ├── Structure
│ │ ├── AbstractImmutableStructure.php
│ │ ├── AbstractStructure.php
│ │ ├── DataType.php
│ │ └── Exception
│ │ │ ├── InvalidPropertyDefinitionException.php
│ │ │ ├── InvalidPropertyTypeException.php
│ │ │ ├── InvalidStructureException.php
│ │ │ └── UndefinedPropertyException.php
│ └── composer.json
├── Common
│ ├── Chrono
│ │ ├── Chrono.php
│ │ └── Step.php
│ ├── PhpIni.php
│ ├── README.md
│ ├── Traits
│ │ ├── ArrayMergeRecursiveDistinct.php
│ │ ├── LoadArrayFiles.php
│ │ ├── Macro.php
│ │ ├── MicroTime.php
│ │ └── UpdateToCamelCase.php
│ ├── composer.json
│ └── helpers.php
├── Config
│ ├── Cache
│ │ └── FileCache.php
│ ├── Config.php
│ ├── ConfigCacheFactory.php
│ ├── ConfigFactory.php
│ ├── ConfigResolver.php
│ ├── Exception
│ │ ├── CacheInvalidKeyException.php
│ │ ├── CachePathNotFoundException.php
│ │ ├── CachePathNotWritableException.php
│ │ ├── FileNotFoundException.php
│ │ ├── FileNotReadableException.php
│ │ ├── LoaderException.php
│ │ ├── NoFileHandlersException.php
│ │ ├── ProcessorException.php
│ │ ├── ProcessorTypeException.php
│ │ └── UnknownResourceException.php
│ ├── FilesHandlers.php
│ ├── Loader
│ │ ├── DefaultLoader.php
│ │ ├── PhpLoader.php
│ │ └── TextLoader.php
│ ├── Processor
│ │ ├── ArrayProcessor.php
│ │ ├── CallableProcessor.php
│ │ ├── CollectionProcessor.php
│ │ ├── ConfigProcessor.php
│ │ ├── EnvProcessor.php
│ │ ├── IniProcessor.php
│ │ ├── JsonProcessor.php
│ │ ├── StdClassProcessor.php
│ │ ├── XmlProcessor.php
│ │ └── YamlProcessor.php
│ ├── README.md
│ ├── Stream
│ │ ├── DataStream.php
│ │ ├── FileStream.php
│ │ ├── JsonStream.php
│ │ └── XmlStream.php
│ └── composer.json
├── Di
│ ├── ArrayDefinition.php
│ ├── Binding
│ │ ├── AbstractBinding.php
│ │ ├── BindingInterface.php
│ │ ├── Factory.php
│ │ ├── Prototype.php
│ │ └── Singleton.php
│ ├── BindingResolver.php
│ ├── ClassInspector.php
│ ├── ClassInstantiator.php
│ ├── ClassResolver.php
│ ├── Container.php
│ ├── Exception
│ │ ├── AmbiguousResolutionException.php
│ │ ├── ClassDefinitionNotFoundException.php
│ │ ├── ClassNotFoundException.php
│ │ ├── InfiniteLoopResolutionException.php
│ │ ├── InterfaceNotFoundException.php
│ │ ├── InvalidDefinitionException.php
│ │ └── MethodNotFoundException.php
│ ├── ExplicitResolver.php
│ ├── InterfaceResolver.php
│ ├── README.md
│ └── composer.json
└── Http
│ ├── Exception
│ ├── BodyParserException.php
│ ├── EmptyStackException.php
│ ├── JsonBodyParserException.php
│ └── StackEndedWithoutResponseException.php
│ ├── Middleware
│ ├── CallableMiddleware.php
│ └── JsonBodyParserMiddleware.php
│ ├── README.md
│ ├── Request
│ ├── BlankRequest.php
│ ├── Exception
│ │ ├── HandlerNotFoundException.php
│ │ ├── InvalidHandlerException.php
│ │ └── UnresolvableHandlerException.php
│ ├── HandlerResolver.php
│ ├── PreRoute.php
│ ├── Route.php
│ ├── RouteArgs.php
│ ├── RouteExpression.php
│ └── RouteServerRequest.php
│ ├── Response
│ └── Emitter.php
│ ├── Stack.php
│ ├── StackFactory.php
│ └── composer.json
└── supporters.txt
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | checks:
3 | argument-count:
4 | enabled: true
5 | config:
6 | threshold: 5
7 | complex-logic:
8 | enabled: true
9 | config:
10 | threshold: 4
11 | file-lines:
12 | enabled: true
13 | config:
14 | threshold: 300
15 | method-complexity:
16 | enabled: true
17 | config:
18 | threshold: 5
19 | method-count:
20 | enabled: true
21 | config:
22 | threshold: 40
23 | method-lines:
24 | enabled: true
25 | config:
26 | threshold: 30
27 | nested-control-flow:
28 | enabled: true
29 | config:
30 | threshold: 4
31 | return-statements:
32 | enabled: true
33 | config:
34 | threshold: 5
35 | similar-code:
36 | enabled: true
37 | config:
38 | threshold: #language-specific defaults. overrides affect all languages.
39 | identical-code:
40 | enabled: true
41 | config:
42 | threshold: #language-specific defaults. overrides affect all languages.
43 | plugins:
44 | rubocop:
45 | enabled: true
46 | eslint:
47 | enabled: true
48 | exclude_patterns:
49 | - "tests/"
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) François Lajoie
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Peak
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | > **Note:** This repository contains the core code of the Peak framework. If you want an example on how to build an application using Peak, visit the main [Peak repository](https://github.com/peakphp/peak).
12 |
13 | ### What is Peak?
14 |
15 | Peak is a powerful, minimalist and flexible micro-framework, middleware centric based on PSR-7, PSR-11 and PSR-15.
16 |
17 | ### Installation
18 |
19 | ``$ composer require peak/framework``
20 |
21 | ### Quick start project
22 |
23 | Check https://github.com/peakphp/peak for a quick start project structure.
24 |
25 | ### Documentation & Site
26 |
27 | Check https://peakframework.com
28 |
29 |
--------------------------------------------------------------------------------
/build/split-notags.sh:
--------------------------------------------------------------------------------
1 | git subsplit init git@github.com:peakphp/framework.git
2 | git subsplit publish --no-tags --heads="master" src/Bedrock:git@github.com:peakphp/bedrock.git
3 | git subsplit publish --no-tags --heads="master" src/Blueprint:git@github.com:peakphp/blueprint.git
4 | git subsplit publish --no-tags --heads="master" src/Collection:git@github.com:peakphp/collection.git
5 | git subsplit publish --no-tags --heads="master" src/Common:git@github.com:peakphp/common.git
6 | git subsplit publish --no-tags --heads="master" src/Config:git@github.com:peakphp/config.git
7 | git subsplit publish --no-tags --heads="master" src/Di:git@github.com:peakphp/di.git
8 | git subsplit publish --no-tags --heads="master" src/Http:git@github.com:peakphp/http.git
9 | rm -rf .subsplit/
--------------------------------------------------------------------------------
/build/split.sh:
--------------------------------------------------------------------------------
1 | git subsplit init git@github.com:peakphp/framework.git
2 | git subsplit publish --heads="master" src/Bedrock:git@github.com:peakphp/bedrock.git
3 | git subsplit publish --heads="master" src/Blueprint:git@github.com:peakphp/blueprint.git
4 | git subsplit publish --heads="master" src/Collection:git@github.com:peakphp/collection.git
5 | git subsplit publish --heads="master" src/Common:git@github.com:peakphp/common.git
6 | git subsplit publish --heads="master" src/Config:git@github.com:peakphp/config.git
7 | git subsplit publish --heads="master" src/Di:git@github.com:peakphp/di.git
8 | git subsplit publish --heads="master" src/Http:git@github.com:peakphp/http.git
9 | rm -rf .subsplit/
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/framework",
3 | "description": "The Peak Framework.",
4 | "keywords": ["framework", "peak", "middleware", "psr-7", "psr-11", "psr-15"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework",
10 | "api": "http://api.peakframework.com/"
11 | },
12 | "authors": [
13 | {
14 | "name": "Francois Lajoie",
15 | "email": "francois@peakframework.com"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=7.2",
20 | "psr/container": "^1.0",
21 | "psr/log": "^1.1",
22 | "psr/http-message": "^1.0",
23 | "psr/http-server-handler": "^1.0",
24 | "psr/http-server-middleware": "^1.0",
25 | "psr/http-factory": "^1.0",
26 | "psr/simple-cache": "^1.0"
27 | },
28 | "require-dev": {
29 | "phpunit/phpunit": "^8.5",
30 | "phpstan/phpstan": "^0.12",
31 | "symfony/yaml": "^4.4"
32 | },
33 | "suggest": {
34 | "symfony/console": "Required to use Peak\\Bedrock\\Cli\\Application (^4.4).",
35 | "symfony/process": "Required to use Peak\\Bedrock\\Cli\\Application (^4.4).",
36 | "symfony/yaml": "Required to parse yaml file with Peak\\Config (^4.4)."
37 | },
38 | "autoload": {
39 | "files": [
40 | "src/Common/helpers.php"
41 | ],
42 | "psr-4": {
43 | "Peak\\": "src/"
44 | }
45 | },
46 | "minimum-stability": "dev",
47 | "prefer-stable": true
48 | }
49 |
--------------------------------------------------------------------------------
/src/Backpack/AppBuilder.php:
--------------------------------------------------------------------------------
1 | env = $env;
64 | return $this;
65 | }
66 |
67 | /**
68 | * @param ContainerInterface $container
69 | * @return $this
70 | */
71 | public function setContainer(ContainerInterface $container)
72 | {
73 | $this->container = $container;
74 | return $this;
75 | }
76 |
77 | /**
78 | * @param $props
79 | * @return $this
80 | * @throws Exception
81 | */
82 | public function setProps($props)
83 | {
84 | if (!is_array($props) && !$props instanceof Dictionary) {
85 | throw new Exception('Props must be an array or an instance of Peak\Blueprint\Collection\Dictionary. '.gettype($props).' given ...');
86 | } elseif (is_array($props)) {
87 | $props = new DotNotationCollection($props);
88 | }
89 |
90 | $this->props = $props;
91 | return $this;
92 | }
93 |
94 | /**
95 | * @param Kernel $kernel
96 | * @return $this
97 | */
98 | public function setKernel(Kernel $kernel)
99 | {
100 | $this->kernel = $kernel;
101 | return $this;
102 | }
103 |
104 | /**
105 | * @param string $kernelClass
106 | * @return $this
107 | */
108 | public function setKernelClass(string $kernelClass)
109 | {
110 | $this->kernelClass = $kernelClass;
111 | return $this;
112 | }
113 |
114 | /**
115 | * @param string $appClass
116 | * @return $this
117 | */
118 | public function setAppClass(string $appClass)
119 | {
120 | $this->appClass = $appClass;
121 | return $this;
122 | }
123 |
124 | /**
125 | * @param Closure $fn
126 | * @return $this
127 | */
128 | public function executeAfterBuild(Closure $fn)
129 | {
130 | $this->afterBuild = $fn;
131 | return $this;
132 | }
133 | }
--------------------------------------------------------------------------------
/src/Backpack/Bedrock/AppRoutesMapper.php:
--------------------------------------------------------------------------------
1 | inspectRecursive($app->getHandlers());
21 | }
22 |
23 | /**
24 | * @param array $handlers
25 | * @return array
26 | */
27 | private function inspectRecursive(array $handlers): array
28 | {
29 | $routes = [];
30 | foreach ($handlers as $handler) {
31 | $subRoutes = [];
32 | if ($handler instanceof Route && !($handler instanceof PreRoute)) {
33 | $route = [
34 | 'method' => $handler->getMethod(),
35 | 'path' => $handler->getPath(),
36 | 'stack' => [],
37 | ];
38 | $handlers = $handler->getHandlers();
39 | foreach ($handlers as $h) {
40 | if (is_string($h)) {
41 | $route['stack'][] = $h;
42 | } elseif (is_object($h)) {
43 | $route['stack'][] = get_class($h);
44 | }
45 | }
46 | $routes[] = $route;
47 | $subRoutes = $this->inspectRecursive($handler->getHandlers());
48 | } elseif (is_array($handler)) {
49 | $subRoutes = $this->inspectRecursive($handler);
50 | } elseif ($handler instanceof Stack) {
51 | $subRoutes = $this->inspectRecursive($handler->getHandlers());
52 | }
53 |
54 | if (!empty($subRoutes)) {
55 | $routes = array_merge($subRoutes, $routes);
56 | }
57 | }
58 |
59 | //reorder routes by path
60 | array_multisort (array_column($routes, 'path'), SORT_ASC, $routes);
61 | return $routes;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Backpack/Bedrock/HttpAppBuilder.php:
--------------------------------------------------------------------------------
1 | handlerResolver = $handlerResolver;
41 | return $this;
42 | }
43 |
44 | /**
45 | * @param string|null $alias
46 | * @return $this
47 | */
48 | public function addToContainerAfterBuild(string $alias = null)
49 | {
50 | $this->addToContainerAfterBuild = true;
51 | $this->aliasContainer = $alias;
52 | return $this;
53 | }
54 |
55 | /**
56 | * @return HttpApplication
57 | * @throws Exception
58 | */
59 | public function build(): HttpApplication
60 | {
61 | return $this->internalBuild($this->appClass ?? Application::class);
62 | }
63 |
64 | /**
65 | * @param string $applicationClass
66 | * @return \Peak\Blueprint\Bedrock\HttpApplication
67 | * @throws \Exception
68 | */
69 | private function internalBuild(string $applicationClass): \Peak\Blueprint\Bedrock\HttpApplication
70 | {
71 | $kernel = $this->kernel;
72 | if (!isset($kernel)) {
73 | $kernelClass = $this->kernelClass ?? \Peak\Bedrock\Kernel::class;
74 | $kernel = new $kernelClass(
75 | $this->env ?? 'prod',
76 | $this->container ?? new Container()
77 | );
78 | } elseif (isset($this->container) || isset($this->env) || isset($this->kernelClass)) {
79 | $this->triggerKernelError();
80 | }
81 |
82 | $app = new $applicationClass(
83 | $kernel,
84 | $this->handlerResolver ?? new HandlerResolver($kernel->getContainer()),
85 | $this->props ?? null
86 | );
87 |
88 | if ($this->addToContainerAfterBuild) {
89 | $this->addToContainer($app);
90 | }
91 |
92 | if (isset($this->afterBuild) && is_callable($this->afterBuild)) {
93 | ($this->afterBuild)($app);
94 | }
95 |
96 | return $app;
97 | }
98 |
99 | /**
100 | * Trigger an error with arguments container and env when a kernel has been set previously
101 | */
102 | private function triggerKernelError()
103 | {
104 | $msgErrorSuffix = 'setting will be ignored because Kernel had been set previously.';
105 | if (isset($this->container)) {
106 | trigger_error('Container '.$msgErrorSuffix);
107 | }
108 | if (isset($this->env)) {
109 | trigger_error('Env '.$msgErrorSuffix);
110 | }
111 | if (isset($this->kernelClass)) {
112 | trigger_error('Kernel class '.$msgErrorSuffix);
113 | }
114 | }
115 |
116 | /**
117 | * @param \Peak\Blueprint\Bedrock\HttpApplication $app
118 | * @throws \Exception
119 | */
120 | private function addToContainer(\Peak\Blueprint\Bedrock\HttpApplication $app)
121 | {
122 | $container = $app->getKernel()->getContainer();
123 | if (!$container instanceof Container) {
124 | throw new Exception('Cannot add application instance to the container');
125 | }
126 | $container->set($app);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Backpack/Bootstrap/PhpSettings.php:
--------------------------------------------------------------------------------
1 | app = $app;
32 | $this->phpPropName = $phpPropName;
33 | }
34 |
35 | /**
36 | * Configure php setting on the fly via ini_set
37 | * @throws Exception
38 | */
39 | public function boot()
40 | {
41 | new PhpIni($this->app->getProp($this->phpPropName, []));
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Backpack/Bootstrap/Routing.php:
--------------------------------------------------------------------------------
1 | app = $app;
41 | $this->routesPropName = $routesPropName;
42 | $this->routesPathPrefixPropName = $routesPathPrefixPropName;
43 | }
44 |
45 | /**
46 | * Look for routes to register in application properties
47 | * @throws \Exception
48 | */
49 | public function boot()
50 | {
51 | if ($this->app->getProps() === null) {
52 | return;
53 | }
54 |
55 | $routes = [];
56 | $pathPrefix = $this->app->getProp($this->routesPathPrefixPropName, '');
57 | $propRoutes = $this->app->getProp($this->routesPropName, []);
58 |
59 | if (!is_array($propRoutes)) {
60 | throw new \Exception('Routes definitions must be an array!');
61 | }
62 |
63 | foreach ($this->app->getProp($this->routesPropName, []) as $route) {
64 | $this->validate($route);
65 | $routes[] = $this->app->createRoute(
66 | $route['method'] ?? null,
67 | $pathPrefix.$route['path'],
68 | $route['stack']
69 | );
70 | }
71 |
72 | $this->app->stack($routes);
73 | }
74 |
75 | /**
76 | * @param mixed $route
77 | * @throws \Exception
78 | */
79 | private function validate($route)
80 | {
81 | if (!is_array($route)) {
82 | throw new \Exception('Route definition must be an array!');
83 | } elseif (!isset($route['path'])) {
84 | throw new \Exception('Route definition must have a path!');
85 | } elseif (!isset($route['stack'])) {
86 | throw new \Exception('Route definition must have a stack!');
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/src/Backpack/Bootstrap/Session.php:
--------------------------------------------------------------------------------
1 | app = $app;
39 | $this->sessionPropName = $sessionPropName;
40 | }
41 | /**
42 | * Setup and start session from app props
43 | * @throws Exception
44 | */
45 | public function boot()
46 | {
47 | if ((php_sapi_name() === 'cli' || defined('STDIN'))) {
48 | return;
49 | }
50 |
51 | if (session_status() == PHP_SESSION_ACTIVE) {
52 | throw new Exception('Session is already started');
53 | }
54 |
55 | // save path
56 | if ($this->app->hasProp($this->sessionPropName.'.save_path')) {
57 | session_save_path($this->app->getProp($this->sessionPropName.'.save_path'));
58 | }
59 |
60 | // save handler class
61 | if ($this->app->hasProp($this->sessionPropName.'.save_handler')) {
62 | session_set_save_handler($this->app->getProp($this->sessionPropName.'.save_handler'));
63 | }
64 |
65 | // name the session
66 | if ($this->app->hasProp($this->sessionPropName.'.name')) {
67 | session_name($this->app->getProp($this->sessionPropName.'.name'));
68 | }
69 |
70 | // session options
71 | $options = [];
72 | if ($this->app->hasProp($this->sessionPropName.'.options')) {
73 | $options = $this->app->getProp($this->sessionPropName.'.options');
74 | }
75 |
76 | // start the session
77 | session_start($options);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Backpack/Config/ConfigLoader.php:
--------------------------------------------------------------------------------
1 | cachePath)) {
50 | trigger_error('Cache configurations will be ignored because ConfigFactory have been set.');
51 | }
52 | $this->configFactory = $configFactory;
53 | return $this;
54 | }
55 |
56 | /**
57 | * @param string $path
58 | * @param string $uid
59 | * @param int $ttlInSec
60 | * @param CacheInterface|null $driver
61 | * @return $this
62 | */
63 | public function setCache(string $path, string $uid, int $ttlInSec, CacheInterface $driver = null)
64 | {
65 | if (isset($this->configFactory)) {
66 | trigger_error('Cache configurations will be ignored because ConfigFactory have been set.');
67 | }
68 | $this->cachePath = $path;
69 | $this->cacheId = $uid;
70 | $this->cacheTtl = $ttlInSec;
71 | $this->cacheDriver = $driver;
72 | return $this;
73 | }
74 |
75 | /**
76 | * @param mixed $resources
77 | * @return mixed|Config|\Peak\Config\Config
78 | * @throws \Peak\Config\Exception\CachePathNotFoundException
79 | * @throws \Peak\Config\Exception\CachePathNotWritableException
80 | * @throws \Peak\Config\Exception\UnknownResourceException
81 | * @throws \Psr\SimpleCache\InvalidArgumentException
82 | */
83 | public function load($resources)
84 | {
85 | return $this->loadConfig($resources);
86 | }
87 |
88 | /**
89 | * @param array $resources
90 | * @param Config $config
91 | * @return Config|\Peak\Config\Config
92 | * @throws \Peak\Config\Exception\CachePathNotFoundException
93 | * @throws \Peak\Config\Exception\CachePathNotWritableException
94 | * @throws \Peak\Config\Exception\UnknownResourceException
95 | * @throws \Psr\SimpleCache\InvalidArgumentException
96 | */
97 | public function loadWith(array $resources, Config $config)
98 | {
99 | // todo to remove
100 | return $this->loadConfig($resources, $config);
101 | }
102 |
103 | /**
104 | * @param array $resources
105 | * @param Config|null $config
106 | * @return Config
107 | * @throws \Peak\Config\Exception\CachePathNotFoundException
108 | * @throws \Peak\Config\Exception\CachePathNotWritableException
109 | * @throws \Peak\Config\Exception\UnknownResourceException
110 | * @throws \Psr\SimpleCache\InvalidArgumentException
111 | */
112 | private function loadConfig(array $resources, \Peak\Blueprint\Config\Config $config = null)
113 | {
114 | $configFactory = $this->configFactory;
115 |
116 | if (!isset($configFactory)) {
117 | if (!isset($this->cachePath)) {
118 | $configFactory = new \Peak\Config\ConfigFactory();
119 | } else {
120 | $configFactory = new ConfigCacheFactory(
121 | (string) $this->cacheId,
122 | (int) $this->cacheTtl,
123 | new \Peak\Config\ConfigFactory(),
124 | $this->cacheDriver ?? new FileCache($this->cachePath)
125 | );
126 | }
127 | }
128 |
129 | if (!isset($config)) {
130 | $config = new \Peak\Config\Config();
131 | }
132 |
133 | return $configFactory->loadResourcesWith($resources, $config);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Backpack/ConfigLoader.php:
--------------------------------------------------------------------------------
1 | getMethod() === 'GET');
16 | }
17 |
18 | /**
19 | * @param ServerRequestInterface $request
20 | * @return bool
21 | */
22 | public static function isPost(ServerRequestInterface $request): bool
23 | {
24 | return ($request->getMethod() === 'POST');
25 | }
26 |
27 | /**
28 | * @param ServerRequestInterface $request
29 | * @return bool
30 | */
31 | public static function isPut(ServerRequestInterface $request): bool
32 | {
33 | return ($request->getMethod() === 'PUT');
34 | }
35 |
36 | /**
37 | * @param ServerRequestInterface $request
38 | * @return bool
39 | */
40 | public static function isPatch(ServerRequestInterface $request): bool
41 | {
42 | return ($request->getMethod() === 'PATCH');
43 | }
44 |
45 | /**
46 | * @param ServerRequestInterface $request
47 | * @return bool
48 | */
49 | public static function isDelete(ServerRequestInterface $request): bool
50 | {
51 | return ($request->getMethod() === 'DELETE');
52 | }
53 | }
--------------------------------------------------------------------------------
/src/Bedrock/AbstractApplication.php:
--------------------------------------------------------------------------------
1 | kernel;
32 | }
33 |
34 | /**
35 | * @return ContainerInterface
36 | */
37 | public function getContainer(): ContainerInterface
38 | {
39 | return $this->kernel->getContainer();
40 | }
41 |
42 | /**
43 | * @param string $property
44 | * @param mixed $default
45 | * @return mixed
46 | */
47 | public function getProp(string $property, $default = null)
48 | {
49 | if (!isset($this->props)) {
50 | return $default;
51 | }
52 | return $this->props->get($property, $default);
53 | }
54 |
55 | /**
56 | * @param string $property
57 | * @return bool
58 | */
59 | public function hasProp(string $property): bool
60 | {
61 | if (!isset($this->props)) {
62 | return false;
63 | }
64 | return $this->props->has($property);
65 | }
66 |
67 | /**
68 | * @return Dictionary|null
69 | */
70 | public function getProps(): ?Dictionary
71 | {
72 | return $this->props;
73 | }
74 |
75 | /**
76 | * Bootstrap bootable processes
77 | * @param array $processes
78 | * @return $this
79 | * @throws InvalidBootableProcessException
80 | */
81 | public function bootstrap(array $processes)
82 | {
83 | $bootstrap = new Bootstrap($processes, $this->getContainer());
84 | $bootstrap->boot();
85 | return $this;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Bedrock/AbstractBootstrapper.php:
--------------------------------------------------------------------------------
1 | application = $application;
51 | $this->resolver = new BootableResolver($application->getContainer());
52 | }
53 |
54 | /**
55 | * Boot
56 | * @return bool|mixed
57 | * @throws \Exception
58 | */
59 | public function boot()
60 | {
61 | // call environment method if exists
62 | // e.g. envDev() envProd() envStating() envTesting()
63 | $envMethod = 'env'.ucfirst(strtolower($this->application->getKernel()->getEnv()));
64 | $this->call($envMethod);
65 |
66 | // run processes
67 | foreach ($this->processes as $process) {
68 | $processResolved = $this->resolver->resolve($process);
69 | $processResolved->boot();
70 | }
71 |
72 | // call method beginning by $bootMethodsPrefix
73 | $bootMethods = get_class_methods(get_class($this));
74 | if (!empty($bootMethods)) {
75 | $l = strlen($this->bootMethodsPrefix);
76 | foreach ($bootMethods as $bootMethod) {
77 | if (substr($bootMethod, 0, $l) === $this->bootMethodsPrefix) {
78 | $this->call($bootMethod);
79 | }
80 | }
81 | }
82 |
83 | return true;
84 | }
85 |
86 | /**
87 | * @param string $method
88 | */
89 | private function call(string $method)
90 | {
91 | if (method_exists($this, $method)) {
92 | if ('Peak\Di\Container' === get_class($this->application->getContainer())) {
93 | $this->application->getContainer()->call([$this, $method]);
94 | } else {
95 | $this->$method();
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Bedrock/Bootstrap/BootableResolver.php:
--------------------------------------------------------------------------------
1 | container = $container;
28 | }
29 |
30 | /**
31 | * Try to return a resolved item or throw an exception
32 | * @param mixed $item
33 | * @return Bootable
34 | * @throws InvalidBootableProcessException
35 | */
36 | public function resolve($item): Bootable
37 | {
38 | if(is_string($item) && class_exists($item)) {
39 | if (null !== $this->container) {
40 | $item = $this->container->get($item);
41 | }
42 | if (is_string($item)) {
43 | $item = new $item();
44 | }
45 | } elseif (is_callable($item)) {
46 | $item = new CallableProcess($item);
47 | }
48 |
49 | if (!$item instanceof Bootable) {
50 | throw new InvalidBootableProcessException($item);
51 | }
52 |
53 | return $item;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Bedrock/Bootstrap/Bootstrap.php:
--------------------------------------------------------------------------------
1 | processes = $processes;
30 | $this->resolver = new BootableResolver($container);
31 | }
32 |
33 | /**
34 | * Boot
35 | *
36 | * @return bool|mixed
37 | * @throws Exception\InvalidBootableProcessException
38 | */
39 | public function boot()
40 | {
41 | foreach ($this->processes as $process) {
42 | $processResolved = $this->resolver->resolve($process);
43 | $processResolved->boot();
44 | }
45 | return true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Bedrock/Bootstrap/CallableProcess.php:
--------------------------------------------------------------------------------
1 | callable = $callable;
23 | }
24 |
25 | /**
26 | * @return mixed|void
27 | */
28 | public function boot()
29 | {
30 | $fn = $this->callable;
31 | $fn();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Bedrock/Bootstrap/Exception/InvalidBootableProcessException.php:
--------------------------------------------------------------------------------
1 | process = $process;
23 | }
24 |
25 | /**
26 | * @return mixed
27 | */
28 | public function getProcess()
29 | {
30 | return $this->process;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Bedrock/Cli/Application.php:
--------------------------------------------------------------------------------
1 | kernel = $kernel;
34 | $this->props = $props;
35 |
36 | $name = 'CliApplication';
37 | $version = '0.1';
38 |
39 | if (isset($props)) {
40 | $name = $props->get('name', $name);
41 | $version = $props->get('version', $version);
42 | }
43 |
44 | $this->console = new \Symfony\Component\Console\Application($name, $version);
45 | }
46 |
47 | /**
48 | * @return \Symfony\Component\Console\Application
49 | */
50 | public function console(): \Symfony\Component\Console\Application
51 | {
52 | return $this->console;
53 | }
54 |
55 | /**
56 | * @param $commands
57 | * @return $this|mixed
58 | * @throws \Exception
59 | */
60 | public function add($commands)
61 | {
62 | if (!is_array($commands)) {
63 | $commands = [$commands];
64 | }
65 |
66 | foreach ($commands as $command) {
67 | if (is_string($command)) {
68 | $command = $this->getContainer()->get($command);
69 | }
70 | if (!$command instanceof Command) {
71 | throw new InvalidCommandException();
72 | }
73 | $this->console->add($command);
74 | }
75 | return $this;
76 | }
77 |
78 | /**
79 | * @param InputInterface|null $input
80 | * @param OutputInterface|null $output
81 | * @return mixed
82 | * @throws \Exception
83 | */
84 | public function run(InputInterface $input = null, OutputInterface $output = null)
85 | {
86 | return $this->console->run($input, $output);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Bedrock/Cli/Exception/InvalidCommandException.php:
--------------------------------------------------------------------------------
1 | startedGroups++;
30 | $this->currentPathPrefix .= $path;
31 | $this->handlers[$this->currentPathPrefix] = [];
32 | }
33 |
34 | /**
35 | * @param string $path
36 | */
37 | public function stopGroup(string $path): void
38 | {
39 | $this->startedGroups--;
40 | $this->currentPathPrefix = substr($this->currentPathPrefix, 0, strlen($this->currentPathPrefix) - strlen($path));
41 | }
42 |
43 | /**
44 | * @param $handler
45 | */
46 | public function holdHandler($handler): void
47 | {
48 | $this->handlers[$this->currentPathPrefix][] = $handler;
49 | }
50 |
51 | /**
52 | * @param array $handlers
53 | */
54 | public function holdHandlers(array $handlers): void
55 | {
56 | foreach ($handlers as $handler) {
57 | $this->holdHandler($handler);
58 | }
59 | }
60 |
61 | /**
62 | * @param string $path
63 | * @return array
64 | */
65 | public function getHandlers(string $path): array
66 | {
67 | return $this->handlers[$path];
68 | }
69 |
70 | /**
71 | * @return array
72 | */
73 | public function getAllHandlers(): array
74 | {
75 | return $this->handlers;
76 | }
77 |
78 | /**
79 | * @param string|null $path
80 | */
81 | public function releaseHandlers(string $path = null): void
82 | {
83 | (isset($path)) ? $this->handlers[$path] = [] : $this->handlers = [];
84 | }
85 |
86 | /**
87 | * @return int
88 | */
89 | public function countStartedGroups(): int
90 | {
91 | return $this->startedGroups;
92 | }
93 |
94 | public function currentlyInAGroup(): bool
95 | {
96 | return ($this->startedGroups > 0);
97 | }
98 |
99 | /**
100 | * @return string
101 | */
102 | public function getCurrentPathPrefix(): string
103 | {
104 | return $this->currentPathPrefix;
105 | }
106 |
107 | /**
108 | * @param string $path
109 | * @return string
110 | */
111 | public function getFullPathFor(string $path): string
112 | {
113 | return $this->currentPathPrefix.$path;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Bedrock/Kernel.php:
--------------------------------------------------------------------------------
1 | environment = $environment;
38 | $this->container = $container;
39 | $this->initialize();
40 | }
41 |
42 | /**
43 | * @return ContainerInterface
44 | */
45 | public function getContainer(): ContainerInterface
46 | {
47 | return $this->container;
48 | }
49 |
50 | /**
51 | * @return string
52 | */
53 | public function getEnv(): string
54 | {
55 | return $this->environment;
56 | }
57 |
58 | /**
59 | * Initialize kernel
60 | */
61 | public function initialize()
62 | {
63 | // nothing by default
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Bedrock/README.md:
--------------------------------------------------------------------------------
1 | # Peak\Bedrock
2 |
3 | This package help you to create:
4 | - HTTP Application compatible PSR-7, PSR-11 and PSR-15.
5 | - CLI application compatible PSR-11 (with symfony/console).
6 |
7 | ### Installation
8 |
9 | ```
10 | $ compose require peak/bedrock
11 | ```
12 |
13 | ### Documentation
14 |
15 | See https://peakphp.github.io/docs/bedrock
--------------------------------------------------------------------------------
/src/Bedrock/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/bedrock",
3 | "description": "Peak Bedrock Component",
4 | "keywords": ["peak", "bedrock", "http", "cli" ,"middleware", "psr-7", "psr-15", "application"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework"
10 | },
11 | "authors": [
12 | {
13 | "name": "Francois Lajoie",
14 | "email": "francois@peakframework.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.2",
19 | "peak/http": "^4.0",
20 | "peak/collection": "^4.0"
21 | },
22 | "suggest": {
23 | "symfony/console": "Required to use Peak\\Bedrock\\Cli\\Application (^4.3).",
24 | "symfony/process": "Required to use Peak\\Bedrock\\Cli\\Application (^4.3)."
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Peak\\Bedrock\\": ""
29 | }
30 | },
31 | "extra": {
32 | "branch-alias": {
33 | "dev-master": "4.0-dev"
34 | }
35 | },
36 | "minimum-stability": "dev",
37 | "prefer-stable": true
38 | }
--------------------------------------------------------------------------------
/src/Blueprint/Backpack/AppBuilderBase.php:
--------------------------------------------------------------------------------
1 | Peak/Blueprint
2 |
3 | Contains all framework packages interfaces.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ compose require peak/blueprint
9 | ```
10 |
11 | ### Documentation
12 |
13 | See https://peakphp.github.io/docs/blueprint
--------------------------------------------------------------------------------
/src/Blueprint/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/blueprint",
3 | "description": "Peak Blueprint Interfaces",
4 | "keywords": ["peak", "blueprint", "interfaces", "contract"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework"
10 | },
11 | "authors": [
12 | {
13 | "name": "Francois Lajoie",
14 | "email": "francois@peakframework.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.2"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Peak\\Blueprint\\": ""
23 | }
24 | },
25 | "extra": {
26 | "branch-alias": {
27 | "dev-master": "4.0-dev"
28 | }
29 | },
30 | "minimum-stability": "dev",
31 | "prefer-stable": true
32 | }
--------------------------------------------------------------------------------
/src/Collection/CollectionFlattener.php:
--------------------------------------------------------------------------------
1 | collection = $coll;
39 | }
40 |
41 | /**
42 | * Change keys separators
43 | * @param string $sep
44 | * @return $this
45 | * @throws Exception
46 | */
47 | public function separator(string $sep): CollectionFlattener
48 | {
49 | if (mb_strlen($sep) != 1 || $sep === '*') {
50 | throw new Exception(__CLASS__.': Separator must be 1 character and cannot be an asterisk (*)');
51 | }
52 | $this->separator = $sep;
53 | return $this;
54 | }
55 |
56 | /**
57 | * Simply flat all the collection
58 | * @return array
59 | */
60 | public function flatAll(): array
61 | {
62 | $this->search = [];
63 | return $this->flatCollection($this->collection->toArray());
64 | }
65 |
66 | /**
67 | * @param string $key
68 | * @return array
69 | */
70 | public function flatKey(string $key): array
71 | {
72 | $this->search = [$key];
73 | return $this->flatCollection($this->collection->toArray());
74 | }
75 |
76 | /**
77 | * Flat multiple keys names
78 | * @param array $keys
79 | * @return array
80 | */
81 | public function flatKeys(array $keys): array
82 | {
83 | $this->search = $keys;
84 | return $this->flatCollection($this->collection->toArray());
85 | }
86 |
87 | /**
88 | * Flat collection recursively to one level key,val array
89 | * @param array $data
90 | * @param string|null $prefix
91 | * @param array $flat_data
92 | * @return array
93 | */
94 | protected function flatCollection(array $data, string $prefix = null, array $flat_data = []): array
95 | {
96 | foreach ($data as $key => $val) {
97 | if ($prefix !== null) {
98 | $key = $prefix.$this->separator.$key;
99 | }
100 |
101 | $skip_key = $this->skipKey($key);
102 |
103 | if (is_array($val)) {
104 | $flat_data = array_merge(
105 | $flat_data,
106 | $this->flatCollection($val, (string) $key)
107 | );
108 | continue;
109 | }
110 |
111 | if ($skip_key) {
112 | continue;
113 | }
114 |
115 | $flat_data[$key] = $val;
116 | }
117 |
118 | return $flat_data;
119 | }
120 |
121 | /**
122 | * Detect if search is finishing by a wildcard (.*)
123 | * @param string $search
124 | * @return bool
125 | */
126 | protected function hasWildCard(string $search): bool
127 | {
128 | return (substr($search, -2) === $this->separator.'*');
129 | }
130 |
131 | /**
132 | * Detect if key must be skipped according to $search
133 | * @param string $key
134 | * @return bool
135 | */
136 | protected function skipKey(string $key): bool
137 | {
138 | if (!empty($this->search)) {
139 | foreach ($this->search as $search) {
140 | if ($this->hasWildCard($search)) {
141 | $search = substr($search, 0,-2);
142 | if (substr($key, 0, mb_strlen($search)) === $search) {
143 | return false;
144 | }
145 | } elseif ($search === $key) {
146 | return false;
147 | }
148 | }
149 | return true;
150 | }
151 | return false;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/Collection/DotNotationCollection.php:
--------------------------------------------------------------------------------
1 | items;
32 |
33 | if (!empty($path)) {
34 | $keys = $this->explode($path);
35 | foreach ($keys as $key) {
36 | if (!is_array($array) || !array_key_exists($key, $array)) {
37 | return $default;
38 | }
39 | $array = $array[$key];
40 | }
41 | }
42 |
43 | return $array;
44 | }
45 |
46 | /**
47 | * Add a path
48 | * @param string $path
49 | * @param mixed $value
50 | */
51 | public function set(string $path, $value): void
52 | {
53 | if (!empty($path)) {
54 | $at = & $this->items;
55 | $keys = $this->explode($path);
56 |
57 | while (count($keys) > 0) {
58 | if (count($keys) === 1) {
59 | if (!is_array($at)) {
60 | throw new RuntimeException('Can not set value at this path ['.$path.'] because is not array.');
61 | }
62 | $at[array_shift($keys)] = $value;
63 | } else {
64 | $key = array_shift($keys);
65 | if (!isset($at[$key])) {
66 | $at[$key] = [];
67 | }
68 | $at =& $at[$key];
69 | }
70 | }
71 | } else {
72 | $this->items = [$value];
73 | }
74 | }
75 |
76 | /**
77 | * Merge a path with an array
78 | * @param string $path
79 | * @param array $values
80 | */
81 | public function add(string $path, array $values): void
82 | {
83 | $get = (array)$this->get($path);
84 | $this->set($path, $this->arrayMergeRecursiveDistinct($get, $values));
85 | }
86 |
87 | /**
88 | * Check if we have path
89 | * @param string $path
90 | * @return bool
91 | */
92 | public function has(string $path): bool
93 | {
94 | $keys = $this->explode($path);
95 | $array = $this->items;
96 | foreach ($keys as $key) {
97 | if (!is_array($array) || !array_key_exists($key, $array)) {
98 | return false;
99 | }
100 | $array = $array[$key];
101 | }
102 | return true;
103 | }
104 |
105 | /**
106 | * Explode path string
107 | * @param string $path
108 | * @return mixed
109 | */
110 | protected function explode(string $path)
111 | {
112 | return explode(self::SEPARATOR, $path);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Collection/Exception/ImmutableException.php:
--------------------------------------------------------------------------------
1 | properties = $properties;
32 | }
33 |
34 | /**
35 | * @param string $prop
36 | * @param null $default
37 | * @return mixed
38 | */
39 | public function get(string $prop, $default = null)
40 | {
41 | return $this->properties[$prop] ?? $default;
42 | }
43 |
44 | /**
45 | * @param string $prop
46 | * @param mixed $value
47 | * @return mixed
48 | */
49 | public function set(string $prop, $value)
50 | {
51 | $this->properties[$prop] = $value;
52 | return $this;
53 | }
54 |
55 | /**
56 | * @param string $prop
57 | * @return bool
58 | */
59 | public function has(string $prop): bool
60 | {
61 | return array_key_exists($prop, $this->properties);
62 | }
63 |
64 | /**
65 | * @param string $property
66 | * @return mixed
67 | * @throws Exception
68 | */
69 | public function &__get(string $property)
70 | {
71 | if (!array_key_exists($property, $this->properties)) {
72 | throw new Exception('Property '.$property.' not found');
73 | }
74 |
75 | return $this->properties[$property];
76 | }
77 |
78 | /**
79 | * @param string $property
80 | * @param mixed $val
81 | */
82 | public function __set(string $property, $val)
83 | {
84 | $this->properties[$property] = $val;
85 | }
86 |
87 | /**
88 | * @param string $property
89 | * @return bool
90 | */
91 | public function __isset(string $property): bool
92 | {
93 | return array_key_exists($property, $this->properties);
94 | }
95 |
96 | /**
97 | * @param string $property
98 | */
99 | public function __unset(string $property)
100 | {
101 | unset($this->properties[$property]);
102 | }
103 |
104 | /**
105 | * Assign a value to the specified offset
106 | *
107 | * @param string $offset
108 | * @param mixed $value
109 | */
110 | public function offsetSet($offset, $value)
111 | {
112 | $this->properties[$offset] = $value;
113 | }
114 |
115 | /**
116 | * Whether an item exists
117 | *
118 | * @param string $offset
119 | * @return bool
120 | */
121 | public function offsetExists($offset): bool
122 | {
123 | return isset($this->properties[$offset]);
124 | }
125 |
126 | /**
127 | * Item to delete
128 | *
129 | * @param string $offset
130 | */
131 | public function offsetUnset($offset)
132 | {
133 | unset($this->properties[$offset]);
134 | }
135 |
136 | /**
137 | * Offset to retrieve
138 | *
139 | * @param string $offset
140 | * @return mixed
141 | */
142 | public function offsetGet($offset)
143 | {
144 | return isset($this->properties[$offset]) ? $this->properties[$offset] : null;
145 | }
146 |
147 | /**
148 | * @return int
149 | */
150 | public function count()
151 | {
152 | return count($this->properties);
153 | }
154 |
155 | /**
156 | * @return ArrayIterator
157 | */
158 | public function getIterator()
159 | {
160 | return new ArrayIterator($this->properties);
161 | }
162 |
163 | /**
164 | * @return array
165 | */
166 | public function toArray()
167 | {
168 | return $this->properties;
169 | }
170 |
171 | /**
172 | * @return string
173 | */
174 | public function serialize()
175 | {
176 | return serialize($this->properties);
177 | }
178 |
179 | /**
180 | * @param string $data
181 | */
182 | public function unserialize($data)
183 | {
184 | $this->properties = unserialize($data);
185 | }
186 |
187 | /**
188 | * @return array|mixed
189 | */
190 | public function jsonSerialize()
191 | {
192 | return $this->properties;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/Collection/README.md:
--------------------------------------------------------------------------------
1 | #
Peak/Collection
2 |
3 | This package adds additional functionality on top of PHP arrays and typed structure.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ composer require peak/collection
9 | ```
10 |
11 | ### Documentation
12 |
13 | See https://peakphp.github.io/docs/collection
--------------------------------------------------------------------------------
/src/Collection/Structure/AbstractImmutableStructure.php:
--------------------------------------------------------------------------------
1 | types = $types;
38 | $this->default = $default;
39 | }
40 |
41 | /**
42 | * @return array
43 | */
44 | public function getTypes(): array
45 | {
46 | return $this->types;
47 | }
48 |
49 | /**
50 | * @return mixed
51 | */
52 | public function getDefault()
53 | {
54 | return $this->default;
55 | }
56 |
57 | /**
58 | * @param mixed $defaultValue
59 | * @return $this
60 | */
61 | public function default($defaultValue)
62 | {
63 | $this->default = $defaultValue;
64 | return $this;
65 | }
66 |
67 | /**
68 | * @return $this
69 | */
70 | public function null()
71 | {
72 | $this->types[] = self::NIL;
73 | return $this;
74 | }
75 |
76 | /**
77 | * @return DataType
78 | */
79 | public function string(): DataType
80 | {
81 | $this->types[] = self::STRING;
82 | return $this;
83 | }
84 |
85 | /**
86 | * @return DataType
87 | */
88 | public function integer(): DataType
89 | {
90 | $this->types[] = self::INT;
91 | return $this;
92 | }
93 |
94 | /**
95 | * @return DataType
96 | */
97 | public function float(): DataType
98 | {
99 | $this->types[] = self::FLOAT;
100 | return $this;
101 | }
102 |
103 | /**
104 | * @return DataType
105 | */
106 | public function boolean(): DataType
107 | {
108 | $this->types[] = self::BOOL;
109 | return $this;
110 | }
111 |
112 | /**
113 | * @return DataType
114 | */
115 | public function array(): DataType
116 | {
117 | $this->types[] = self::ARRAY;
118 | return $this;
119 | }
120 |
121 | /**
122 | * @param string $className
123 | * @return DataType
124 | */
125 | public function object(string $className = 'object'): DataType
126 | {
127 | $this->types[] = $className;
128 | return $this;
129 | }
130 |
131 | /**
132 | * @return DataType
133 | */
134 | public function resource(): DataType
135 | {
136 | $this->types[] = self::RESOURCE;
137 | return $this;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Collection/Structure/Exception/InvalidPropertyDefinitionException.php:
--------------------------------------------------------------------------------
1 | class = get_class($class);
27 | $this->propertyName = $propertyName;
28 | parent::__construct(getClassShortName($this->class).' - Structure definition for [' . $propertyName . '] must be an instance of DataType');
29 | }
30 |
31 | /**
32 | * @return object
33 | */
34 | public function getClass(): object
35 | {
36 | return $this->class;
37 | }
38 |
39 | /**
40 | * @return string
41 | */
42 | public function getPropertyName(): string
43 | {
44 | return $this->propertyName;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Collection/Structure/Exception/InvalidPropertyTypeException.php:
--------------------------------------------------------------------------------
1 | class = get_class($class);
42 | $this->propertyName = $propertyName;
43 | $this->typesExpected = $typesExpected;
44 | $this->typeReceived = $typeReceived;
45 | $typesExpectedString = implode(' OR ', $typesExpected);
46 | parent::__construct(
47 | getClassShortName($this->class).' - Property ['.$propertyName.'] expect a type of ('.$typesExpectedString.') ... '. $typeReceived . ' given'
48 | );
49 | }
50 |
51 | /**
52 | * @return string
53 | */
54 | public function getClass(): string
55 | {
56 | return $this->class;
57 | }
58 |
59 | /**
60 | * @return string
61 | */
62 | public function getPropertyName(): string
63 | {
64 | return $this->propertyName;
65 | }
66 |
67 | /**
68 | * @return array
69 | */
70 | public function getTypesExpected(): array
71 | {
72 | return $this->typesExpected;
73 | }
74 |
75 | /**
76 | * @return string
77 | */
78 | public function getTypeReceived(): string
79 | {
80 | return $this->typeReceived;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Collection/Structure/Exception/InvalidStructureException.php:
--------------------------------------------------------------------------------
1 | class = get_class($class);
27 | $this->propertyName = $propertyName;
28 | parent::__construct(getClassShortName($this->class).' - Property [' . $propertyName . '] is undefined in the structure');
29 | }
30 |
31 | /**
32 | * @return string
33 | */
34 | public function getPropertyName(): string
35 | {
36 | return $this->propertyName;
37 | }
38 |
39 | /**
40 | * @return string
41 | */
42 | public function getClass(): string
43 | {
44 | return $this->class;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Collection/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/collection",
3 | "description": "Peak Collection Component",
4 | "keywords": ["peak", "collection", "bag", "utilities"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework"
10 | },
11 | "authors": [
12 | {
13 | "name": "Francois Lajoie",
14 | "email": "francois@peakframework.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.2",
19 | "peak/blueprint": "^4.0",
20 | "peak/common": "^4.0"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Peak\\Collection\\": ""
25 | }
26 | },
27 | "extra": {
28 | "branch-alias": {
29 | "dev-master": "4.0-dev"
30 | }
31 | },
32 | "minimum-stability": "dev",
33 | "prefer-stable": true
34 | }
--------------------------------------------------------------------------------
/src/Common/Chrono/Chrono.php:
--------------------------------------------------------------------------------
1 |
32 | */
33 | private $steps = [];
34 |
35 | /**
36 | * Chrono constructor.
37 | * @param string $name
38 | */
39 | public function __construct(string $name = '')
40 | {
41 | $this->name = $name;
42 | $this->start = $this->getMicroTime();
43 | }
44 |
45 | /**
46 | * @return string
47 | */
48 | public function getName(): string
49 | {
50 | return $this->name;
51 | }
52 |
53 | /**
54 | * @return array
55 | */
56 | public function getSteps(): array
57 | {
58 | return $this->steps;
59 | }
60 |
61 | /**
62 | * @return bool
63 | */
64 | public function stop(): bool
65 | {
66 | if (isset($this->end)) {
67 | return false;
68 | }
69 | $this->end = $this->getMicroTime();
70 | return true;
71 | }
72 |
73 | /**
74 | * @param string $id
75 | * @param string $description
76 | */
77 | public function markStep(string $id, string $description = '')
78 | {
79 | $time = $this->start;
80 | if (!empty($this->steps)) {
81 | $lastStep = $this->steps[count($this->steps) - 1];
82 | $time = $lastStep->getStart() + $lastStep->getDuration();
83 | }
84 | $this->steps[] = new Step($time, $id, $description);
85 | }
86 |
87 | /**
88 | * @return bool
89 | */
90 | public function isStopped(): bool
91 | {
92 | return (isset($this->end));
93 | }
94 |
95 | /**
96 | * @param int $decimalPrecision
97 | * @return float
98 | */
99 | public function getSec(int $decimalPrecision = 2): float
100 | {
101 | return $this->formatSec($this->getElapsed(), $decimalPrecision);
102 | }
103 |
104 | /**
105 | * @param int $decimalPrecision
106 | * @return float
107 | */
108 | public function getMs(int $decimalPrecision = 2): float
109 | {
110 | return $this->formatMs($this->getElapsed(), $decimalPrecision);
111 | }
112 |
113 | /**
114 | * @return float
115 | */
116 | private function getElapsed(): float
117 | {
118 | $end = $this->end ?? $this->getMicroTime();
119 | return $end - $this->start;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Common/Chrono/Step.php:
--------------------------------------------------------------------------------
1 | start = $start;
43 | $this->duration = $this->getMicroTime() - $start;
44 | $this->id = $id;
45 | $this->description = $description;
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getId(): string
52 | {
53 | return $this->id;
54 | }
55 |
56 | /**
57 | * @return string
58 | */
59 | public function getDescription(): string
60 | {
61 | return $this->description;
62 | }
63 |
64 | /**
65 | * @return float
66 | */
67 | public function getStart(): float
68 | {
69 | return $this->start;
70 | }
71 |
72 | /**
73 | * @return float
74 | */
75 | public function getDuration(): float
76 | {
77 | return $this->duration;
78 | }
79 |
80 | /**
81 | * @param int $decimalPrecision
82 | * @return float
83 | */
84 | public function getSec(int $decimalPrecision = 2): float
85 | {
86 | return $this->formatSec($this->duration, $decimalPrecision);
87 | }
88 |
89 | /**
90 | * @param int $decimalPrecision
91 | * @return float
92 | */
93 | public function getMs(int $decimalPrecision = 2): float
94 | {
95 | return $this->formatMs($this->duration, $decimalPrecision);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Common/PhpIni.php:
--------------------------------------------------------------------------------
1 | $val) {
26 | if (!is_array($val)) {
27 | $this->set($setting, $val, $strict);
28 | } else {
29 | foreach ($val as $k => $v) {
30 | $this->set($setting.'.'.$k, $v, $strict);
31 | }
32 | }
33 | }
34 | }
35 |
36 | /**
37 | * @param string $option
38 | * @param mixed $value
39 | * @param bool $strict
40 | * @throws \Exception
41 | */
42 | private function set(string $option, $value, bool $strict = false)
43 | {
44 | $result = @ini_set($option, (string)$value);
45 | if ($strict && $result === false) {
46 | throw new \Exception('Fail to set php option '.$option.' to "'.print_r($value, true).'"');
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Common/README.md:
--------------------------------------------------------------------------------
1 | # Peak/Common
2 |
3 | This component contains a bunch of useful and generic tools.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ composer require peak/common
9 | ```
10 |
11 | ### Documentation
12 |
13 | See https://peakphp.github.io/docs/blueprint
--------------------------------------------------------------------------------
/src/Common/Traits/ArrayMergeRecursiveDistinct.php:
--------------------------------------------------------------------------------
1 | $value) {
25 | if (!$mergeNumericKeys && is_numeric($key)) {
26 | $merged[] = $value;
27 | continue;
28 | }
29 | if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key])) {
30 | $merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
31 | } else {
32 | $merged[$key] = $value;
33 | }
34 | }
35 |
36 | return $merged;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Common/Traits/LoadArrayFiles.php:
--------------------------------------------------------------------------------
1 | array_files_content[] = $content;
42 | }
43 |
44 | return $this->array_files_content;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Common/Traits/Macro.php:
--------------------------------------------------------------------------------
1 | macros[$name] = Closure::bind($macroCallable, $this, get_class());
26 | return $this;
27 | }
28 |
29 | /**
30 | * @param string $name
31 | * @return bool
32 | */
33 | public function hasMacro(string $name)
34 | {
35 | return isset($this->macros[$name]);
36 | }
37 |
38 | /**
39 | * Call a macro
40 | * @param string $name
41 | * @param array $args
42 | * @return mixed
43 | */
44 | public function callMacro(string $name, array $args)
45 | {
46 | if (isset($this->macros[$name])) {
47 | return call_user_func_array($this->macros[$name], $args);
48 | }
49 | throw new RuntimeException('There is no macro with the given name "'.$name.'" to call');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Common/Traits/MicroTime.php:
--------------------------------------------------------------------------------
1 | $v) {
19 | $k = $this->updateStringToCamelCase($k);
20 | $newData[$k] = $v;
21 | }
22 | return $newData;
23 | }
24 |
25 | /**
26 | * @param string $string
27 | * @return string
28 | */
29 | protected function updateStringToCamelCase(string $text): string
30 | {
31 | return lcfirst(str_replace(['_', '-', ' '],'', lcfirst(ucwords(strtolower($text), '_- '))));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Common/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/common",
3 | "description": "Peak Common Component",
4 | "keywords": ["peak", "common", "misc", "tools", "library", "utilities"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework"
10 | },
11 | "authors": [
12 | {
13 | "name": "Francois Lajoie",
14 | "email": "francois@peakframework.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.2"
19 | },
20 | "autoload": {
21 | "files": [
22 | "helpers.php"
23 | ],
24 | "psr-4": {
25 | "Peak\\Common\\": ""
26 | }
27 | },
28 | "extra": {
29 | "branch-alias": {
30 | "dev-master": "4.0-dev"
31 | }
32 | },
33 | "minimum-stability": "dev",
34 | "prefer-stable": true
35 |
36 | }
--------------------------------------------------------------------------------
/src/Config/Config.php:
--------------------------------------------------------------------------------
1 | cacheId = $cacheId;
48 | $this->ttl = $ttl;
49 | $this->configFactory = $configFactory;
50 | $this->configCache = $configCache;
51 | }
52 |
53 | /**
54 | * @param array $resources
55 | * @return ConfigBlueprint
56 | * @throws \Psr\SimpleCache\InvalidArgumentException
57 | */
58 | public function loadResources(array $resources): ConfigBlueprint
59 | {
60 | return $this->load($this->cacheId, $resources);
61 | }
62 |
63 | /**
64 | * @param array $resources
65 | * @param ConfigBlueprint $customConfig
66 | * @return ConfigBlueprint
67 | * @throws \Psr\SimpleCache\InvalidArgumentException
68 | */
69 | public function loadResourcesWith(array $resources, ConfigBlueprint $customConfig): ConfigBlueprint
70 | {
71 | return $this->load($this->cacheId, $resources, $customConfig);
72 | }
73 |
74 | /**
75 | * @param string $cacheId
76 | * @param array $resources
77 | * @param ConfigBlueprint|null $customConfig
78 | * @return ConfigBlueprint
79 | * @throws \Psr\SimpleCache\InvalidArgumentException
80 | */
81 | protected function load(string $cacheId, array $resources, ConfigBlueprint $customConfig = null): ConfigBlueprint
82 | {
83 | $config = $this->configCache->get($cacheId);
84 | if (null !== $config) {
85 | return $config;
86 | }
87 |
88 | if (isset($customConfig)) {
89 | $config = $this->configFactory->loadResourcesWith($resources, $customConfig);
90 | } else {
91 | $config = $this->configFactory->loadResources($resources);
92 | }
93 |
94 | $this->configCache->set($cacheId, $config, $this->ttl);
95 |
96 | return $config;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Config/ConfigFactory.php:
--------------------------------------------------------------------------------
1 | configResolver = $configResolver;
30 | }
31 |
32 | /**
33 | * @param FilesHandlers|null $filesHandlers
34 | * @return $this
35 | */
36 | public function setFilesHandlers(?FilesHandlers $filesHandlers)
37 | {
38 | $this->filesHandlers = $filesHandlers;
39 | return $this;
40 | }
41 |
42 | /**
43 | * @param array $resources
44 | * @return ConfigBlueprint
45 | * @throws UnknownResourceException
46 | */
47 | public function loadResources(array $resources): ConfigBlueprint
48 | {
49 | return $this->processResources($resources, new Config());
50 | }
51 |
52 | /**
53 | * @param array $resources
54 | * @param ConfigBlueprint $customConfig
55 | * @return ConfigBlueprint
56 | * @throws UnknownResourceException
57 | */
58 | public function loadResourcesWith(array $resources, ConfigBlueprint $customConfig): ConfigBlueprint
59 | {
60 | return $this->processResources($resources, $customConfig);
61 | }
62 |
63 | /**
64 | * @param array $resources
65 | * @param ConfigBlueprint $config
66 | * @return ConfigBlueprint
67 | * @throws UnknownResourceException
68 | */
69 | protected function processResources(array $resources, ConfigBlueprint $config): ConfigBlueprint
70 | {
71 | $filesHandler = $this->filesHandlers;
72 | if (!isset($filesHandler)) {
73 | $filesHandler = new FilesHandlers(null);
74 | }
75 |
76 | $configResolver = $this->configResolver;
77 | if (!isset($configResolver)) {
78 | $configResolver = new ConfigResolver($filesHandler);
79 | }
80 |
81 | foreach ($resources as $resource) {
82 | $stream = $configResolver->resolve($resource);
83 | // @todo fix method not in signature
84 | $config->mergeRecursiveDistinct($stream->get());
85 | }
86 | return $config;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Config/ConfigResolver.php:
--------------------------------------------------------------------------------
1 | filesHandlers = $filesHandlers;
40 | }
41 |
42 | /**
43 | * Resolve a config resource to a valid StreamInterface
44 | *
45 | * @param mixed $resource
46 | * @return Stream
47 | * @throws UnknownResourceException
48 | */
49 | public function resolve($resource): Stream
50 | {
51 | // detect best way to load and process configuration content
52 | if (is_array($resource)) {
53 | return new DataStream($resource, new ArrayProcessor());
54 | } elseif (is_callable($resource)) {
55 | return new DataStream($resource, new CallableProcessor());
56 | } elseif ($resource instanceof Config) {
57 | return new DataStream($resource, new ConfigProcessor());
58 | } elseif ($resource instanceof Collection) {
59 | return new DataStream($resource, new CollectionProcessor());
60 | } elseif ($resource instanceof Stream) {
61 | return $resource;
62 | } elseif ($resource instanceof stdClass) {
63 | return new DataStream($resource, new StdClassProcessor());
64 | } elseif (is_string($resource)) {
65 | $filesHandlers = $this->filesHandlers;
66 | if (null === $filesHandlers) {
67 | $filesHandlers = new FilesHandlers(null);
68 | }
69 | return new FileStream($resource, $filesHandlers);
70 | }
71 |
72 | throw new UnknownResourceException($resource);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Config/Exception/CacheInvalidKeyException.php:
--------------------------------------------------------------------------------
1 | path = $path;
25 | }
26 |
27 | /**
28 | * @return string
29 | */
30 | public function getPath(): string
31 | {
32 | return $this->path;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Config/Exception/CachePathNotWritableException.php:
--------------------------------------------------------------------------------
1 | path = $path;
25 | }
26 |
27 | /**
28 | * @return string
29 | */
30 | public function getPath(): string
31 | {
32 | return $this->path;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Config/Exception/FileNotFoundException.php:
--------------------------------------------------------------------------------
1 | configFile = $configFile;
24 | }
25 |
26 | /**
27 | * @return string
28 | */
29 | public function getConfigFile(): string
30 | {
31 | return $this->file;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Config/Exception/FileNotReadableException.php:
--------------------------------------------------------------------------------
1 | configFile = $configFile;
24 | }
25 |
26 | /**
27 | * @return string
28 | */
29 | public function getConfigFile(): string
30 | {
31 | return $this->configFile;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Config/Exception/LoaderException.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
23 | parent::__construct('Unknown config resources');
24 | }
25 |
26 | /**
27 | * @return mixed
28 | */
29 | public function getResource()
30 | {
31 | return $this->resource;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Config/FilesHandlers.php:
--------------------------------------------------------------------------------
1 | [
25 | 'loader' => \Peak\Config\Loader\PhpLoader::class,
26 | 'processor' => \Peak\Config\Processor\ArrayProcessor::class
27 | ],
28 | 'json' => [
29 | 'loader' => \Peak\Config\Loader\DefaultLoader::class,
30 | 'processor' => \Peak\Config\Processor\JsonProcessor::class
31 | ],
32 | 'yml' => [
33 | 'loader' => \Peak\Config\Loader\DefaultLoader::class,
34 | 'processor' => \Peak\Config\Processor\YamlProcessor::class
35 | ],
36 | 'ini' => [
37 | 'loader' => \Peak\Config\Loader\DefaultLoader::class,
38 | 'processor' => \Peak\Config\Processor\IniProcessor::class
39 | ],
40 | 'txt' => [
41 | 'loader' => \Peak\Config\Loader\TextLoader::class,
42 | 'processor' => \Peak\Config\Processor\ArrayProcessor::class
43 | ],
44 | 'log' => [
45 | 'loader' => \Peak\Config\Loader\TextLoader::class,
46 | 'processor' => \Peak\Config\Processor\ArrayProcessor::class
47 | ],
48 | 'xml' => [
49 | 'loader' => \Peak\Config\Loader\DefaultLoader::class,
50 | 'processor' => \Peak\Config\Processor\XmlProcessor::class
51 | ],
52 | 'env' => [
53 | 'loader' => \Peak\Config\Loader\DefaultLoader::class,
54 | 'processor' => \Peak\Config\Processor\EnvProcessor::class
55 | ],
56 | ];
57 |
58 |
59 | /**
60 | * FilesHandlers constructor.
61 | *
62 | * @param array|null $handlers if null, $defaultHandlers is used instead
63 | */
64 | public function __construct(?array $handlers)
65 | {
66 | if (null === $handlers) {
67 | $handlers = $this->defaultHandlers;
68 | }
69 | $this->handlers = $handlers;
70 | }
71 |
72 | /**
73 | * Check if we have a specific file handlers
74 | *
75 | * @param string $name
76 | * @return bool
77 | */
78 | public function has(string $name): bool
79 | {
80 | return array_key_exists($name, $this->handlers);
81 | }
82 |
83 | /**
84 | * Get a file handlers
85 | *
86 | * @param string $name
87 | * @return array
88 | * @throws NoFileHandlersException
89 | */
90 | public function get(string $name): array
91 | {
92 | if (!$this->has($name)) {
93 | throw new NoFileHandlersException($name);
94 | }
95 |
96 | return $this->handlers[$name];
97 | }
98 |
99 | /**
100 | * Get a file loader
101 | *
102 | * @param string $name
103 | * @return ResourceLoader
104 | * @throws NoFileHandlersException
105 | */
106 | public function getLoader(string $name): ResourceLoader
107 | {
108 | $handlers = $this->get($name);
109 | return new $handlers['loader']();
110 | }
111 |
112 | /**
113 | * Get a file processor
114 | *
115 | * @param string $name
116 | * @return ResourceProcessor
117 | * @throws NoFileHandlersException
118 | */
119 | public function getProcessor(string $name): ResourceProcessor
120 | {
121 | $handlers = $this->get($name);
122 | return new $handlers['processor']();
123 | }
124 |
125 | /**
126 | * Get current files handlers
127 | *
128 | * @return array
129 | */
130 | public function getAll(): array
131 | {
132 | return $this->handlers;
133 | }
134 |
135 | /**
136 | * Add or override a file handlers
137 | *
138 | * @param string $name
139 | * @param string $loader
140 | * @param string $processor
141 | * @return FilesHandlers
142 | */
143 | public function set(string $name, string $loader, string $processor): FilesHandlers
144 | {
145 | $this->handlers[$name] = [
146 | 'loader' => new $loader(),
147 | 'processor' => new $processor(),
148 | ];
149 | return $this;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Config/Loader/DefaultLoader.php:
--------------------------------------------------------------------------------
1 | toArray();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Config/Processor/ConfigProcessor.php:
--------------------------------------------------------------------------------
1 | toArray();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Config/Processor/EnvProcessor.php:
--------------------------------------------------------------------------------
1 | load($data);
35 | return $this->content;
36 | }
37 |
38 | /**
39 | * Loads in the ini file specified in filename, and returns the settings in
40 | * it as an associative multi-dimensional array
41 | * @param string $data
42 | * @throws ProcessorException
43 | */
44 | public function load(string $data)
45 | {
46 | // we silence error(s) so we can catch them and throw a proper exception after
47 | $data = @parse_ini_string($data, true);
48 |
49 | // fail if there was an error while processing the specified ini file
50 | if ($data === false) {
51 | $error = error_get_last();
52 | throw new ProcessorException(__CLASS__.' fail to parse data: '.$error['message']);
53 | }
54 |
55 | // reset the result array
56 | $this->content = [];
57 |
58 | // loop through each section
59 | foreach ($data as $section => $contents) {
60 | $this->processSection($section, $contents);
61 | }
62 | }
63 |
64 | /**
65 | * Process contents of the specified section
66 | *
67 | * @param string $section Section name
68 | * @param array $contents Section contents
69 | * @throws ProcessorException
70 | */
71 | private function processSection(string $section, array $contents)
72 | {
73 | // the section does not extend another section
74 | if (stripos($section, ':') === false) {
75 | $this->content[$section] = $this->processSectionContents($contents);
76 | return;
77 | }
78 |
79 | // section extends another section
80 | // extract section names
81 | list($ext_target, $ext_source) = explode(':', $section);
82 | $ext_target = trim($ext_target);
83 | $ext_source = trim($ext_source);
84 |
85 | // check if the extended section exists
86 | if (!isset($this->content[$ext_source])) {
87 | throw new ProcessorException(__CLASS__.': Unable to extend section ' . $ext_source . ', section not found');
88 | }
89 |
90 | // process section contents
91 | $this->content[$ext_target] = $this->processSectionContents($contents);
92 |
93 | // merge the new section with the existing section values
94 | $this->content[$ext_target] = $this->arrayMergeRecursiveDistinct(
95 | $this->content[$ext_source],
96 | $this->content[$ext_target]
97 | );
98 | }
99 |
100 | /**
101 | * Process contents of a section
102 | *
103 | * @param array $contents Section contents
104 | * @return array
105 | */
106 | private function processSectionContents(array $contents)
107 | {
108 | $result = [];
109 |
110 | // loop through each line and convert it to an array
111 | foreach ($contents as $path => $value) {
112 | // convert all a.b.c.d to multi-dimensional arrays
113 | $process = $this->processContentEntry($path, $value);
114 | // merge the current line with all previous ones
115 | $result = $this->arrayMergeRecursiveDistinct($result, $process);
116 | }
117 |
118 | return $result;
119 | }
120 |
121 | /**
122 | * Convert a.b.c.d paths to multi-dimensional arrays
123 | *
124 | * @param string $path Current ini file's line's key
125 | * @param mixed $value Current ini file's line's value
126 | * @return array
127 | */
128 | private function processContentEntry($path, $value)
129 | {
130 | $pos = strpos($path, '.');
131 |
132 | if ($pos === false) {
133 | return [
134 | $path => $value
135 | ];
136 | }
137 |
138 | $key = substr($path, 0, $pos);
139 | $path = substr($path, $pos + 1);
140 |
141 | return [
142 | $key => $this->processContentEntry($path, $value)
143 | ];
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/Config/Processor/JsonProcessor.php:
--------------------------------------------------------------------------------
1 | '.json_last_error_msg());
28 | }
29 |
30 | return $content;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Config/Processor/StdClassProcessor.php:
--------------------------------------------------------------------------------
1 | data = $data;
30 | $this->processor = $processor;
31 | }
32 |
33 | /**
34 | * @return array
35 | */
36 | public function get(): array
37 | {
38 | return $this->processor->process($this->data);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Config/Stream/FileStream.php:
--------------------------------------------------------------------------------
1 | file = $file;
34 | $this->handlers = $handlers;
35 | }
36 |
37 | /**
38 | * @return array
39 | * @throws \Peak\Config\Exception\NoFileHandlersException
40 | */
41 | public function get(): array
42 | {
43 | $ext = strtolower(pathinfo($this->file, PATHINFO_EXTENSION));
44 |
45 | $loader = $this->handlers->getLoader($ext);
46 | $processor = $this->handlers->getProcessor($ext);
47 |
48 | return $processor->process($loader->load($this->file));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Config/Stream/JsonStream.php:
--------------------------------------------------------------------------------
1 | =7.2",
20 | "psr/simple-cache": "^1.0",
21 | "peak/blueprint": "^4.0",
22 | "peak/collection": "^4.0"
23 | },
24 | "suggest": {
25 | "symfony/yaml": "Required to parse yaml file with Peak\\Config (^4.3)."
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "Peak\\Config\\": ""
30 | },
31 | "exclude-from-classmap": [
32 | "/Tests/"
33 | ]
34 | },
35 | "minimum-stability": "dev",
36 | "prefer-stable": true,
37 | "extra": {
38 | "branch-alias": {
39 | "dev-master": "4.0-dev"
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Di/ArrayDefinition.php:
--------------------------------------------------------------------------------
1 | instantiator = new ClassInstantiator();
43 | $this->classResolver = new ClassResolver();
44 | }
45 |
46 | /**
47 | * @param array $definition
48 | * @param Container $container
49 | * @param array $args
50 | * @return object
51 | * @throws Exception\AmbiguousResolutionException
52 | * @throws Exception\ClassDefinitionNotFoundException
53 | * @throws Exception\InterfaceNotFoundException
54 | * @throws InfiniteLoopResolutionException
55 | * @throws \ReflectionException
56 | */
57 | public function resolve(array $definition, Container $container, array $args = [])
58 | {
59 | $final_args = $definition;
60 | if (!empty($args)) { // add create argument at the end
61 | foreach ($args as $arg) {
62 | $final_args[] = $arg;
63 | }
64 | }
65 | $definition = array_shift($final_args);
66 |
67 | foreach ($final_args as $index => $arg) {
68 | if (is_array($arg)) {
69 | $final_args[$index] = $this->resolve($arg, $container);
70 | } elseif (is_callable($arg)) {
71 | $final_args[$index] = $arg($container);
72 | } elseif (is_object($arg)) {
73 | $final_args[$index] = $arg;
74 | } elseif (class_exists($arg) && $this->newInstanceOnly) {
75 | $this->n++;
76 | if ($this->n > 1) {
77 | throw new InfiniteLoopResolutionException($definition);
78 | }
79 | $subArg = $this->classResolver->resolve($arg, $container, $args);
80 | $final_args[$index] = $this->instantiator->instantiate($arg, $subArg);
81 | } elseif (class_exists($arg) && !$this->newInstanceOnly) {
82 | if ($container->has($arg)) {
83 | $final_args[$index] = $container->get($arg);
84 | } elseif ($container->hasDefinition($arg)) {
85 | $final_args[$index] = $container->resolve($arg);
86 | } else {
87 | $this->n++;
88 | if ($this->n > 1) {
89 | throw new InfiniteLoopResolutionException($definition);
90 | }
91 | $subArg = $this->classResolver->resolve($arg, $container, $args);
92 | $final_args[$index] = $this->instantiator->instantiate($arg, $subArg);
93 | }
94 | }
95 | }
96 |
97 | return $this->instantiator->instantiate($definition, $final_args);
98 | }
99 |
100 | /**
101 | * Set string resolution to new instance only
102 | *
103 | * @return $this
104 | */
105 | public function newInstancesOnly()
106 | {
107 | $this->newInstanceOnly = true;
108 | return $this;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Di/Binding/AbstractBinding.php:
--------------------------------------------------------------------------------
1 | name = $name;
43 | $this->type = $type;
44 | $this->definition = $definition;
45 | }
46 |
47 | /**
48 | * Get name
49 | *
50 | * @return string
51 | */
52 | public function getName()
53 | {
54 | return $this->name;
55 | }
56 |
57 | /**
58 | * @return int|mixed
59 | */
60 | public function getType()
61 | {
62 | return $this->type;
63 | }
64 |
65 | /**
66 | * Get definition
67 | *
68 | * @return string
69 | */
70 | public function getDefinition()
71 | {
72 | return $this->definition;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Di/Binding/BindingInterface.php:
--------------------------------------------------------------------------------
1 | definition;
36 |
37 | if (!is_null($explicit) && !empty($explicit)) {
38 | $definition = $explicit;
39 | }
40 |
41 | return $definition($container, $args);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Di/Binding/Prototype.php:
--------------------------------------------------------------------------------
1 | instantiator = new ClassInstantiator();
49 | $this->arrayDefinition = new ArrayDefinition();
50 | $this->classResolver = new ClassResolver();
51 | parent::__construct($name, self::PROTOTYPE, $definition);
52 | }
53 |
54 | /**
55 | * Resolve the binding
56 | *
57 | * @param Container $container
58 | * @param array $args
59 | * @param callable|null $explicit
60 | * @return mixed|null
61 | * @throws \Exception
62 | */
63 | public function resolve(Container $container, array $args = [], $explicit = null)
64 | {
65 | $definition = $this->definition;
66 |
67 | if (!is_null($explicit) && !empty($explicit)) {
68 | $definition = $explicit;
69 | }
70 |
71 | if (is_string($definition)) {
72 | // a string should be resolved once only, if more than one, it mean that the class behind the
73 | // string have also dependency on itself and may go on infinite loop
74 | $this->n++;
75 | if ($this->n > 1) {
76 | throw new InfiniteLoopResolutionException($definition);
77 | }
78 | $args = $this->classResolver->resolve($definition, $container, $args, $explicit);
79 | $instance = $this->instantiator->instantiate($definition, $args);
80 | // since it is a prototype, when we reach this line, it mean that prototype has been resolved properly,
81 | // so we decrease $n to allow other call to this prototype
82 | $this->n--;
83 | return $instance;
84 | } elseif (is_array($definition)) {
85 | return $this->arrayDefinition
86 | ->newInstancesOnly()
87 | ->resolve($definition, $container, $args);
88 | }
89 |
90 | throw new InvalidDefinitionException($this->name);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Di/Binding/Singleton.php:
--------------------------------------------------------------------------------
1 | instantiator = new ClassInstantiator();
56 | $this->arrayDefinition = new ArrayDefinition();
57 | $this->classResolver = new ClassResolver();
58 | parent::__construct($name, self::SINGLETON, $definition);
59 | }
60 |
61 | /**
62 | * @param Container $container
63 | * @param array $args
64 | * @param null $explicit
65 | * @return mixed|object|null
66 | * @throws InfiniteLoopResolutionException
67 | * @throws InvalidDefinitionException
68 | * @throws \Peak\Di\Exception\AmbiguousResolutionException
69 | * @throws \Peak\Di\Exception\ClassDefinitionNotFoundException
70 | * @throws \Peak\Di\Exception\InterfaceNotFoundException
71 | * @throws \ReflectionException
72 | */
73 | public function resolve(Container $container, array $args = [], $explicit = null)
74 | {
75 | $definition = $this->definition;
76 |
77 | if (null !== $this->storedInstance) {
78 | $definition = $this->storedInstance;
79 | }
80 |
81 | $is_explicit = false;
82 | if (!is_null($explicit) && !empty($explicit)) {
83 | $definition = $explicit;
84 | $is_explicit = true;
85 | }
86 |
87 | if (!$is_explicit && $container->has($this->name)) {
88 | return $container->get($this->name);
89 | }
90 |
91 | if (is_callable($definition)) {
92 | $instance = $definition($container, $args);
93 | } elseif (is_object($definition)) {
94 | $instance = $definition;
95 | } elseif(is_string($definition)) {
96 | // a string should be resolved once only, if more than one, it mean that the class behind the
97 | // string have also dependency on itself and may go on infinite loop
98 | $this->n++;
99 | if ($this->n > 1) {
100 | throw new InfiniteLoopResolutionException($definition);
101 | }
102 | $args = $this->classResolver->resolve($definition, $container, $args, $explicit);
103 | $instance = $this->instantiator->instantiate($definition, $args);
104 | } elseif (is_array($definition)) {
105 | $instance = $this->arrayDefinition->resolve($definition, $container, $args);
106 | }
107 |
108 | if (isset($instance)) {
109 | if (!$is_explicit) {
110 | $this->storedInstance = $instance;
111 | $container->set($instance);
112 | }
113 | return $instance;
114 | }
115 |
116 | throw new InvalidDefinitionException($this->name);
117 | }
118 |
119 | /**
120 | * Removed stored instance
121 | */
122 | public function deleteStoredInstance()
123 | {
124 | $this->storedInstance = null;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Di/BindingResolver.php:
--------------------------------------------------------------------------------
1 | resolve($container, $args, $explicit);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Di/ClassInspector.php:
--------------------------------------------------------------------------------
1 | hasMethod($method)) {
36 | if ($method === '__construct') {
37 | return $dependencies;
38 | }
39 | throw new MethodNotFoundException($r->getName(), $method);
40 | }
41 |
42 | $rp = $r->getMethod($method)->getParameters();
43 |
44 | foreach ($rp as $p) {
45 | $prop = $p->name;
46 |
47 | $dependencies[$prop] = [];
48 | $dependencies[$prop]['optional'] = $p->isOptional();
49 |
50 | try {
51 | $class = $p->getClass();
52 | if (isset($class)) {
53 | $dependencies[$prop]['class'] = $class->name;
54 | }
55 | } catch (\Exception $e) {
56 | $dependencies[$prop]['error'] = $e->getMessage();
57 | }
58 | }
59 |
60 | return $dependencies;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Di/ClassInstantiator.php:
--------------------------------------------------------------------------------
1 | newInstanceArgs($args);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Di/ClassResolver.php:
--------------------------------------------------------------------------------
1 | inspector = new ClassInspector();
44 | $this->explicit = new ExplicitResolver();
45 | $this->iresolver = new InterfaceResolver();
46 | }
47 |
48 | /**
49 | * Resolve class arguments dependencies
50 | *
51 | * @param mixed $class
52 | * @param Container $container
53 | * @param array $args
54 | * @param null $explicit
55 | * @return array
56 | * @throws AmbiguousResolutionException
57 | * @throws ClassDefinitionNotFoundException
58 | * @throws InterfaceNotFoundException
59 | * @throws \ReflectionException
60 | */
61 | public function resolve($class, Container $container, array $args = [], $explicit = null)
62 | {
63 | $method = '__construct';
64 |
65 | if (is_array($class)) {
66 | if (count($class) != 2) {
67 | throw new InvalidArgumentException('Expecting a valid callback definition');
68 | }
69 | // treat $class as a callback
70 | $method = $class[1];
71 | $class = $class[0];
72 | }
73 |
74 | $dependencies = $this->inspector->inspect($class, $method);
75 | $class_args = [];
76 | $class_count = 0;
77 | $i = 0;
78 |
79 | foreach ($dependencies as $d) {
80 | if (isset($d['error'])) {
81 | throw new Exception($d['error']);
82 | }
83 |
84 | // its a class or an interface
85 | if (isset($d['class'])) {
86 | $name = $d['class'];
87 | ++$class_count;
88 |
89 | // look for object in explicit dependency declaration
90 | $result = $this->explicit->resolve($name, $container, $explicit);
91 | if ($result !== null) {
92 | $class_args[] = $result;
93 | } elseif ($container->has($name)) {
94 | // check if container has a stored instance
95 | $class_args[] = $container->get($name);
96 | } elseif (!$d['optional'] && interface_exists($name)) {
97 | // otherwise check if we are dealing with an interface dependency
98 | $class_args[] = $this->iresolver->resolve($name, $container, $explicit);
99 | } elseif (!$d['optional']) {
100 | // or resolve dependency by trying to instantiate object class name string
101 | $child_args = [];
102 | if (array_key_exists($name, $args)) {
103 | $child_args = $args[$name];
104 | }
105 | $class_args[] = $container->create($name, $child_args, $explicit);
106 | }
107 | } elseif (array_key_exists($i - ($class_count), $args)) {
108 | // everything else that is not a type of class or interface
109 | $class_args[] = $args[$i - $class_count];
110 | }
111 |
112 | ++$i;
113 | }
114 |
115 | return $class_args;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Di/Exception/AmbiguousResolutionException.php:
--------------------------------------------------------------------------------
1 | bindingResolver = new BindingResolver();
27 | }
28 |
29 | /**
30 | * @param string $interface
31 | * @param Container $container
32 | * @param array $explicit
33 | * @return mixed|object
34 | * @throws AmbiguousResolutionException
35 | * @throws Exception\ClassDefinitionNotFoundException
36 | * @throws InterfaceNotFoundException
37 | * @throws \ReflectionException
38 | */
39 | public function resolve(string $interface, Container $container, $explicit = [])
40 | {
41 | // try to find a match in the container for a class or an interface
42 | if ($container->hasInterface($interface)) {
43 | $instance = $container->getInterface($interface);
44 | if (is_array($instance)) {
45 | if (empty($explicit) || !array_key_exists($interface, $explicit)) {
46 | throw new AmbiguousResolutionException($interface, $instance);
47 | }
48 | return $container->get($explicit[$interface]);
49 | }
50 | return $container->get($instance);
51 | }
52 |
53 | // try to find a match in container definition
54 | if ($container->hasDefinition($interface)) {
55 | $definition = $container->getDefinition($interface);
56 | return $this->bindingResolver->resolve($definition, $container, [], $explicit);
57 | }
58 |
59 | throw new InterfaceNotFoundException($interface);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Di/README.md:
--------------------------------------------------------------------------------
1 | #
Peak/Di
2 |
3 | ##### PSR-11 Dependency Injection Container
4 |
5 | This package allows you to standardize and centralize the way objects are constructed in your application.
6 |
7 | ### Installation
8 |
9 | ```
10 | $ composer require peak/di
11 | ```
12 |
13 | ### Documentation
14 |
15 | See https://peakphp.github.io/docs/di
--------------------------------------------------------------------------------
/src/Di/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/di",
3 | "description": "Peak Dependency Injection Container Component",
4 | "keywords": ["peak", "di", "dependency injection", "container", "psr-11"],
5 | "license": "MIT",
6 | "homepage": "https://peakframework.com",
7 | "support": {
8 | "issues": "https://github.com/peakphp/framework/issues",
9 | "source": "https://github.com/peakphp/framework"
10 | },
11 | "authors": [
12 | {
13 | "name": "Francois Lajoie",
14 | "email": "francois@peakframework.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.2",
19 | "psr/container": "^1.0"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "Peak\\Di\\": ""
24 | }
25 | },
26 | "extra": {
27 | "branch-alias": {
28 | "dev-master": "4.0-dev"
29 | }
30 | },
31 | "minimum-stability": "dev",
32 | "prefer-stable": true
33 | }
--------------------------------------------------------------------------------
/src/Http/Exception/BodyParserException.php:
--------------------------------------------------------------------------------
1 | stack = $stack;
25 | }
26 |
27 | /**
28 | * @return Stack
29 | */
30 | public function getStack()
31 | {
32 | return $this->stack;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Exception/JsonBodyParserException.php:
--------------------------------------------------------------------------------
1 | stack = $stack;
25 | }
26 |
27 | /**
28 | * @return Stack
29 | */
30 | public function getStack()
31 | {
32 | return $this->stack;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Middleware/CallableMiddleware.php:
--------------------------------------------------------------------------------
1 | callable = $callable;
29 | }
30 |
31 | /**
32 | * Process an incoming server request and return a response, optionally delegating
33 | * response creation to a handler.
34 | *
35 | * @param ServerRequestInterface $request
36 | * @param RequestHandlerInterface $handler
37 | * @return ResponseInterface
38 | */
39 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
40 | {
41 | $fn = $this->callable;
42 | if (!$this->callable instanceof Closure) {
43 | $return = call_user_func_array($fn, [$request, $handler]);
44 | if (null === $return) {
45 | return $handler->handle($request);
46 | }
47 | return $return;
48 | }
49 | return $fn($request, $handler);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Http/Middleware/JsonBodyParserMiddleware.php:
--------------------------------------------------------------------------------
1 | hasHeader('content-type') &&
24 | $request->getHeaderLine('content-type') === 'application/json') {
25 |
26 | $json = json_decode((string)$request->getBody(), true);
27 | if (json_last_error() !== 0) {
28 | throw new JsonBodyParserException(lcfirst(json_last_error_msg()));
29 | }
30 | $request = $request->withParsedBody($json);
31 | }
32 | // call the next
33 | return $handler->handle($request);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Http/README.md:
--------------------------------------------------------------------------------
1 | # Peak/Http
2 |
3 | Http foundation component compatible with PSR-7, PSR-11 and PSR-15.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ composer require peak/http
9 | ```
10 |
11 | ### Documentation
12 |
13 | See https://peakphp.github.io/docs/http
--------------------------------------------------------------------------------
/src/Http/Request/BlankRequest.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
22 | }
23 |
24 | /**
25 | * @return mixed
26 | */
27 | public function getHandler()
28 | {
29 | return $this->handler;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Request/Exception/InvalidHandlerException.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
39 | }
40 |
41 | /**
42 | * @return mixed
43 | */
44 | public function getHandler()
45 | {
46 | return $this->handler;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Http/Request/Exception/UnresolvableHandlerException.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
22 | }
23 |
24 | /**
25 | * @return mixed
26 | */
27 | public function getHandler()
28 | {
29 | return $this->handler;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Request/HandlerResolver.php:
--------------------------------------------------------------------------------
1 | container = $container;
34 | }
35 |
36 | /**
37 | * @param mixed $handler
38 | * @return mixed|CallableMiddleware
39 | */
40 | public function resolve($handler)
41 | {
42 | if ($handler instanceof Stack ||
43 | $handler instanceof MiddlewareInterface ||
44 | $handler instanceof RequestHandlerInterface) {
45 | return $handler;
46 | }
47 |
48 | if (is_callable($handler)) {
49 | return $this->resolveCallable($handler);
50 | }
51 |
52 | if (is_string($handler)) {
53 | $handler = $this->resolveString($handler);
54 | if (is_callable($handler) && !$handler instanceof MiddlewareInterface) {
55 | return $this->resolveCallable($handler);
56 | }
57 | return $handler;
58 | }
59 |
60 | throw new UnresolvableHandlerException($handler);
61 | }
62 |
63 | /**
64 | * @param callable $handler
65 | * @return CallableMiddleware
66 | */
67 | protected function resolveCallable(callable $handler)
68 | {
69 | return new CallableMiddleware($handler);
70 | }
71 |
72 | /**
73 | * @param string $handler
74 | * @return mixed
75 | */
76 | protected function resolveString(string $handler)
77 | {
78 | if (!class_exists($handler)) {
79 | throw new HandlerNotFoundException($handler);
80 | }
81 |
82 | // resolve using a container
83 | if (null !== $this->container) {
84 | return $this->container->get($handler);
85 | }
86 |
87 | // manual instantiation, work only with empty constructor classes
88 | return new $handler();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Http/Request/PreRoute.php:
--------------------------------------------------------------------------------
1 | getMethod() && $this->getMethod() !== $request->getMethod()) {
29 | return false;
30 | }
31 |
32 | // compile pseudo route syntax {param} and {param}:type into valid regex
33 | $routeRegex = (new RouteExpression($this->getPath()))->getRegex();
34 |
35 | // look to match the route
36 | $this->pregMatch('#^'.$routeRegex.'#', $request->getUri()->getPath());
37 |
38 | return !empty($this->getMatches());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Http/Request/Route.php:
--------------------------------------------------------------------------------
1 | method = isset($method) ? strtoupper(trim($method)) : null;
53 | $this->path = $path;
54 | $this->stack = $stack;
55 | }
56 |
57 | /**
58 | * @param Stack $parentStack
59 | */
60 | public function setParent(Stack $parentStack)
61 | {
62 | $this->parentStack = $parentStack;
63 | $this->stack->setParent($parentStack);
64 | }
65 |
66 | /**
67 | * @return Stack
68 | */
69 | public function getParent(): Stack
70 | {
71 | return $this->parentStack;
72 | }
73 |
74 | /**
75 | * @param ServerRequestInterface $request
76 | * @return bool
77 | */
78 | public function match(ServerRequestInterface $request): bool
79 | {
80 | if (null !== $this->method && $this->method !== $request->getMethod()) {
81 | return false;
82 | }
83 |
84 | // compile pseudo route syntax {param} and {param}:type into valid regex
85 | $routeRegex = (new RouteExpression($this->path))->getRegex();
86 |
87 | // look to match the route
88 | $this->pregMatch('#^'.$routeRegex.'$#', $request->getUri()->getPath());
89 | return !empty($this->matches);
90 | }
91 |
92 | /**
93 | * Process an incoming server request and return a response, optionally delegating
94 | * response creation to a handler.
95 | *
96 | * @param ServerRequestInterface $request
97 | * @param RequestHandlerInterface $handler
98 | * @return ResponseInterface
99 | */
100 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
101 | {
102 | $isMatching = $this->match($request);
103 |
104 | // add regex matches(aka route arguments) to the request.
105 | $routeArgs = new RouteArgs($this->matches);
106 | foreach ($routeArgs->toArray() as $name => $value) {
107 | $request = $request->withAttribute($name, $value);
108 | }
109 | // $request->args works but "pollute" the request object and will be removed in the next major version.
110 | // use the PSR-7 method $request->getAttribute() instead for retrieving an route argument
111 | $request->args = $routeArgs;
112 |
113 | if (!$isMatching) {
114 | return $this->processParent($request, $handler);
115 | }
116 | return $this->stack->handle($request);
117 | }
118 |
119 | /**
120 | * Handle the request and return a response.
121 | *
122 | * @param ServerRequestInterface $request
123 | * @return ResponseInterface
124 | */
125 | public function handle(ServerRequestInterface $request): ResponseInterface
126 | {
127 | return $this->process($request, $this);
128 | }
129 |
130 | /**
131 | * @param ServerRequestInterface $request
132 | * @param RequestHandlerInterface $handler
133 | * @return ResponseInterface
134 | */
135 | public function processParent(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
136 | {
137 | if (!isset($this->parentStack)) {
138 | throw new StackEndedWithoutResponseException($this);
139 | }
140 | return $this->parentStack->process($request, $handler);
141 | }
142 |
143 | /**
144 | * @return string|null
145 | */
146 | public function getMethod(): ?string
147 | {
148 | return $this->method;
149 | }
150 |
151 | /**
152 | * @return string
153 | */
154 | public function getPath(): string
155 | {
156 | return $this->path;
157 | }
158 |
159 | /**
160 | * @return array
161 | */
162 | public function getHandlers(): array
163 | {
164 | return $this->stack->getHandlers();
165 | }
166 |
167 | /**
168 | * @return array
169 | */
170 | protected function getMatches(): array
171 | {
172 | return $this->matches;
173 | }
174 |
175 | /**
176 | * @param string $pattern
177 | * @param string $path
178 | * @return array
179 | */
180 | protected function pregMatch(string $pattern, string $path): array
181 | {
182 | preg_match($pattern, $path, $this->matches);
183 | return $this->matches;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/Http/Request/RouteArgs.php:
--------------------------------------------------------------------------------
1 | args = $args;
23 | }
24 |
25 | /**
26 | * @deprecated
27 | * @return array
28 | */
29 | public function raw()
30 | {
31 | return $this->args;
32 | }
33 |
34 | /**
35 | * @return array
36 | */
37 | public function toArray()
38 | {
39 | return $this->args;
40 | }
41 |
42 | /**
43 | * @param string $name
44 | * @return mixed
45 | */
46 | public function __get(string $name)
47 | {
48 | return $this->args[$name];
49 | }
50 |
51 | /**
52 | * @param string $name
53 | * @return bool
54 | */
55 | public function __isset(string $name)
56 | {
57 | return isset($this->args[$name]);
58 | }
59 |
60 | /**
61 | * Offset to retrieve
62 | *
63 | * @param string $offset
64 | * @return mixed
65 | */
66 | public function offsetGet($offset)
67 | {
68 | return isset($this->args[$offset]) ? $this->args[$offset] : null;
69 | }
70 |
71 | /**
72 | * @param string $offset
73 | * @return bool
74 | */
75 | public function offsetExists($offset): bool
76 | {
77 | return isset($this->args[$offset]);
78 | }
79 |
80 | /**
81 | * @param mixed $offset
82 | * @param mixed $value
83 | */
84 | public function offsetSet($offset, $value)
85 | {
86 | $this->args[$offset] = $value;
87 | }
88 |
89 | /**
90 | * @param mixed $offset
91 | */
92 | public function offsetUnset($offset)
93 | {
94 | unset($this->args[$offset]);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Http/Request/RouteExpression.php:
--------------------------------------------------------------------------------
1 | '[^\/]+',
31 | ':negnum' => '-[0-9]+',
32 | ':posnum' => '[0-9]+',
33 | ':num' => '-?[0-9]+',
34 | // for float pattern, string like .5 is not valid, it must be 0.5
35 | ':negfloat' => '-([0-9]+\.[0-9]+|[0-9]+)',
36 | ':posfloat' => '([0-9]+\.[0-9]+|[0-9]+)',
37 | ':float' => '[-+]?([0-9]+\.[0-9]+|[0-9]+)',
38 | // chars, numbers, -, _ and + only
39 | ':permalink' => '[a-zA-Z0-9+_-]+',
40 | ':alphanum' => '[a-zA-Z0-9]+',
41 | ':alpha' => '[a-zA-Z]+',
42 | ':year' => '[12][0-9]{3}', // 1000 to 2999
43 | ':month' => '0[1-9]|1[012]|[1-9]', // valid ex: 07, 7, 12, 31
44 | ':day' => '[12][0-9]|3[01]|0?[1-9]', // valid ex: 2, 12, 02, 15, 31
45 | ];
46 |
47 | /**
48 | * RouteExpression constructor.
49 | * @param string $expression
50 | */
51 | public function __construct(string $expression)
52 | {
53 | $this->expression = $expression;
54 | $this->regex = $expression;
55 | $this->compile();
56 | }
57 |
58 | /**
59 | * @return string
60 | */
61 | public function getRegex(): string
62 | {
63 | return $this->regex;
64 | }
65 |
66 | /**
67 | * @return string
68 | */
69 | public function getExpression(): string
70 | {
71 | return $this->expression;
72 | }
73 |
74 | /**
75 | * Compile expression to valid regex
76 | */
77 | private function compile()
78 | {
79 | // replace pseudo {param}:type syntax to valid regex
80 | if (strpos($this->regex, '}:') !== false) {
81 | $this->regex = preg_replace('#\{([a-zA-Z0-9_-]+)\}:([a-z]+)#', '(?P<$1>:$2)', $this->expression);
82 | $this->regex = str_replace(
83 | array_keys($this->regexExpression),
84 | array_values($this->regexExpression),
85 | $this->regex
86 | );
87 | }
88 |
89 | // replace pseudo {param} syntax without type to valid regex
90 | if (strpos($this->regex, '}') !== false) {
91 | $this->regex = preg_replace('#\{([a-zA-Z_]+)\}#', '(?P<$1>[^\/]+)', $this->regex);
92 | }
93 |
94 | // allow optional trailing slash at the end
95 | if (substr($this->regex, -1) !== '/') {
96 | $this->regex .= '[\/]?';
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/src/Http/Request/RouteServerRequest.php:
--------------------------------------------------------------------------------
1 | request = $request;
32 | if (isset($request->args)) {
33 | $this->routeArgs = $request->args;
34 | }
35 | }
36 |
37 | /**
38 | * @param string $name
39 | * @param string|null $type
40 | * @return bool
41 | */
42 | public function hasArg(string $name, string $type = null): bool
43 | {
44 | if (!isset($this->routeArgs)) {
45 | return false;
46 | }
47 | $exists = isset($this->routeArgs->$name);
48 | if (!$exists || !isset($type)) {
49 | return $exists;
50 | }
51 |
52 | return (gettype($this->routeArgs->$name) === $type) ? true : false;
53 | }
54 |
55 | /**
56 | * @param string $name
57 | * @param mixed $default
58 | * @return mixed|null
59 | */
60 | public function getArg(string $name, $default = null)
61 | {
62 | if (!isset($this->routeArgs) || !$this->hasArg($name)) {
63 | return $default;
64 | }
65 | return $this->routeArgs->$name;
66 | }
67 |
68 | /**
69 | * ---- end of wrapper ----
70 | * The rest of methods below are only there for satisfying ServerRequestInterface
71 | */
72 |
73 | public function getProtocolVersion()
74 | {
75 | return $this->request->getProtocolVersion();
76 | }
77 |
78 | public function withProtocolVersion($version)
79 | {
80 | return new self($this->request->withProtocolVersion($version));
81 | }
82 |
83 | public function getHeaders()
84 | {
85 | return $this->request->getHeaders();
86 | }
87 |
88 | public function hasHeader($name)
89 | {
90 | return $this->request->hasHeader($name);
91 | }
92 |
93 | public function getHeader($name)
94 | {
95 | return $this->request->getHeader($name);
96 | }
97 |
98 | public function getHeaderLine($name)
99 | {
100 | return $this->request->getHeaderLine($name);
101 | }
102 |
103 | public function withHeader($name, $value)
104 | {
105 | return new self($this->request->withHeader($name, $value));
106 | }
107 |
108 | public function withAddedHeader($name, $value)
109 | {
110 | return new self($this->request->withAddedHeader($name, $value));
111 | }
112 |
113 | public function withoutHeader($name)
114 | {
115 | return new self($this->request->withoutHeader($name));
116 | }
117 |
118 | public function getBody()
119 | {
120 | return $this->request->getBody();
121 | }
122 |
123 | public function withBody(StreamInterface $body)
124 | {
125 | return new self($this->request->withBody($body));
126 | }
127 |
128 | public function getRequestTarget()
129 | {
130 | return $this->request->getRequestTarget();
131 | }
132 |
133 | public function withRequestTarget($requestTarget)
134 | {
135 | return new self($this->request->withRequestTarget($requestTarget));
136 | }
137 |
138 | public function getMethod()
139 | {
140 | return $this->request->getMethod();
141 | }
142 |
143 | public function withMethod($method)
144 | {
145 | return new self($this->request->withMethod($method));
146 | }
147 |
148 | public function getUri()
149 | {
150 | return $this->request->getUri();
151 | }
152 |
153 | public function withUri(UriInterface $uri, $preserveHost = false)
154 | {
155 | return new self($this->request->withUri($uri, $preserveHost));
156 | }
157 |
158 | public function getServerParams()
159 | {
160 | return $this->request->getServerParams();
161 | }
162 |
163 | public function getCookieParams()
164 | {
165 | return $this->request->getCookieParams();
166 | }
167 |
168 | public function withCookieParams(array $cookies)
169 | {
170 | return new self($this->request->withCookieParams($cookies));
171 | }
172 |
173 | public function getQueryParams()
174 | {
175 | return $this->request->getQueryParams();
176 | }
177 |
178 | public function withQueryParams(array $query)
179 | {
180 | return new self($this->request->withQueryParams($query));
181 | }
182 |
183 | public function getUploadedFiles()
184 | {
185 | return $this->request->getUploadedFiles();
186 | }
187 |
188 | public function withUploadedFiles(array $uploadedFiles)
189 | {
190 | return new self($this->request->withUploadedFiles($uploadedFiles));
191 | }
192 |
193 | public function getParsedBody()
194 | {
195 | return $this->request->getParsedBody();
196 | }
197 |
198 | public function withParsedBody($data)
199 | {
200 | return new self($this->request->withParsedBody($data));
201 | }
202 |
203 | public function getAttributes()
204 | {
205 | return $this->request->getAttributes();
206 | }
207 |
208 | public function getAttribute($name, $default = null)
209 | {
210 | return $this->request->getAttribute($name, $default);
211 | }
212 |
213 | public function withAttribute($name, $value)
214 | {
215 | return new self($this->request->withAttribute($name, $value));
216 | }
217 |
218 | public function withoutAttribute($name)
219 | {
220 | return new self($this->request->withoutAttribute($name));
221 | }
222 | }
--------------------------------------------------------------------------------
/src/Http/Response/Emitter.php:
--------------------------------------------------------------------------------
1 | assertNoPreviousOutput();
36 | $this->emitHeaders($response);
37 | $this->emitStatusLine($response);
38 | }
39 |
40 | $this->emitBody($response);
41 |
42 | return true;
43 | }
44 |
45 | /**
46 | * Emit the message body.
47 | *
48 | * @param ResponseInterface $response
49 | */
50 | private function emitBody(ResponseInterface $response)
51 | {
52 | echo $response->getBody();
53 | }
54 |
55 | /**
56 | * Checks to see if content has previously been sent.
57 | *
58 | * If either headers have been sent or the output buffer contains content,
59 | * raises an exception.
60 | *
61 | * @throws \RuntimeException if headers have already been sent.
62 | * @throws \RuntimeException if output is present in the output buffer.
63 | */
64 | private function assertNoPreviousOutput()
65 | {
66 | // headers_sent() will return false, even if you sent something to the ouptut using print() or header(),
67 | // if output_buffering is different from Off in you php.ini, and the length of what you sent does not
68 | // exceed the size of output_buffering.
69 | if (headers_sent()) {
70 | throw new \RuntimeException('Unable to emit response; headers already sent');
71 | }
72 |
73 | /*
74 | if (ob_get_level() > 0 && ob_get_length() > 0) {
75 | throw new \RuntimeException('Output has been emitted previously; cannot emit response');
76 | }
77 | */
78 | }
79 |
80 | /**
81 | * Emit the status line.
82 | *
83 | * Emits the status line using the protocol version and status code from
84 | * the response; if a reason phrase is available, it, too, is emitted.
85 | *
86 | * It is important to mention that this method should be called after
87 | * `emitHeaders()` in order to prevent PHP from changing the status code of
88 | * the emitted response.
89 | *
90 | * @param ResponseInterface $response
91 | *
92 | * @see \Zend\Diactoros\Response\SapiEmitterTrait::emitHeaders()
93 | */
94 | private function emitStatusLine(ResponseInterface $response)
95 | {
96 | $reasonPhrase = $response->getReasonPhrase();
97 | $statusCode = $response->getStatusCode();
98 |
99 | header(sprintf(
100 | 'HTTP/%s %d%s',
101 | $response->getProtocolVersion(),
102 | $statusCode,
103 | ($reasonPhrase ? ' ' . $reasonPhrase : '')
104 | ), true, $statusCode);
105 | }
106 |
107 | /**
108 | * Emit response headers.
109 | *
110 | * Loops through each header, emitting each; if the header value
111 | * is an array with multiple values, ensures that each is sent
112 | * in such a way as to create aggregate headers (instead of replace
113 | * the previous).
114 | *
115 | * @param ResponseInterface $response
116 | */
117 | private function emitHeaders(ResponseInterface $response)
118 | {
119 | $statusCode = $response->getStatusCode();
120 |
121 | foreach ($response->getHeaders() as $header => $values) {
122 | $name = $this->filterHeader($header);
123 | $first = $name === 'Set-Cookie' ? false : true;
124 | foreach ($values as $value) {
125 | header(sprintf(
126 | '%s: %s',
127 | $name,
128 | $value
129 | ), $first, $statusCode);
130 | $first = false;
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Filter a header name to wordcase
137 | *
138 | * @param string $header
139 | * @return string
140 | */
141 | private function filterHeader($header)
142 | {
143 | $filtered = str_replace('-', ' ', $header);
144 | $filtered = ucwords($filtered);
145 | return str_replace(' ', '-', $filtered);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Http/Stack.php:
--------------------------------------------------------------------------------
1 | handlers = $handlers;
57 | $this->handlerResolver = $handlerResolver;
58 | }
59 |
60 | /**
61 | * @param \Peak\Blueprint\Http\Stack $parentStack
62 | */
63 | public function setParent(\Peak\Blueprint\Http\Stack $parentStack)
64 | {
65 | $this->parentStack = $parentStack;
66 | }
67 |
68 | /**
69 | * @return \Peak\Blueprint\Http\Stack
70 | */
71 | public function getParent(): \Peak\Blueprint\Http\Stack
72 | {
73 | return $this->parentStack;
74 | }
75 |
76 | /**
77 | * @return array
78 | */
79 | public function getHandlers(): array
80 | {
81 | return $this->handlers;
82 | }
83 |
84 | /**
85 | * Process an incoming server request and return a response, optionally delegating
86 | * response creation to a handler.
87 | *
88 | * @param ServerRequestInterface $request
89 | * @param RequestHandlerInterface $handler
90 | * @return ResponseInterface
91 | */
92 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
93 | {
94 | $this->nextHandler = (isset($this->nextHandler)) ? next($this->handlers) : current($this->handlers);
95 |
96 | // nextHandler is a stack
97 | if ($this->nextHandler instanceof \Peak\Blueprint\Http\Stack) {
98 | return $this->returnResponse($this->handleStack($this->nextHandler, $request));
99 | }
100 |
101 | // no more handlers, look for parent
102 | if ($this->nextHandler === false && isset($this->parentStack)) {
103 | return $this->returnResponse($this->parentStack->process($request, $this->parentStack));
104 | } elseif ($this->nextHandler === false) {
105 | throw new StackEndedWithoutResponseException($this);
106 | }
107 |
108 | // resolve nextHandler if not already a object
109 | $handlerInstance = $this->nextHandler;
110 | if (is_callable($handlerInstance) || !is_object($handlerInstance)) {
111 | $handlerInstance = $this->handlerResolver->resolve($this->nextHandler);
112 | }
113 |
114 | // how to call the handler (MiddlewareInterface or RequestHandlerInterface)
115 | if ($handlerInstance instanceof MiddlewareInterface) {
116 | return $this->returnResponse($handlerInstance->process($request, $this));
117 | } elseif($handlerInstance instanceof RequestHandlerInterface) {
118 | return $this->returnResponse($handlerInstance->handle($request));
119 | }
120 |
121 | // at this point, the handler is not good
122 | throw new InvalidHandlerException($handlerInstance);
123 | }
124 |
125 | /**
126 | * Handle the request and return a response.
127 | *
128 | * @param ServerRequestInterface $request
129 | * @return ResponseInterface
130 | */
131 | public function handle(ServerRequestInterface $request): ResponseInterface
132 | {
133 | return $this->process($request, $this);
134 | }
135 |
136 | /**
137 | * Handle a child stack
138 | *
139 | * @param \Peak\Blueprint\Http\Stack $stack
140 | * @param ServerRequestInterface $request
141 | * @return ResponseInterface
142 | */
143 | protected function handleStack(\Peak\Blueprint\Http\Stack $stack, ServerRequestInterface $request): ResponseInterface
144 | {
145 | $stack->setParent($this);
146 | return $stack->handle($request);
147 | }
148 |
149 | /**
150 | * Reset the stack before returning the response,
151 | * This allow the stack to be re-handle without throwing exception
152 | * @param ResponseInterface $response
153 | * @return ResponseInterface
154 | */
155 | protected function returnResponse(ResponseInterface $response): ResponseInterface
156 | {
157 | $this->nextHandler = null;
158 | reset($this->handlers);
159 | return $response;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Http/StackFactory.php:
--------------------------------------------------------------------------------
1 | handlerResolver = $handlerResolver;
24 | }
25 |
26 | /**
27 | * @param array $handlers
28 | * @param ResourceResolver|null $handlerResolver
29 | * @return Stack
30 | */
31 | public function create(array $handlers, ResourceResolver $handlerResolver = null)
32 | {
33 | $handlerResolver = $handlerResolver ?? $this->handlerResolver;
34 | $stack = new Stack($handlers, $handlerResolver);
35 | return $stack;
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Http/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peak/http",
3 | "description": "Peak Http Component",
4 | "type": "library",
5 | "keywords": ["peak", "http", "psr-7", "psr-15"],
6 | "license": "MIT",
7 | "homepage": "https://peakframework.com",
8 | "support": {
9 | "issues": "https://github.com/peakphp/framework/issues",
10 | "source": "https://github.com/peakphp/framework"
11 | },
12 | "authors": [
13 | {
14 | "name": "Francois Lajoie",
15 | "email": "francois@peakframework.com"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=7.2",
20 | "psr/http-message": "^1.0",
21 | "psr/http-server-handler": "^1.0",
22 | "psr/http-server-middleware": "^1.0",
23 | "peak/blueprint": "^4.0"
24 | },
25 | "autoload": {
26 | "psr-4": {
27 | "Peak\\Http\\": ""
28 | },
29 | "exclude-from-classmap": [
30 | "/Tests/"
31 | ]
32 | },
33 | "minimum-stability": "dev",
34 | "prefer-stable": true,
35 | "extra": {
36 | "branch-alias": {
37 | "dev-master": "4.0-dev"
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/supporters.txt:
--------------------------------------------------------------------------------
1 | Valérie Bastille
2 | Dany Bouchard
--------------------------------------------------------------------------------