Ansofra Framework Documentation

Installation & Setup

Using Ansofra

To use Ansofra, you must install it globally on your device/machine.

Prerequisite: Make sure you already have Composer installed.

Step 1: Install Ansofra Globally

Open your terminal and run:

composer global require newdich/ansofra

Step 2: Confirm Ansofra is Installed

To confirm it’s installed, open your terminal and run:

ansofra --version

You should see output similar to:

Ansofra by Newdich Technology
Note: If you get a command not found error, make sure to add your Composer bin directory to your environment PATH.

Step 3: Create Your Project (App/Software) with Ansofra

Now that you have Ansofra installed on your machine, you can create your software project with it.

Navigate (cd) to the directory where you want to create the project and run:

ansofra new name_of_your_project

Example:

ansofra new myEcommerce

This command will:

  • Create a project folder named myEcommerce
  • Set up all Ansofra framework directories and files inside it
  • Automatically start a development server at http://localhost:8000

Open your browser and visit http://localhost:8000. You should see the welcome Ansofra page.

Congratulations! You can now start coding and building with Ansofra.

Step 4 (Optional): Keep Ansofra Updated

We regularly maintain and improve the Ansofra framework. It is good practice to keep your global installation up to date.

To update Ansofra, run:

composer global update newdich/ansofra

Introduction to Ansofra Framework

The Ansofra framework uses the PSR-4 autoload to load the entire directories, files, and parts. All namespaces are set in the autoload PSR-4 in the composer.json file.

Now, let's examine the directories from top to bottom. The first directory in the Ansofra framework is the ansofra directory.

Ansofra Framework

ANSOFRA is a modern PHP framework developed and maintained by Newdich Technology.

Official Website: www.newdich.tech
Support: [email protected]
Created by: Samuel Idebi (Senior Software Engineer)

ANSOFRA stands for Any Software Framework. It follows the CQRS (Command Query Responsibility Segregation) architecture and is optimized for building scalable software, APIs, and enterprise systems.

  • Speeds up development by ~60%
  • Easy to maintain
  • Familiar to .NET, Java & C++ developers

1. The ansofra Directory

This directory contains the index.php file, which is the entrance to the Ansofra app framework. The index.php file captures all incoming requests to the Ansofra app and sends them to the route.

Note: Do not tamper with the index.php file to avoid making things messy.

In a nutshell, the entrance to the Ansofra App is ansofra/index.php.

Note: In production, your document root should be set to /ansofra. That is, your root directory should look something like: html/user/home/ansofra or var/www/html/ansofra.

Also, in the ansofra directory, there is the public folder for frontend, in case you want to build both frontend and backend together.

2. The route Directory

The route directory has the file index.php in it. This is the file that checks every endpoint/request that comes into the Ansofra App.

It then routes the incoming request to the Controller (if it's an API request) or loads the page (if it's a frontend request).

You add your incoming request endpoints in this file.

Example 1:

Assuming there is an API endpoint you want to listen to at /api/login, meaning that the API request will be sent to /api/login.

Then in your route/index.php, you will add this before the else{} statement:

elseif($url === $usersArea . "/login"){
    require_once $appController . "/LoginController.php";
    exit;
}

You might wonder why we use $usersArea and $appController.

If you check the /route/index.php file, you will notice that $usersArea is already holding the value /api, so there is no need to manually write /api since $usersArea is already holding it. So we just use $usersArea.

Also, if you check the route/index.php file, you will see that $appController is already routing to Controller/App.

Then, LoginController.php is the file you want to write your controller code into. It should be created in the Controller/App directory.

Ansofra separates coding that has to do with admins from that of users. If the API or endpoint is a user endpoint or API, Ansofra expects that the endpoint should be /api, then you can attach any other endpoint to it, e.g., /api/login.

And it expects that you should send /api requests to the Controller/App directory.

In the same vein, Ansofra expects that anything that has to do with admin endpoints should be /apiadmin, then you can attach whatever endpoint you want, e.g., /apiadmin/staff.

And it expects that you should send /apiadmin requests to the Controller/Src directory.

Note: You can name your controller anything, but it's a professional practice to name it the name of the endpoint but in Pascal casing, and it should end with Controller.

Note: If the request you are listening to is a frontend request, and you want to display a webpage on getting the request, the full path of the file is what you will require.

For example, assuming you want to display the HTML webpage of the ansofra/public/index.html once users load / or /home on their browser.

Meaning that if a user opens their browser and loads http://localhost/ansofra/ or http://localhost/ansofra/home, then in the route/index.php file you will add something like:

elseif($url ==="/" || $url ==="/home"){
    require_once $serverDir . $rootDir . "/public/index.html";
    exit;
}

Note: $serverDir is already declared in route/index.php and it gets the document root of the project.

Note: $rootDir is already declared in route/index.php and it gets the root directory of the project. In production, $rootDir is usually not useful, but just leave it there.

3. The Controller Directory

The Controller Directory has 2 subdirectories which are:

  • App/ - The subdirectory in Controller that handles requests from /api endpoints.
  • Src/ - The subdirectory in Controller that handles requests from /apiadmin endpoints.

For example: Assuming you want to handle users registration requests, you create the controller (e.g., RegisterController.php) inside the /App subdirectory.

So, such requests will be controlled at Controller/App/RegisterController.php.

Practical Example:

Request from frontend looks like this:

fetch('/api/register', {
    method: "POST",
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data_to_send_to_api_in_an_object_format)
});

In the route/index.php file, you will put the following before the else{} statement:

elseif($url === $usersArea . "/register"){
    require_once $appController . "/RegisterController.php";
    exit;
}

In the Controller/App/RegisterController.php file, you will have something like:

namespace NewdichControllerApp;

4. The app Directory

The app directory is the directory where the business logic for all /api requests should be processed.

It has 2 subdirectories which are:

  • Command
  • Query

It follows the CQRS pattern. All command requests go to Command, and all query requests go to Query.

For example, if you want to write the logic for the registration of users, it will go inside Command.

So, inside the Command subdirectory, you create a Register.php file.

So, you can have something like:

namespace NewdichApp\Command;
use NewdichSchema\Migration;
use NewdichSchema\Platform;
use NewdichSchema\Settings;
use NewdichDto\AnsofraDto;

class Register {
    private $dto;
    private $table = Platform::USER_TABLE;

    public function __construct(AnsofraDto $dto) {
        $this->dto = $dto;
    }

    public function process() {
        $uniqueCol = "email";
        $uniqueValue = $this->dto->email;
        $dataToSave = [
            "email" => $this->dto->email,
            "fullname" => $this->dto->fullname,
            "country" => $this->dto->country
        ];
        $newMigration = new Migration(null, $this->table);
        $process = $newMigration->saveUnique($uniqueCol, $uniqueValue, $dataToSave);
        return $process;
    }
}

NewdichSchema is the namespace that handles Ansofra ORM Database.

It has 3 major classes:

  • Migration: This is the class that handles all ORM CRUD actions.

The Migration class has several methods (functions) for performing CRUD, and they are:

saveUnique($uniqueCol, $uniqueVal, $dataToSave)

This function/method is used when you're inserting or creating a new record in the database but you want uniqueness. For example, you want to register users but you don't want one email to register more than once.

E.g.

$uniqueCol = "email";
$uniqVal = "[email protected]";

Then the dataToSave in an object (key=>value) format.

The Migration class itself takes 2 arguments into its constructor, which are:

  1. The columns of the table.
  2. The name of the table.

Note: The columns of the table are only required when creating a table and running Migration. But for other CRUD operations like saving to table, selecting, etc., the first argument (the columns of the table) is not needed, hence we just put null in the first argument.

Then the second argument is the name of the table.

new Migration(null, "users_table")

So, you can now call any method you want on the Migration variable.

$migration = new Migration(null, "users_table");
$saving = $migration->saveUnique($uniqueCol, $uniqueValue, $dataToSave);

Check under Newdich Schema to see full details on Schema and using it.

Platform is another class in the NewdichSchema namespace that holds names of tables and table columns.

Settings class loads all the Ansofra Settings from the .env file.

The NewdichDto namespace is responsible for Data Transfer Object. It has a class named AnsofraDto where all Data Objects are set for transferring data from one point to the other on the Ansofra App.

5. The src Directory

The src directory is the directory where the business logic for all /apiadmin requests should be processed.

It has 2 subdirectories which are:

  • Command
  • Query

It follows the CQRS pattern. All command requests go to Command, and all query requests go to Query.

For example, if you want to write the logic for the registration of users, it will go inside Command.

So, inside the Command subdirectory, you create a Register.php file.

So, you can have something like:

namespace NewdichSrc\Command;
use NewdichSchema\Migration;
use NewdichSchema\Platform;
use NewdichSchema\Settings;
use NewdichDto\AnsofraDto;

class Register {
    private $dto;
    private $table = Platform::ADMIN_TABLE;

    public function __construct(AnsofraDto $dto) {
        $this->dto = $dto;
    }

    public function process() {
        $uniqueCol = "email";
        $uniqueValue = $this->dto->email;
        $dataToSave = [
            "email" => $this->dto->email,
            "fullname" => $this->dto->fullname,
            "country" => $this->dto->country
        ];
        $newMigration = new Migration(null, $this->table);
        $process = $newMigration->saveUnique($uniqueCol, $uniqueValue, $dataToSave);
        return $process;
    }
}

NewdichSchema is the namespace that handles Ansofra ORM Database.

It has 3 major classes:

  • Migration: This is the class that handles all ORM CRUD actions.

The Migration class has several methods (functions) for performing CRUD, and they are:

saveUnique($uniqueCol, $uniqueVal, $dataToSave)

This function/method is used when you're inserting or creating a new record in the database but you want uniqueness. For example, you want to register users but you don't want one email to register more than once.

E.g.

$uniqueCol = "email";
$uniqVal = "[email protected]";

Then the dataToSave in an object (key=>value) format.

The Migration class itself takes 2 arguments into its constructor, which are:

  1. The columns of the table.
  2. The name of the table.

Note: The columns of the table are only required when creating a table and running Migration. But for other CRUD operations like saving to table, selecting, etc., the first argument (the columns of the table) is not needed, hence we just put null in the first argument.

Then the second argument is the name of the table.

new Migration(null, "admin_table")

So, you can now call any method you want on the Migration variable.

$migration = new Migration(null, "admin_table");
$saving = $migration->saveUnique($uniqueCol, $uniqueValue, $dataToSave);

Check under Newdich Schema to see full details on Schema and using it.

Platform is another class in the NewdichSchema namespace that holds names of tables and table columns.

Settings class loads all the Ansofra Settings from the .env file.

The NewdichDto namespace is responsible for Data Transfer Object. It has a class named AnsofraDto where all Data Objects are set for transferring data from one point to the other on the Ansofra App.

6. The Dto Directory

The Dto directory handles the Data Transfer Object. It has the class AnsofraDto.php which handles all data transfer within the Ansofra App.

It has the namespace NewdichDto. Hence, the Ansofra App Dto is handled at Dto/AnsofraDto.php.

To use it, you use the namespace and class.

For example: assuming you want to use the DTO in the Register Controller:

namespace NewdichControllerApp;
use NewdichDto\AnsofraDto;

$incoming = $_POST; // data from frontend
$newDto = new AnsofraDto($incoming);

Let's look at a complete practical example of how to use the DTO from frontend

Practical example:

Frontend sends request, something like this:

fetch('/api/register', {
    method: "POST",
    body: data_from_the_html_form
});

Then in the route/index.php, you add this before the else{} statement:

elseif($url === $usersArea . "/register"){
    require_once $appController . "/RegisterController.php";
    exit;
}

Your Controller/App/RegisterController.php looks like this:

namespace NewdichControllerApp;
use NewdichDto\AnsofraDto;
use NewdichApp\Command\Register;

$incoming = $_POST;
$newDto = new AnsofraDto($incoming);
$register = new Register($newDto);
echo $register->process();
exit;

Note: Always clean incoming data with the cleanData() method inside the Middleware namespace and always hash passwords with the hashData() method within the Middleware namespace. Check the Middleware section to see an example of how to use it.

Your app/Command/Register.php looks like:

namespace NewdichApp\Command;
use NewdichSchema\Migration;
use NewdichSchema\Platform;
use NewdichSchema\Settings;
use NewdichDto\AnsofraDto;

class Register {
    private $dto;
    private $table = Platform::USERS_TABLE;

    public function __construct(AnsofraDto $dto) {
        $this->dto = $dto;
    }

    public function process() {
        $uniqueCol = "email";
        $uniqueVal = $this->dto->email;
        $dataToSave = [
            "email" => $this->dto->email,
            "fullname" => $this->dto->fullname
        ];
        $newMigration = new Migration(null, $this->table);
        $save = $newMigration->saveUnique($uniqueCol, $uniqueVal, $dataToSave);
        return $save;
    }
}

7. The Middleware Directory

The Middleware Directory is the namespace that holds the Index class.

It has a class named Index.php which contains methods/functions that can run on requests and responses. It has the following methods/functions:

  • getIp(): This method, when called, returns the IP address of the user.
  • hashData(parameter): This method, when called, returns the hashed/cipher text of the data/value passed into it. It uses the bcrypt hashing algorithm.
  • verifyHash(param1, param2): This method, when called, returns a boolean. It takes 2 arguments which are:
    param1 - The data or text you want to verify
    param2 - The hashed or cipher text you want to verify it against
    It uses the password_verify() method for verification and it returns boolean (true or false).
  • cleanData(parameter): This method cleans the data passed into it by removing white spaces and special characters.
  • Note: Do not clean passwords; that is, if it's a password, do not pass it through cleanData() but rather hashData().
  • otp(): This method, when called, generates a 6-numeric One Time Password that can be used for verification, etc.

To use the middleware, use the namespace NewdichMiddleware\Index.

For example: to use the middleware inside the RegisterController:

namespace NewdichControllerApp;
use NewdichDto\AnsofraDto;
use NewdichMiddleware\Index;
use NewdichApp\Command\Register;

$incoming = $_POST;
$newMiddleware = new Index();
$extractedData = [];
foreach ($incoming as $key => $value) {
    if ($key === "password") {
        $extractedData[$key] = $newMiddleware->hashData($value);
    } elseif ($key === "otp") {
        $extractedData[$key] = $newMiddleware->otp();
    } else {
        $extractedData[$key] = $newMiddleware->cleanData($value);
    }
}
$newDto = new AnsofraDto($extractedData);
$register = new Register($newDto);
echo $register->process();
exit;

8. The Schema Directory

The Schema directory is the namespace/directory that handles Database and ORM.

It has the Dealer.php class which loads the server configuration from the Settings.php class. Do not touch the Dealer.php class to avoid making things messy.

The Migration.php class holds all the methods/functions that run CRUD on the database. It accepts ORM and converts to SQL queries that run on the database. Do not change anything in the Migration.php class to avoid making things messy.

Methods/functions in the Migration.php class are:

Firstly, the Migration class takes 2 optional arguments in its constructor.

The first parameter is the columns of the table to work with (in an array format).

The second parameter is the name of the table to work with (a string).

new Migration(["users_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY", "email VARCHAR(255) NOT NULL"], "users_table");

In the situation where you aren't passing any arguments into the Migration constructor, just pass null into it.

new Migration(null, "users_table"); // if you are only passing the table to work with
// or
new Migration(null, null); // if you are not passing any constructor argument.

Methods in the Migration class:

  • createDB(parameter): This method creates the database name on the server. It takes a parameter which is the name of the database to create. It assumes that you already set your SERVER, SERVER_USER, SERVER_PASS in the .env file.
    Syntax:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, null);
    $newMigration->createDB(database_name_to_create);
  • createTB(): This method creates a table on the database. It assumes you already set your SERVER, SERVER_USER, SERVER_DB, SERVER_PASS in the .env file.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(["users_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY", "email VARCHAR(255) NOT NULL", "password VARCHAR(255) NOT NULL", "country VARCHAR(255)"], "users_table");
    $newMigration->createTB();
  • saveUnique($uniqueColumn, $uniqueValue, $dataToSaveInKeysAndValues): This method creates a row in the table with uniqueness, meaning that no two rows will be the same based on the uniqueColumn and uniqueValue set.
    For Example, assuming you want to register users, and you don't want one user to register an email more than once, then the uniqueColumn will be the column you don't want multiple rows for, then the uniqueValue will be the data of the user registering.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->saveUnique("email", "[email protected]", ["email"=>"[email protected]", "fullname"=>"sam newdich", "country"=>"nigeria"]);
  • saveUniqueMulti($uniqueColumnsInArray, $uniqueValuesInArray, $dataToSaveInKeysAndValues): This does the same thing as saveUnique(), the difference is that in saveUnique(), you can only set one uniqueColumn and one uniqueValue, but in saveUniqueMulti(), you can set multiple unique columns and multiple unique values, but must be in an array.
    For Example, assuming you want to register users, and you don't want the following values to be duplicated: email, phone.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->saveUniqueMulti(["email", "phone"], ["[email protected]", "08030000000"], ["email"=>"[email protected]", "phone"=>"08030000000", "fullname"=>"sam newdich", "country"=>"nigeria"]);
  • save($dataToSaveInKeysAndValues): This method creates a row in the table, and it does not care whether there is a duplicate row or not.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->save(["email"=>"[email protected]", "fullname"=>"sam newdich", "country"=>"nigeria"]);
  • get($conditionToSearchInKeyAndValue, $offset, $limit): This method fetches from the table and it takes 3 arguments.
    The first argument is the condition to search, e.g., search the email column where you find [email protected], so it will be like this: ["email" => "[email protected]"]
    Second argument is where to start selecting from, e.g., 0
    And the third argument is the total rows to select, e.g., 50.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->get(["email"=>"[email protected]"], 0, 50);
    So under the hood, the SQL query will be: SELECT * FROM users_table WHERE email="[email protected]"
  • getSpecific($conditionToSearchInKeyAndValue, $columnsToSelect, $offset, $limit): This method does the same thing as get(), only that it selects specific columns instead of all columns from the table.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->getSpecific(["email"=>"[email protected]"], ["email", "phone"], 0, 50);
    So under the hood, the SQL query will be: SELECT email, phone FROM users_table WHERE email="[email protected]"
    Note: This is perfect for optimization.
  • count($conditionToSearchInKeyAndValue): This method counts the total rows in a table based on the conditions passed into it.
    Assuming I want to count a table only where email is [email protected].
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->count(["email"=>"[email protected]"]);
  • remove($conditionToDeleteInKeyAndValue): This method deletes rows from a table based on the condition passed into it.
    Assuming I want to delete all rows with email of [email protected].
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->remove(["email"=>"[email protected]"]);
  • edit($dataToUpdateInKeysAndValues, $conditionsToUpdateInKeysAndValues): This method updates a table row(s) with the data passed into it and based on the conditions passed.
    Assuming I want to update price to be 2000 where item is pizza.
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->edit(["price"=>2000], ["item"=>"pizza"]);
  • removeTable(): This method drops/deletes a whole table. It returns a boolean (true/false or 0/1).
    Syntax Example:
    namespace NewdichSchema\Migration;
    $newMigration = new Migration(null, "users_table");
    $newMigration->removeTable();
    Note: Use with caution.

Notes: saveUnique(), saveUniqueMulti(), save(), get(), getSpecific(), count(), remove(), edit() methods in the Migration class all return:

  • status: the status of the method, either success or failed
  • response: The details of the action performed.

Platform.php class: This class is where you set your table name and the columns you want the tables to have.

Note: All tables must have the first column as the table name underscore id. Assuming I want to create a table named buy, the first column should be buy_id.

Note: Always end the table const with _TABLE and end the table columns const with _TABLE_COLUMNS

Note: Both table const and table const columns must have the same name, e.g

public const STUDENTS_TABLE ="students"; public const STUDENTS_TABLE_COLUMNS =[ "students_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY", "fullname VARCHAR(255)", "email VARCHAR(255)" ];

Instead of hardcoding your table directly in your Migration class or in your business logic, declare them in the Platform class and call them when needed.

See the examples in the Platform.php class and see how you can add your tables and their columns.

Include it and use it:

use NewdichSchema\Platform;
Platform::USERS_TABLE;

Settings.php class: This class loads the configuration from the bootstrap.php file, which in turn loads from the .env file.

Meaning that Settings.php loads from bootstrap.php, and bootstrap.php loads from .env file.

Anywhere you want to use data from the .env file in your code, just use the Settings class by including it and call any variable you want:

use NewdichSchema\Settings;
Settings::DOMAIN_NAME;

RunMigration.php class: This class creates a new Database or Table if not already existing on the server.

Always run migration whenever you add table and table columns in the Platform class

Check out the examples in the RunMigration class to see how you can run migration/create a new database or table.

To Run the Migration, open your browser and load this:

  • In Development: http://localhost/ansofra/apiadmin/run_migration
  • In Production: https://yourwebsite.com/apiadmin/run_migration

Note: Once you have successfully run migration and you won't be needing it again, you can just comment out the whole content in the RunMigration.php class.

9. The Cache Directory

The cache directory uses Redis to cache data.

To use this directory/namespace, it assumes that you have a Redis server and you must have set your REDIS_SERVER_IP, REDIS_SERVER_PORT, REDIS_AUTH_PASSWORD in the .env file.

It has the Setup.php class that loads the REDIS SERVER.

It has the Index.php class. The Index class contains all the methods that can be used in the Cache namespace.

Methods in the Index class:

  • setCache($key, $dataToCache): This method uses the Redis set() function under the hood to cache data.
    Syntax Example:
    use NewdichCache\Index;
    $newCache = new Index();
    $newCache->setCache("user_data", json_encode(["email"=>"[email protected]", "phone"=>"0803000000000"], true));
    It returns:
    status: success or failed
    response: details of the action performed
  • setExpireCache($key, $dataToCache, $time): This does the same thing as setCache(), only that it has an expiring time.
    Note: $time must be an integer or a timestamp.
    Syntax Example:
    use NewdichCache\Index;
    $newCache = new Index();
    $newCache->setExpireCache("user_data", json_encode(["email"=>"[email protected]", "phone"=>"0803000000000"], true), 1770274627);
    It returns:
    status: success or failed
    response: details of the action performed
  • setIncrease($key): This method increases the value of an already set key.
    Syntax Example:
    use NewdichCache\Index;
    $newCache = new Index();
    $newCache->setIncrease("pageviews");
    It returns:
    status: success or failed
    response: details of the action performed
  • setDecrease($key): This method decreases the value of an already set key.
    Syntax Example:
    use NewdichCache\Index;
    $newCache = new Index();
    $newCache->setDecrease("pageviews");
    It returns:
    status: success or failed
    response: details of the action performed
  • getCache($key): This method retrieves the values stored in Redis by calling the key.
    Syntax Example:
    use NewdichCache\Index;
    $newCache = new Index();
    $newCache->getCache("pageviews");
    It returns:
    status: success or failed
    response: details of the action performed

RateLimit.php class can be used to set a limit to the rate at which traffic hits your endpoint. This is important and very useful to guard against DDoS/DoS, Brute-force Attacks.

It has a constructor that takes 3 arguments:

  • limit: maximum request/traffic allowed within a window frame (in seconds)
  • window: The time duration allowed before the maximum request is hit
  • ip: The IP address of the user

Syntax Example:

use NewdichCache\RateLimit;
$newRateLimit = new RateLimit(20, 60, "192.0.0.1");
$newRateLimit->process();

10. The Files Directory

The Files Directory/namespace handles file processing like file upload and file download.

The Upload.php class: This is a class that handles file uploading in your Ansofra App.

It assumes that you have set your UPLOAD_DIRECTORY in the .env file. Also set your MAX_UPLOAD_SIZE.

Note: Make sure your server also allows upload for the same file size you set in your .env file. You can set it in your php.ini file: (upload_max_filesize = the same value you set in .env) (post_max_size = let it be a bit higher than the value you set in .env).

It uses the Namespace NewdichFiles.

The Upload class can handle up to 20 file uploads at a time.

On successful upload, the Upload class has a method process() that returns:

  • status: success or failed
  • response: the array of the hashed file names that were uploaded or array of errors

Let's see a practical example using the Upload class:

Frontend looks like:

<form id="upload">
    <input type="text" name="email" /><br>
    <input type="text" name="password" /><br>
    <input type="file" name="file[]" multiple /><br>
    <button onclick="uploadData()">Upload</button>
</form>
<div id="output"></div>
<script>
    async function uploadData() {
        event.preventDefault();
        const form = document.getElementById("upload");
        const formdata = new FormData(form);
        let api = await fetch('/api/upload', {
            method: "POST",
            body: formdata
        });
        let res = await api.json();
        if (res.status === "success") {
            document.getElementById("output").textContent = res.response;
        } else {
            document.getElementById("output").textContent = res.response;
        }
    }
</script>

In your route/index.php add the following before the else{} statement:

elseif($url === $usersArea . "/upload"){
    require_once $appController . "/UploadController.php";
    exit;
}

In your Controller/App/UploadController.php:

namespace NewdichControllerApp;
use NewdichDto\AnsofraDto;
use NewdichMiddleware\Index;
use NewdichFiles\Upload;
use NewdichApp\Command\Register;

$incoming = $_POST; // this gets all inputs apart from files
$files = $_FILES["file"] ?? [];
$newUpload = new Upload($files);
$uploadProcess = $newUpload->process();
$uploadProcessDecode = json_decode($uploadProcess, true);
if ($uploadProcessDecode["status"] === "success") {
    $uploadProcessResponse = json_decode($uploadProcessDecode["response"], true);
    $newMiddleware = new Index();
    $extractedData = [];
    foreach ($incoming as $key => $value) {
        if ($key === "password") {
            $extractedData[$key] = $newMiddleware->hashData($value);
        } elseif ($key === "otp") {
            $extractedData[$key] = $newMiddleware->otp();
        } elseif ($key === "file" || $key === "file[]") {
            $extractedData[$key] = $uploadProcessResponse;
        } else {
            $extractedData[$key] = $newMiddleware->cleanData($value);
        }
    }

    $newDto = new AnsofraDto($extractedData);
    $newRegister = new Register($newDto);
    echo $newRegister->process();
    exit;
} else {
    echo $uploadProcess;
    exit;
}

Then your app/Command/Register.php looks like:

namespace NewdichApp\Command;
use NewdichSchema\Migration;
use NewdichSchema\Platform;
use NewdichSchema\Settings;
use NewdichDto\AnsofraDto;

class Register {
    private $dto;
    private $table = Platform::USERS_TABLE;

    public function __construct(AnsofraDto $dto) {
        $this->dto = $dto;
    }

    public function process() {
        $uniqueColumn = "email";
        $uniqueValue = $this->dto->email;
        $dataToSave = [
            "email" => $this->dto->email,
            "password" => $this->dto->password,
            "otp" => $this->dto->otp,
            "file" => $this->dto->file
        ];
        $newMigration = new Migration(null, $this->table);
        $save = $newMigration->saveUnique($uniqueColumn, $uniqueValue, $dataToSave);
        return $save;
    }
}

The Download class downloads a file from the server to the user's device.

It takes one argument into its constructor, which is the full path to the file to download, then call the process() function on it.

Example:

use NewdichFiles\Download;
$newDownload = new Download("/var/www/html/myproject/ansofra/public/uploads/file_to_download.pdf");
$newDownload->process();

11. The Mail Directory

The mail directory/namespace is used when you want to send mails. Ansofra App comes with 4 email service providers and they are declared in each class/file in the Mail directory:

  • Index.php: This class is for sending email via Gmail's SMTP.
  • Mailersend.php: Use this class if you prefer Mailersend.
  • Mailgun.php: This is for Mailgun.
  • Sendgrid: This is for SendGrid, if you prefer them.

Note: Whichever one you are using, make sure you set keys and other credentials in the .env file.

Note: Each class has its own method that can be called to send emails.

Their namespace is NewdichMail.

Syntax Example:

use NewdichMail\Mailgun;
$newMailgun = new Mailgun();
$newMailgun->send("[email protected]", "Recipient Name", "Mail subject", "Body or content of mail");

It returns:

  • status: success or failed
  • response: details of the action performed

12. The apis Directory

The apis directory handles the logic for third-party services.

The namespace is NewdichApis.

For example, to use the CurrencyConverter class:

use NewdichApis\CurrencyConverter;
use NewdichDto\AnsofraDto;

$newAnsofraDto = new AnsofraDto;
$newCurrencyConverter = new CurrencyConverter($newAnsofraDto);
$newCurrencyConverter->process();

13. The Auth Directory

This class uses JWT for Authentication and Authorization. It assumes that you already set your AUTH_KEY, JWT_KEY, JWT_EXPIRY, JWT_SECURE_LEVEL, JWT_SAMESITE, JWT_HASH_ALGORITHM in the .env file.

This directory has 2 classes:

  • Authentication: This class authenticates users, and it's commonly used once the user's login credentials have been validated.
    It has an auth() method that takes 2 arguments, which are:
    $user_id: The ID of the user
    $role: The role assigned for the user.
    It returns:
    status: success or failed
    response: The details of the action performed.
    Professional standard demands that Auth is used inside the Controller.
    Example:
    namespace NewdichControllerApp;
    use NewdichDto\AnsofraDto;
    use NewdichMiddleware\Index;
    use NewdichAuth\Authentication;
    use NewdichApp\Query\Login;
    
    $incoming = $_POST;
    $newDto = new AnsofraDto($incoming);
    $newLogin = new Login($newDto);
    $loginProcess = $newLogin->process();
    $loginProcessDecode = json_decode($loginProcess, true);
    if ($loginProcessDecode["status"] === "success") {
        $loginResponse = $loginProcessDecode["response"][0];
        $userId = $loginResponse["users_id"];
        $fullname = $loginResponse["fullname"];
        $role = "user";
        $newAuthication = new Authentication();
        $newAuth = $newAuthentication->auth($userId, $role); // Authentication also sets Cookies to user's device
        $newAuthDec = json_decode($newAuth, true);
        if ($newAuthDec["status"] === "success") {
            $jwt_generated_key = $newAuthDec["response"]; // You may send the key to the frontend if you want
            $outputArray = ["fullname" => $fullname, "jwt_generated_key" => $jwt_generated_key];
            echo json_encode(["status" => "success", "response" => $outputArray], JSON_PRETTY_PRINT);
            exit;
        } else {
            echo $newAuth;
            exit;
        }
    } else {
        echo $loginProcess;
        exit;
    }
  • Authorization class checks if a user is authorized/permitted to access an endpoint by using JWT.
    It is used on any endpoint that is accessible based on roles/permissions.
    It has a method authorize() that runs on the Authorization object.
    It is a professional standard that you authorize in the Controller.
    Example:
    use NewdichControllerApp;
    use NewdichDto\AnsofraDto;
    use NewdichMiddleware\Index;
    use NewdichAuth\Authorization;
    
    $incoming = $_POST;
    $newAuthorization = new Authorization();
    $auth = $newAuthorization->authorize();
    $authDec = json_decode($auth, true);
    if ($authDec["status"] === "success") {
        // Then you can grant access/permission here
    } else {
        echo $auth;
        exit;
    }

14. The bootstrap.php File

This file loads data/configuration from the .env file. Do not tamper with this file to avoid making things messy.

15. The ansofra-generator.sh Shell

This is a shell file that you run to generate Swagger API documentation.

Note: You must have added Swagger documentation into your Command and Query endpoints.

16. The .env.example File

This file contains what you should have in your .env file.

Once you create your .env file, copy everything in the .env.example file into it and edit/configure it.

17. The .htaccess File

This file is very sensitive as it controls your server

In Production, remove the ansofra/ from the RewriteRule

So, in production, RewriteRule will be :

RewriteRule ^ index.php [QSA,L]

But, in development, is should be:

RewriteRule ^ ansofra/index.php [QSA,L]

18. Swagger UI

If you generated Swagger documentation in your project, you can view it on the ansofra/public/swagger directory.

For Documentations at the app/ directory (that is /api endpoints), you access the Swagger UI at ansofra/public/swagger/.

For Documentations at the src/ directory (that is /apiadmin endpoints), you access the Swagger UI at ansofra/public/swagger/src.

Production-Ready Settings

Once you are done developing your software and you want to push to production, always remember to do the following:

    Remove ansofra/ from the RewriteRule in the .htaccess , so the RewriteRule rule in production will be
RewriteRule ^ index.php [QSA,L]

    Do not set value to the DOC_ROOT in the .env file, so in production, the DOC_ROOT in .env file will be
DOC_ROOT=

    Do not set value to the ROOT_DIRECTORY in the .env file, so in production, the ROOT_DIRECTORY in .env file will be
ROOT_DIRECTORY=

    When setting your document root in your production server, always add /ansofra to it e.g your document root in production server will be like
/home/your_username/your_project/ansofra

    Also Adjust the href link to your css, js, assets and other static assets in your frontend(public/ directory) accordingly