Verified Commit 21880557 authored by Dominik Schrom's avatar Dominik Schrom
Browse files

Initial commit

parents
# Changelog
## Version 1.0.0
> Initial release
### Features:
* PSR-11 Container
* PSR-4 Autoload
* PSR-3 Logger
* Simple routing (API supported)
* PSR-1 & PSR-12 Coding Styles
* PHP 8 ready, min. PHP 7.3
* SOLID principles
* Skeleton directory structure
* NON-static app
* ANTI-singleton app
###### tags: `Modur Framework v1.0.0`
# Contributing
## Issue tracker
* Did you encounter an error? Do you have your own idea to improve the Modur Framework? You can contribute any suggestions and help in the development of this project!
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
MIT License
Copyright (c) 2020-2021 The Modur Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
This diff is collapsed.
# Modur Framework
See [CHANGELOG](https://gitlab.com/modur/core-framework/-/blob/main/CHANGELOG.md) for changes.
## About
Modur Framework is the core PHP framework of Modur App (in development). This framework is focused mainly on web applications development. However you can use it on your own.
## Documentation
You can find our documentation [here](https://gitlab.com/modur/core-framework/-/blob/main/docs/OctopusDoc/INDEX.md).
## Contributing
Are you interested in contribute to our framework? Visit [this](https://gitlab.com/modur/core-framework/-/blob/main/CONTRIBUTING.md) page.
## License
The Modur Framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
## About us
If you want to know something about our team, please visit our websites [modur.one](https://modur.one).
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
<?php
declare(strict_types=1);
header("Content-Type: text/plain");
define("DIR_ROOT", realpath(__DIR__ . "/.."));
define("DIR_BIN", DIR_ROOT . "/bin/");
define("DIR_CONF", DIR_ROOT . "/config/");
define("DIR_DOCS", DIR_ROOT . "/docs/");
define("DIR_LANG", DIR_ROOT . "/locales/");
define("DIR_LOG", DIR_ROOT . "/logs/");
define("DIR_PUBLIC", DIR_ROOT . "/public/");
define("DIR_RES", DIR_ROOT . "/resources/");
define("DIR_ROUTE", DIR_ROOT . "/routes/");
define("DIR_SRC", DIR_ROOT . "/src/");
define("DIR_TEST", DIR_ROOT . "/tests/");
define("DIR_VENDOR", DIR_ROOT . "/vendor/");
\ No newline at end of file
<?php
const AUTOLOAD_ALIASES = [
"OctopusCore" => "src/OctopusCore"
];
/**
* @param $class_name
*/
function autoload($class_name): void
{
$class_array = explode("\\", $class_name);
$array_count = count($class_array);
if ($array_count < 2) {
die("Bad namespace declaration");
}
foreach ($class_array as $value) {
if (!preg_match('~^\p{Lu}~u', $value)) {
die("Bad namespace declaration");
}
}
if (!array_key_exists($class_array[0], AUTOLOAD_ALIASES)) {
array_unshift($class_array, "vendor");
} else {
$class_array[0] = AUTOLOAD_ALIASES[$class_array[0]];
}
$class_file = implode('/', $class_array) . '.php';
$class_path = realpath(__DIR__ . '/..') . '/' . $class_file;
require_once $class_path;
}
spl_autoload_register('autoload');
\ No newline at end of file
This diff is collapsed.
# Application
## ImportTrait
* We prepared 'import' trait with functionality to get components from the [container](CONTAINER.md). Choose components which are important to you. The advantage of this is that you can just use this `ImportTrait` instead of using multiple traits and declaring multiple methods to achieved components from container.
* Working in `/src/OctopusCore/App/ImportTrait.php`
```
<?php
namespace OctopusCore\App;
// Use required namespaces
use OctopusCore\Container\ContainerAwareTrait;
use OctopusCore\Log\ImportLoggerTrait;
trait ImportTrait
{
use ContainerAwareTrait; // import container trait
use ImportLoggerTrait; // import logger trait
private Test $test;
/**
* Declare this method only once.
*/
public function test()
{
if (empty($this->test)) {
$this->test = $this->container->get('test');
}
return $this->test;
}
}
```
* Working in `/src/MyApp/Test5.php`
```
<?php
namespace MyApp;
// Use only ImportTrait as required namespace
use OctopusCore\App\ImportTrait;
class Test5
{
use ImportTrait; // import only this trait
public function something()
{
// We can call anything from ImportTrait easily
$this->test();
$this->logger->log(LogLevel::INFO, 'SYSTEM-5');
$router = $this->container->get('router');
}
}
```
## SpaceShip
* The main purpose of `SpaceShip::class` is to create separate space for configurations files. Thanks this solution you can set just instances which you want to use in the configuration file.
* Just create instance of `SpaceShip::class` and call method `Spaceship::fly()`
* Parameters of `SpaceShip::fly()`:
* `string $filePath` - path to configuration file (**REQUIRED**)
* `array $objects = array()` - array of objects defined as asociative (name of the key will be used as name of variable in configuration file and value of the key must be instance of some class)
* `string $return = null` - you can choose if you need to return result of object from array `$objects` or just `true`.
* Format: `'object@method'`
* For example `$return = 'test@doSomething'` => call object from `$objects` array where name of the key is equal to part before `@` and it's method which is equal to second part after `@` (it means that call is: `$test->doSomething`). Result of the call will be used as final parameter of `$return`.
* Example: working in `/src/MyApp/Test8.php`
```
<?php
namespace MyApp;
use OctopusCore\App\ImportTrait;
class Test8
{
use ImportTrait;
public function setup()
{
$file = DIR_CONF . 'test4.php';
$test = $this->container->get('test6');
$result = (new SpaceShip)->fly($file, [
'test6' => $test
], 'test6@getResult');
// In DIR_CONF . 'test4.php'
// will be available variable $test6
// which will be instance
// of container component with name 'test'
// $test6->getResult() will be returned
// to variable $result
}
}
```
## Fallback
* Class Fallback takes care of taking over HTTP error codes. When you create new instance of `Fallback::class` it will automatically load file from `/resources/fallback` directory with same name as the code used in first parameter with extension `.php` (if it is available).
* For example: If `/resources/fallback/403.php` is exists then will be loaded with call `new Fallback(403)`.
* The second parameter of the constructor is array `$inject_objects` which works same as `SpaceShip::class`. It will create own instance of `SpaceShip::class` and pass on this two parameters to method `SpaceShip::fly()`.
## Functions & constants
### Functions
* At this time there is no functions available. Will be added soon.
### Constants
#### Directories
| NAME | PARENT | PATH |
| ---------- | -------- | ------------ |
| DIR_ROOT | NONE | Project root |
| DIR_BIN | DIR_ROOT | /bin/ |
| DIR_CONF | DIR_ROOT | /config/ |
| DIR_DOCS | DIR_ROOT | /docs/ |
| DIR_LANG | DIR_ROOT | /locales/ |
| DIR_LOG | DIR_ROOT | /logs/ |
| DIR_PUBLIC | DIR_ROOT | /public/ |
| DIR_RES | DIR_ROOT | /resources/ |
| DIR_ROUTE | DIR_ROOT | /routes/ |
| DIR_SRC | DIR_ROOT | /src/ |
| DIR_TEST | DIR_ROOT | /tests/ |
| DIR_VENDOR | DIR_ROOT | /vendor/ |
#### Optional
| NAME | TYPE | DEFAULT | DESCRIPTION |
| ----------- | ---- | ------- | ----------- |
| DISABLE_API | bool | false | Disable/Enable API |
| ENABLE_DEBUG | bool | false | Disable/Enable Debug |
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
This diff is collapsed.
# Container
## How it works
* Container package contains 3 components:
* **Container storage:**
* Store instances & definitions of classes.
* **Container builder:**
* Defines user components to container in `/config/init.php` configuration file. These components are used in project as packages.
* **Main container:**
* Takes care of component's instances
* Loading configuration file of the component (see component configuration below)
* Passes the container to the component
## Adding component
* Components are added in `/config/init.php` configuration file
* We can use the template below where we are creating an array `$component_bundle` and `foreach` loop which register all components from the array to the container.
```
<?php
use UserPackage\Test\Example0;
use UserPackage\Test\Example1 as Example;
// ...
$component_bundle = [
'example-0' => Example0::class,
'example' => Example::class
];
foreach ($component_bundle as $id => $class) {
try {
$app->container()->register($class, $id);
} catch (ContainerException $e) {
echo $e->getMessage();
}
}
```
## Loading component
* Container automatically passes self to classes with `ContainerAwareInterface` implemented.
* We also prepared a `ContainerAwareTrait` with common methods to accessing the container. So You don't need to implement it yourself again, just use this trait.
* Finally the container is accesible from `$this->container` property.
* Getting a component from a container is possible using the `get(id)` method.
```
<?php
namespace UserPackage\Test;
// You need to implement this interface
// to be able to access the container
class Example0 implements ContainerAwareInterface
{
// This trait prepares common methods
// to accessing the container
use ContainerAwareTrait;
public function test()
{
// Getting database instance
// from container to variable
$db = $this->container->get('database');
}
}
```
## Component configuration
* For all components in container is possible to create a config file in `/config` directory with same name as the component and `.php` extension.
**Example:**
Working with `/config/init.php`
```
// ...
$component_bundle = [
'database123' => Database::class
];
// ...
```
* If we register component named `'database123'` of `Database::class` to the container (as you can see above) then container will automatically load configuration file from `/config/database123.php` if already exists.
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
This diff is collapsed.
# Getting started
## Requirements
* UNIX like OS with installed:
* Apache 2.4 or newer with:
* `mod_ssl` - enabled
* `mod_rewrite` - enabled
* PHP 7.4 or newer with:
* JSON - enabled
* Basic coding skills
## Installation
* Download & extract [ZIP](https://gitlab.com/modur/core-framework/-/archive/main/core-framework-main.zip) to your webroot.
**OR**
* Clone GIT repository from [here](https://gitlab.com/modur/core-framework.git).
## Request lifecycle
1. Web client send request to server
2. Inside server - creating instance of App class, container with components, router & logging.
3. Loading configurations and other dependencies
4. Start of the App (routing, hadling of request by the component)
5. The component processes the request and return result
6. Web client receive result and process it for end-user
![](https://i.imgur.com/YBLJ7Kh.png)
## Directory structure
* Our framework follows skeleton standard filesystem. See documentation [here](https://github.com/php-pds/skeleton).
* The `/public` & `/docs` folders are the only publicly accessible and visible folders from the URL.
* `/public` folder is used for assets of the website (CSS, images, etc...)
* `/docs` folder is used for documentation files of your source codes.
* All other folders are handled by framework
## Configuration
* All the configurations files are located in `/config` directory.
* At first the framework load `init.php` configuration file. So, we need to create & configure it. We can use template below:
```
<?php
// Important application namespace
use OctopusCore\App\App;
// Include autoload configuration file
require_once "autoload.php";
// Create new application instance
$app = new App();
```
## Create your first app (example: IP Manager)
### 1. We have to start with [installation](#Installation).
### 2. Another main step is to do [configuration](#Configuration).
### 3. Create new component
* At first we will create a class `IpManager` in `/src/Service` directory, name of the file will be `IpManager.php` and we need to define `namespace Service`.
```
<?php
namespace Service;
class IpManager
{
}
```
* This namespace is unknown for our autoloader, so it will automatically search in `/vendor/Service`. So, it is mandatory to edit configuration file of autoloder which is located in `/config/autoload.php`.
* In opened configuration file we can find constant named `AUTOLOAD_ALIASES`. It is an array which is used for define all paths to the namespaces. We will add new item `Service` referring to `src/Service` directory. You can place this namespace somewhere else, it is your choice, however for all components is usage of `/src` folder.
```
<?php
const AUTOLOAD_ALIASES = [
"OctopusCore" => "src/OctopusCore",
"Service" => "src/Service"
];
// ...
```
### 4. Add component to [container](./CONTAINER.md#Adding-component) (init.php)
* To comfortably work with container, we have to do 2 steps:
1. #### Implement `ContainerAwareInterrface` to our created `IpManager` component. For the right way of usage of this interface, we will use [`ImportTrait`](./APPLICATION.md#ImportTrait).
```
<?php
namespace Service;
use OctopusCore\Container\ContainerAwareInterface;
use OctopusCore\App\ImportTrait;
class IpManager implements ContainerAwareInterface
{
use ImportTrait;
}
```
2. #### Register component in configuration file `/config/init.php`
```
<?php
use Service\IpManager;
// ...
$app->container()->register(
IpManager::class,
'ip-manager'
);
```
* From this moment our component is accessible from container under name `'ip-manager'`. It will automatically load configuration file `/config/ip-manager.php` if exists. We will create this configuration file with the component configuration. For example we can create constant `ALLOW_IP` (array of IP addresses where we can set which one can access to our server).
```
<?php
const ALLOW_IP = [
'192.168.1.151'
];
```
### 5. [Logging](./LOGGING.md) of allowed/denied IPs
* To use of logging IP addresses, we have to create new method `IpManager::manage()` in our component `IpManager` which will control accessing to server.
```
// ..
use ImportTrait;
public function manage()
{
$client_ip = $_SERVER['REMOTE_ADDR'];
if (!in_array($client_ip, ALLOW_IP)) {
// do actions if not allowed
die('Forbidden');
} else {
// do actions if allowed
}
}
//..
```
* At first we have to create a new [logger](./LOGGING.md#Managing-loggers). Next step is to create JSON configuration file for error messages (directory `/resources/log`). As stated in the logging part of documentation, we will create file `codes.firewall.json`. Content of this file will be:
```
{
"403": "Access denied for IP {ip}",
"202": "Access allowed for IP {ip}"
}
```
* And now we can just call method for logging in our component. Logging is now ready!
```
// ..
// Important use statement to use log levels
use Psr\Log\LogLevel;
// ..
if (!in_array($client_ip, ALLOW_IP)) {
// do actions if not allowed
$this->logger()->log(
LogLevel::NOTICE,
'FIREWALL_403',
['ip' => $client_ip]
);
die('Forbidden');
} else {
// do actions if allowed
$this->logger()->log(
LogLevel::INFO,
'FIREWALL_202',
['ip' => $client_ip]
);
}
//..
```
### 6. IP addresses filtering
* We are finishing! Now is mandatory to start ourself created method `IpManager::manage()`. We can do it by two ways:
**Use routing**:
* Routes are configured in `routes/web.php` file. We will add a new route `'test'` and call our component `IpManager` and it´s method `IpManager::manage()` to control IP address.
```
<?php
/** @var Routes $route */
use OctopusCore\Container\ContainerExtendedInterface;
use OctopusCore\Route\Routes;
$route->add('test', function (
ContainerExtendedInterface $container
) {
$container->get('ip-manager')->manage();
echo 'Hello World';
});
```
**Or we can control this by area**:
* We will use `config/start.php` configuration file. It is configuration which will start some of tasks before main start of app and routing.
```
<?php
/** @var Container $start */
use OctopusCore\Container\Container;
use Service\IpManager;
/** @var IpManager $ipManager */
$ipManager = $start->get('ip-manager');
$ipManager->manage();
```
### 7. We can test it now and Hurray!
* At this moment we can try to go on server, route test (http://localhost/test). If our IP address is in `ALLOW_IP` array from step [4](#4-Add-component-to-container-initphp) then we should see "Hello World!", if not we will see "forbidden". All of this accesses are logged by our configuration to file in `/logs` directory.
* **Please make sure `/logs` directory exists and is writable**
### 8. Now you can creat another one of your choice!
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
This diff is collapsed.
# Documentation index
## Why use Modur Framework
* Modur Framework is lightweight PHP framework used for web applications. Thanks to simple routing, utilization of containers and intuitive logging system you can build stable and reliable web application.
## Documentation content
### [Getting started](./GETTING_STARTED.md)
* Requirements
* Installation
* Request lifecycle
* Directory structure
* Configuration
* Create your first app
### [Routing](./ROUTING.md)
* How it works
* Routing components
* API routing
### [Container](./CONTAINER.md)
* How it works
* Adding component
* Loading component
* Component configuration
### [Logging](./LOGGING.md)
* How it works
* Log adapters
* Error codes & messages
* Debugging
### [Application](./APPLICATION.md)
* ImportTrait
* SpaceShip
* Fallback
* Functions & constants
###### tags: `Modur Framework v1.0.0`
\ No newline at end of file
This diff is collapsed.
# Logging
## How it works
* Logging package contains 3 components:
* **Controller** - Control all defined loggers
* **Storage** - Store name, settings and other information of Loggers
* **Builder** - Is passed into the `/config/log.php`. Builder read settings and according to that load loggers. If logger has unknown settings (for example. `'debug: true'` for `FileAdapter` -- it is irelevant), controller will ignore that.
* Logger according to adapter performs activity logging and works on predefined levels.
* Every adapter logs to different storage and under other conditions (file, database, web console, etc...)
* If there is no loggers defined, logger controller automatically define null logger `'main'`. This `'main'` logger logs records, but not save them.
* At first the logger with name `'main'` must be defined for right functionality of logging!
## Log adapters & configuration
### Available log adapters
* In default there is only `FileAdapter` which logs into the file at `/logs` directory.
* **Please make sure `/logs` directory exists and is writable!**
* `FileAdapter` is used to save logs to predefined file
* Configuration of this adapter is defined by 3 keys and their values in array:
* `string $loggerName = 'main'` - The name of the logger instance.
* `string $fileName = date()` - The name of file where logs will be saved. Extension of this file will be always `.log`
* `array $logLevels = []` - The array of log levels. This array must contains only constants from `LogLevel::class` which is included in PSR-3 package.
* More adapters will be available at Framework Packages extension or you can create your own.
### Managing loggers
* Loggers can be managed in `/config/log.php` configuration file.
* In this configuration file is available log builder which is used to create new logger.
* We can use the template below where we are creating an array `$loggers` and `foreach` loop which create all loggers from the array to the log storage.
* Working in `/config/log.php`
```
<?php
/** @var LoggerBuilder $log */
use OctopusCore\Log\Adapter\FileAdapter;
use OctopusCore\Log\LoggerBuilder;
use Psr\Log\LogLevel;
$loggers = [
'main' => new FileAdapter([
// Use only if you want to use
// another name of the key
//"loggerName" => "main",
"fileName" => "main.log",
// We can´t debug to file
//"debug" => false,
"logLevels" => [
LogLevel::INFO,
LogLevel::NOTICE,
LogLevel::WARNING
]
])
];
foreach ($loggers as $logger) {
$log->setLogger($logger);
}
```
## Error codes & message
* Folder `/resources/log` is used for `.json` configurations of error codes.
* The name of file must be in format `codes.{poolname}.json`, where `{poolname}` is the name of the logged pool.
* Example:
* working in `codes.system.json`
```