diff --git a/.gitattributes b/.gitattributes
index c956eb4b9..4c3b0299d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -11,4 +11,9 @@
/ecs.php export-ignore
/php_cs.php.dist export-ignore
/phpbench.json export-ignore
-/sonar-project.properties export-ignore
\ No newline at end of file
+/sonar-project.properties export-ignore
+/ecs.php export-ignore
+/CHANGELOG.md export-ignore
+/release-commit.php export-ignore
+/release-please-manifest.json export-ignore
+/release-please-config.json export-ignore
\ No newline at end of file
diff --git a/.github/workflows/php70.yml b/.github/workflows/php70.yml
deleted file mode 100644
index ae33f2f72..000000000
--- a/.github/workflows/php70.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Build PHP 7.0
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- services:
- sql.data:
- image: mcr.microsoft.com/mssql/server:2019-latest
- env:
- SA_PASSWORD: 1234567890@Eu
- ACCEPT_EULA: Y
- MSSQL_PID: Express
- ports:
- - "1433:1433"
- strategy:
- fail-fast: true
- matrix:
- os: [ ubuntu-latest ]
- php: [7.0]
-
- name: PHP${{matrix.php}} - ${{matrix.os}}
-
- steps:
- - name: Clone Repo
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: mysqli, mbstring, sqlsrv
- tools: phpunit:5.7.27, composer
-
- - name: Shutdown Ubuntu MySQL
- run: sudo service mysql stop
-
- - name: Set up MySQL
- uses: mirromutth/mysql-action@v1.1
- with:
- mysql version: '5.7'
- mysql database: 'testing_db'
- mysql root password: 123456
- mysql user: 'root'
- mysql password: 123456
-
- - name: Wait for MySQL
- run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
- sleep 1
- done
- - name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
- - name: Install Dependencies
- run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
-
- - name: CodeCov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/php71.yml b/.github/workflows/php71.yml
deleted file mode 100644
index cb20b2697..000000000
--- a/.github/workflows/php71.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-name: Build PHP 7.1
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- services:
- sql.data:
- image: mcr.microsoft.com/mssql/server:2019-latest
- env:
- SA_PASSWORD: 1234567890@Eu
- ACCEPT_EULA: Y
- MSSQL_PID: Express
- ports:
- - "1433:1433"
- strategy:
- fail-fast: true
- matrix:
- os: [ ubuntu-latest ]
- php: [7.1]
-
- name: PHP${{matrix.php}} - ${{matrix.os}}
-
- steps:
- - name: Clone Repo
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: mysqli, mbstring, sqlsrv
- tools: phpunit:5.7.27, composer
-
- - name: Shutdown Ubuntu MySQL
- run: sudo service mysql stop
-
- - name: Set up MySQL
- uses: mirromutth/mysql-action@v1.1
- with:
- mysql version: '5.7'
- mysql database: 'testing_db'
- mysql root password: 123456
- mysql user: 'root'
- mysql password: 123456
-
- - name: Wait for MySQL
- run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
- sleep 1
- done
-
- - name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
- - name: Install Dependencies
- run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
-
- - name: CodeCov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/php72.yml b/.github/workflows/php72.yml
deleted file mode 100644
index 7cfa8e832..000000000
--- a/.github/workflows/php72.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Build PHP 7.2
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- services:
- sql.data:
- image: mcr.microsoft.com/mssql/server:2019-latest
- env:
- SA_PASSWORD: 1234567890@Eu
- ACCEPT_EULA: Y
- MSSQL_PID: Express
- ports:
- - "1433:1433"
- strategy:
- fail-fast: true
- matrix:
- os: [ ubuntu-latest ]
- php: [7.2]
-
- name: PHP${{matrix.php}} - ${{matrix.os}}
-
- steps:
- - name: Clone Repo
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: mysqli, mbstring, sqlsrv
- tools: phpunit:8.5.13
-
- - name: Shutdown Ubuntu MySQL
- run: sudo service mysql stop
-
- - name: Set up MySQL
- uses: mirromutth/mysql-action@v1.1
- with:
- mysql version: '5.7'
- mysql database: 'testing_db'
- mysql root password: 123456
- mysql user: 'root'
- mysql password: 123456
-
- - name: Wait for MySQL
- run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
- sleep 1
- done
-
- - name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
- - name: Install Dependencies
- run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
-
- - name: CodeCov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
-
-
diff --git a/.github/workflows/php73.yml b/.github/workflows/php73.yml
deleted file mode 100644
index 8bce6f656..000000000
--- a/.github/workflows/php73.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-name: Build PHP 7.3
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- services:
- sql.data:
- image: mcr.microsoft.com/mssql/server:2019-latest
- env:
- SA_PASSWORD: 1234567890@Eu
- ACCEPT_EULA: Y
- MSSQL_PID: Express
- ports:
- - "1433:1433"
- strategy:
- fail-fast: true
- matrix:
- os: [ ubuntu-latest ]
- php: [7.3]
-
- name: PHP${{matrix.php}} - ${{matrix.os}}
-
- steps:
- - name: Clone Repo
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: mysqli, mbstring, sqlsrv
- tools: phpunit:8.5.13
-
- - name: Shutdown Ubuntu MySQL
- run: sudo service mysql stop
-
- - name: Set up MySQL
- uses: mirromutth/mysql-action@v1.1
- with:
- mysql version: '5.7'
- mysql database: 'testing_db'
- mysql root password: 123456
- mysql user: 'root'
- mysql password: 123456
-
- - name: Wait for MySQL
- run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
- sleep 1
- done
-
- - name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
- - name: Install Dependencies
- run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
-
- - name: CodeCov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: SonarCloud
- uses: SonarSource/sonarcloud-github-action@master
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-
-
diff --git a/.github/workflows/php74.yml b/.github/workflows/php74.yml
deleted file mode 100644
index 2761957fc..000000000
--- a/.github/workflows/php74.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-name: Build PHP 7.4
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- services:
- sql.data:
- image: mcr.microsoft.com/mssql/server:2019-latest
- env:
- SA_PASSWORD: 1234567890@Eu
- ACCEPT_EULA: Y
- MSSQL_PID: Express
- ports:
- - "1433:1433"
- strategy:
- fail-fast: true
- matrix:
- os: [ ubuntu-latest ]
- php: [7.4]
-
- name: PHP${{matrix.php}} - ${{matrix.os}}
-
- steps:
- - name: Clone Repo
- uses: actions/checkout@v4
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: mysqli, mbstring, sqlsrv
- tools: phpunit:8.5.13
-
- - name: Shutdown Ubuntu MySQL
- run: sudo service mysql stop
-
- - name: Set up MySQL
- uses: mirromutth/mysql-action@v1.1
- with:
- mysql version: '5.7'
- mysql database: 'testing_db'
- mysql root password: 123456
- mysql user: 'root'
- mysql password: 123456
-
- - name: Wait for MySQL
- run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
- sleep 1
- done
-
- - name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
- - name: Install Dependencies
- run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
-
- - name: CodeCov
- uses: codecov/codecov-action@v4
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
-
-
-
diff --git a/.github/workflows/php80.yml b/.github/workflows/php80.yml
index 4bb63d38e..20c072522 100644
--- a/.github/workflows/php80.yml
+++ b/.github/workflows/php80.yml
@@ -2,9 +2,9 @@ name: Build PHP 8.0
on:
push:
- branches: [ master ]
+ branches: [ main ]
pull_request:
- branches: [ master ]
+ branches: [ main ]
jobs:
test:
@@ -13,7 +13,7 @@ jobs:
sql.data:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
- SA_PASSWORD: 1234567890@Eu
+ SA_PASSWORD: ${{ secrets.SA_PASSWORD }}
ACCEPT_EULA: Y
MSSQL_PID: Express
ports:
@@ -45,24 +45,29 @@ jobs:
with:
mysql version: '5.7'
mysql database: 'testing_db'
- mysql root password: 123456
+ mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
mysql user: 'root'
- mysql password: 123456
+ mysql password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
- name: Wait for MySQL
run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
+ while ! mysqladmin ping --host=127.0.0.1 --password=${{ secrets.MYSQL_ROOT_PASSWORD }} --silent; do
sleep 1
done
- name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
+ run: |
+ curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
+ curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
+ sudo apt update
+ sudo apt install mssql-tools18
+ /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${{ secrets.SA_PASSWORD }} -Q 'create database testing_db' -C
- name: Install Dependencies
run: composer install --prefer-dist --no-interaction --no-dev
- name: Execute Tests
- run: phpunit
+ run: phpunit --configuration tests/phpunit.xml
- name: CodeCov
uses: codecov/codecov-action@v4
diff --git a/.github/workflows/php81.yml b/.github/workflows/php81.yml
index 1d2db2034..dba9551d4 100644
--- a/.github/workflows/php81.yml
+++ b/.github/workflows/php81.yml
@@ -2,18 +2,18 @@ name: Build PHP 8.1
on:
push:
- branches: [ master ]
+ branches: [ main ]
pull_request:
- branches: [ master ]
+ branches: [ main ]
jobs:
test:
- runs-on: ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
services:
sql.data:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
- SA_PASSWORD: 1234567890@Eu
+ SA_PASSWORD: ${{ secrets.SA_PASSWORD }}
ACCEPT_EULA: Y
MSSQL_PID: Express
ports:
@@ -45,24 +45,29 @@ jobs:
with:
mysql version: '5.7'
mysql database: 'testing_db'
- mysql root password: 123456
+ mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
mysql user: 'root'
- mysql password: 123456
+ mysql password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
- name: Wait for MySQL
run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
+ while ! mysqladmin ping --host=127.0.0.1 --password=${{ secrets.MYSQL_ROOT_PASSWORD }} --silent; do
sleep 1
done
- name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
+ run: |
+ curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
+ curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
+ sudo apt update
+ sudo apt install mssql-tools18
+ /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${{ secrets.SA_PASSWORD }} -Q 'create database testing_db' -C
+
- name: Install Dependencies
run: composer install --prefer-dist --no-interaction --no-dev
- name: Execute Tests
- run: phpunit
+ run: phpunit --configuration tests/phpunit.xml
- name: CodeCov
uses: codecov/codecov-action@v4
diff --git a/.github/workflows/php82.yml b/.github/workflows/php82.yml
index 0cbde10be..d96db4b94 100644
--- a/.github/workflows/php82.yml
+++ b/.github/workflows/php82.yml
@@ -2,9 +2,9 @@ name: Build PHP 8.2
on:
push:
- branches: [ master, dev ]
+ branches: [ main, dev ]
pull_request:
- branches: [ master ]
+ branches: [ main ]
jobs:
@@ -14,7 +14,7 @@ jobs:
sql.data:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
- SA_PASSWORD: 1234567890@Eu
+ SA_PASSWORD: ${{ secrets.SA_PASSWORD }}
ACCEPT_EULA: Y
MSSQL_PID: Express
ports:
@@ -28,8 +28,8 @@ jobs:
name: PHP${{matrix.php}} - ${{matrix.os}}
steps:
- - name: Clone Repo
- uses: actions/checkout@v3
+ - name: Clone Repos
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -46,24 +46,29 @@ jobs:
with:
mysql version: '5.7'
mysql database: 'testing_db'
- mysql root password: 123456
+ mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
mysql user: 'root'
- mysql password: 123456
+ mysql password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
- name: Wait for MySQL
run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
+ while ! mysqladmin ping --host=127.0.0.1 --password=${{ secrets.MYSQL_ROOT_PASSWORD }} --silent; do
sleep 1
done
- name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
+ run: |
+ curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
+ curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
+ sudo apt update
+ sudo apt install mssql-tools18
+ /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${{ secrets.SA_PASSWORD }} -Q 'create database testing_db' -C
+
- name: Install Dependencies
run: composer install --prefer-dist --no-interaction --no-dev
- name: Execute Tests
- run: phpunit
+ run: phpunit --configuration tests/phpunit.xml
- name: CodeCov
uses: codecov/codecov-action@v4
diff --git a/.github/workflows/php83.yml b/.github/workflows/php83.yml
index 20b2f47e2..f4c23a0cf 100644
--- a/.github/workflows/php83.yml
+++ b/.github/workflows/php83.yml
@@ -2,9 +2,9 @@ name: Build PHP 8.3
on:
push:
- branches: [ master, dev ]
+ branches: [ main, dev ]
pull_request:
- branches: [ master ]
+ branches: [ main, dev ]
jobs:
@@ -14,7 +14,7 @@ jobs:
sql.data:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
- SA_PASSWORD: 1234567890@Eu
+ SA_PASSWORD: ${{ secrets.SA_PASSWORD }}
ACCEPT_EULA: Y
MSSQL_PID: Express
ports:
@@ -46,25 +46,30 @@ jobs:
with:
mysql version: '5.7'
mysql database: 'testing_db'
- mysql root password: 123456
+ mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
mysql user: 'root'
- mysql password: 123456
+ mysql password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
- name: Wait for MySQL
run: |
- while ! mysqladmin ping --host=127.0.0.1 --password=123456 --silent; do
+ while ! mysqladmin ping --host=127.0.0.1 --password=${{ secrets.MYSQL_ROOT_PASSWORD }} --silent; do
sleep 1
done
- name: Setup MSSQL
- run: sqlcmd -S localhost -U SA -P 1234567890@Eu -Q 'create database testing_db'
-
+ run: |
+ curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
+ curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
+ sudo apt update
+ sudo apt install mssql-tools18
+ /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${{ secrets.SA_PASSWORD }} -Q 'create database testing_db' -C
+
- name: Install Dependencies
run: composer install --prefer-dist --no-interaction --no-dev
-
- - name: Execute Tests
- run: phpunit
+ - name: Execute Tests
+ run: phpunit --configuration tests/phpunit.xml
+
- name: CodeCov
uses: codecov/codecov-action@v4
with:
@@ -118,5 +123,36 @@ jobs:
- name: Benchmarking
run: phpbench run tests/webfiori/benchmark --report=default
+ release_staging:
+ name: Prepare Beta Release Branch / Publish Release
+ needs:
+ - "test"
+ - "coding_standards_check"
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/dev'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: google-github-actions/release-please-action@v4
+ with:
+ release-type: php
+ target-branch: dev
+ config-file: release-please-config.json
+ manifest-file: .release-please-manifest.json
+ token: ${{ secrets.GITHUB_TOKEN }}
+ release_prod:
+ name: Prepare Production Release Branch / Publish Release
+ needs:
+ - "test"
+ - "coding_standards_check"
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: google-github-actions/release-please-action@v4
+ with:
+ release-type: php
+ target-branch: master
+ config-file: release-please-config.json
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/php84.yml b/.github/workflows/php84.yml
new file mode 100644
index 000000000..ddbb3e05e
--- /dev/null
+++ b/.github/workflows/php84.yml
@@ -0,0 +1,127 @@
+name: Build PHP 8.4
+
+on:
+ push:
+ branches: [ main, dev ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+
+ test:
+ runs-on: ${{ matrix.os }}
+ services:
+ sql.data:
+ image: mcr.microsoft.com/mssql/server:2019-latest
+ env:
+ SA_PASSWORD: ${{ secrets.SA_PASSWORD }}
+ ACCEPT_EULA: Y
+ MSSQL_PID: Express
+ ports:
+ - "1433:1433"
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ ubuntu-latest ]
+ php: [8.4]
+
+ name: PHP${{matrix.php}} - ${{matrix.os}}
+
+ steps:
+ - name: Clone Repo
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: mysqli, mbstring, sqlsrv
+ tools: phpunit:9.5.20, composer, symplify/easy-coding-standard:12.0.6, phpbench/phpbench:1.2.14
+
+ - name: Shutdown Ubuntu MySQL
+ run: sudo service mysql stop
+
+ - name: Set up MySQL
+ uses: mirromutth/mysql-action@v1.1
+ with:
+ mysql version: '5.7'
+ mysql database: 'testing_db'
+ mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
+ mysql user: 'root'
+ mysql password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
+
+ - name: Wait for MySQL
+ run: |
+ while ! mysqladmin ping --host=127.0.0.1 --password=${{ secrets.MYSQL_ROOT_PASSWORD }} --silent; do
+ sleep 1
+ done
+
+ - name: Setup MSSQL
+ run: |
+ curl https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc
+ curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
+ sudo apt update
+ sudo apt install mssql-tools18
+ /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${{ secrets.SA_PASSWORD }} -Q 'create database testing_db' -C
+
+ - name: Install Dependencies
+ run: composer install --prefer-dist --no-interaction --no-dev
+
+ - name: Execute Tests
+ run: phpunit --configuration tests/phpunit.xml
+
+ - name: CodeCov
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+
+ coding_standards_check:
+ name: "Coding Standards Check"
+ needs:
+ - "test"
+
+ runs-on: "ubuntu-latest"
+ steps:
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ php-version: "8.1"
+ extensions: "mbstring"
+ tools: composer, symplify/easy-coding-standard:12.0.6
+
+ - name: "Checkout code"
+ uses: "actions/checkout@v3"
+
+ - name: Install Dependencies
+ run: composer install --prefer-dist --no-interaction --no-dev
+
+ - name: "Check Style"
+ run: "ecs check"
+
+ benchmarking:
+ needs:
+ - "test"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ ubuntu-latest ]
+ php: [8.0,8.1,8.2]
+ steps:
+ - name: Clone Repo
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: mbstring
+ tools: composer, phpbench/phpbench
+
+ - name: Install Dependencies
+ run: composer install --prefer-dist --no-interaction --no-dev
+
+ - name: Benchmarking
+ run: phpbench run tests/webfiori/benchmark --report=default
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..8fa8d739f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,958 @@
+# Changelog
+
+## [3.0.0-Beta.26](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.26...v3.0.0-Beta.26) (2025-04-07)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Fully Implemented Migrations Writer ([ac42d3f](https://github.com/WebFiori/framework/commit/ac42d3fe853fa259382bc05214483ac4146c341e))
+* Rollback of Migrations ([078c94f](https://github.com/WebFiori/framework/commit/078c94f98b090176b22e2354e326be7153258663))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* Buffer Theme Components as They Might be HTML ([d803352](https://github.com/WebFiori/framework/commit/d803352bb1d97436f242807879e665c24015845a))
+* Class Path ([98c6ee6](https://github.com/WebFiori/framework/commit/98c6ee64ca4164b04c9453ef6e16685ee5b8b176))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Created Class Path ([0592ed2](https://github.com/WebFiori/framework/commit/0592ed22403d5890f03279d3dec11f35ee968946))
+* Fix Assignment Issue ([34a522f](https://github.com/WebFiori/framework/commit/34a522ff53e8ad7bc8bc1287bc0c7595a4d7e254))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Create Migration with Defaults ([534845d](https://github.com/WebFiori/framework/commit/534845df19c2fe7f2bec8559b77e47576c04313a))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Registering Middleware ([6cc7ce1](https://github.com/WebFiori/framework/commit/6cc7ce1e39bf0aebfe0eb5ff91360e4c4ed04f05))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* Show Exception on Initialization ([2341841](https://github.com/WebFiori/framework/commit/2341841f9718beb99330f8529e01600d61acfecf))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.0.0-Beta.17 ([a3e5983](https://github.com/WebFiori/framework/commit/a3e598305d75f2fa87d6148520d88f4235a53253))
+* **dev:** release 3.0.0-Beta.18 ([a994097](https://github.com/WebFiori/framework/commit/a994097c021b5575cab7f077f8d730736c3c1bbe))
+* **dev:** release 3.0.0-Beta.19 ([e917eb5](https://github.com/WebFiori/framework/commit/e917eb5ccd18ee8223a73ec9a2ac499a281f5764))
+* **dev:** release 3.0.0-Beta.19 ([5497825](https://github.com/WebFiori/framework/commit/549782509f23be3a90375debe0319b16e549a3aa))
+* **dev:** release 3.0.0-Beta.20 ([7afdb92](https://github.com/WebFiori/framework/commit/7afdb92618bdbc6e11adb29b001d9a9d0a8f4809))
+* **dev:** release 3.0.0-Beta.21 ([eef1270](https://github.com/WebFiori/framework/commit/eef1270f51089065f04e7f8843d9944d368a774e))
+* **dev:** release 3.0.0-Beta.22 ([0e428f0](https://github.com/WebFiori/framework/commit/0e428f03ef510cb9c33bbf940c92b12ff702c8dd))
+* **dev:** release 3.0.0-Beta.23 ([0f70d8b](https://github.com/WebFiori/framework/commit/0f70d8bd2bcfef80bd677d242c79be3cb23a122f))
+* **dev:** release 3.0.0-Beta.24 ([d77357f](https://github.com/WebFiori/framework/commit/d77357f5b220f66e34334703c83c86c66b7fb9ae))
+* **dev:** release 3.0.0-Beta.25 ([e94ea85](https://github.com/WebFiori/framework/commit/e94ea85e94e941bf670954873e06723bf26dc5f3))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* Fix target Branch ([a419a3e](https://github.com/WebFiori/framework/commit/a419a3e29bb416be328b19f1489788d51ebbcd4e))
+* Libraries Bump Up ([d79a44a](https://github.com/WebFiori/framework/commit/d79a44a7d2c3e062974031081c3f5dbc24812b56))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release 3.0.0-Beta.20 ([ddcf6b0](https://github.com/WebFiori/framework/commit/ddcf6b03010a0f011112bfa789f446b1949daaae))
+* release 3.0.0-Beta.20 ([c4fc053](https://github.com/WebFiori/framework/commit/c4fc053d45a5c8b24e963a625d0954c33af2c884))
+* release 3.0.0-Beta.20 ([6e72830](https://github.com/WebFiori/framework/commit/6e72830f0ec1f6943d84ee3266632d5c62e02832))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* release v3.0.0-Beta.20 ([630f512](https://github.com/WebFiori/framework/commit/630f512c6a036e111a7b146cacbd7d72dfe0da06))
+* release v3.0.0-Beta.21 ([56f0bdc](https://github.com/WebFiori/framework/commit/56f0bdc7917faa97c8d3e4b73076d91213b85366))
+* release v3.0.0-Beta.22 ([4271914](https://github.com/WebFiori/framework/commit/4271914182564a0c917a185010933f7f76b86f5d))
+* release v3.0.0-Beta.23 ([3f74229](https://github.com/WebFiori/framework/commit/3f74229b2b38449330763f40883a6ce383eda504))
+* release v3.0.0-Beta.24 ([2870002](https://github.com/WebFiori/framework/commit/28700026d50339f1b3be07f21d801594d682b7b4))
+* release v3.0.0-Beta.25 ([7609472](https://github.com/WebFiori/framework/commit/76094722f597e943bf159b57368af5650d265af0))
+* release v3.0.0-Beta.25 ([77671f5](https://github.com/WebFiori/framework/commit/77671f5735902aef7fd25cf1dd6c5fa601ca7eca))
+* release v3.0.0-Beta.26 ([ab08f87](https://github.com/WebFiori/framework/commit/ab08f870c53cc8fc47212b579e077e8eef2fdc65))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Import ([4cd7cf3](https://github.com/WebFiori/framework/commit/4cd7cf313f836231c76b4533bacf7f7283589052))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Skeleton of Database Migrations Writer ([3b53f8e](https://github.com/WebFiori/framework/commit/3b53f8e33a89667df0479d146e7902db3b8d4d90))
+* Update composer.json ([08c60a8](https://github.com/WebFiori/framework/commit/08c60a878c48198f61b2149127113c359ca5f635))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated .gitattributes ([3b2334c](https://github.com/WebFiori/framework/commit/3b2334ce9b29d1be3b71049b3caf759a00c84724))
+* Updated App Version ([099d631](https://github.com/WebFiori/framework/commit/099d631e4c65de00fea4242cb8b5814dc6047113))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([6936c18](https://github.com/WebFiori/framework/commit/6936c18cd1895df3ba101aaacfe4e599d39d59c4))
+* Updated Dependencies ([e48f333](https://github.com/WebFiori/framework/commit/e48f3336d8c0b1910e18b8baa5ea40be28c9e50d))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies + Framework Version ([9f5dd93](https://github.com/WebFiori/framework/commit/9f5dd9374ebf818605b6ae4acff3d5b95237d1ff))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([ddaeca0](https://github.com/WebFiori/framework/commit/ddaeca02a7a192b49340fa15b4bd88b3d1f2dfab))
+* Updated Framework Version ([36b0f55](https://github.com/WebFiori/framework/commit/36b0f5514329db80bae9102094e402e240987260))
+* Updated Framework Version ([834782c](https://github.com/WebFiori/framework/commit/834782c8fd1ae846ffbaa72b8ee76e5fc7796f56))
+* Updated Framework Version ([7f84cf6](https://github.com/WebFiori/framework/commit/7f84cf65da991a63daccc2cb0896b78b98d578c5))
+* Updated Framework Version ([361e5d5](https://github.com/WebFiori/framework/commit/361e5d545a274043efceab4087d4db7769990d60))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Libraries Versions ([248d1da](https://github.com/WebFiori/framework/commit/248d1dab9efffd44ac5728d005dde58470d2fef1))
+* Updated Library Version ([c681042](https://github.com/WebFiori/framework/commit/c68104267d7abf876d2be93b3f7f2ea96697ca17))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.25](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.24...v3.0.0-Beta.25) (2025-02-04)
+
+
+### Miscellaneous Chores
+
+* release v3.0.0-Beta.25 ([77671f5](https://github.com/WebFiori/framework/commit/77671f5735902aef7fd25cf1dd6c5fa601ca7eca))
+* Updated App Version ([099d631](https://github.com/WebFiori/framework/commit/099d631e4c65de00fea4242cb8b5814dc6047113))
+
+## [3.0.0-Beta.24](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.23...v3.0.0-Beta.24) (2025-01-29)
+
+
+### Bug Fixes
+
+* Show Exception on Initialization ([2341841](https://github.com/WebFiori/framework/commit/2341841f9718beb99330f8529e01600d61acfecf))
+
+
+### Miscellaneous Chores
+
+* release v3.0.0-Beta.24 ([2870002](https://github.com/WebFiori/framework/commit/28700026d50339f1b3be07f21d801594d682b7b4))
+* Update composer.json ([08c60a8](https://github.com/WebFiori/framework/commit/08c60a878c48198f61b2149127113c359ca5f635))
+* Updated Framework Version ([36b0f55](https://github.com/WebFiori/framework/commit/36b0f5514329db80bae9102094e402e240987260))
+
+## [3.0.0-Beta.23](https://github.com/WebFiori/framework/compare/v3.0.1-Beta.22...v3.0.0-Beta.23) (2025-01-07)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* Buffer Theme Components as They Might be HTML ([d803352](https://github.com/WebFiori/framework/commit/d803352bb1d97436f242807879e665c24015845a))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Fix Assignment Issue ([34a522f](https://github.com/WebFiori/framework/commit/34a522ff53e8ad7bc8bc1287bc0c7595a4d7e254))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Registering Middleware ([6cc7ce1](https://github.com/WebFiori/framework/commit/6cc7ce1e39bf0aebfe0eb5ff91360e4c4ed04f05))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.0.0-Beta.17 ([a3e5983](https://github.com/WebFiori/framework/commit/a3e598305d75f2fa87d6148520d88f4235a53253))
+* **dev:** release 3.0.0-Beta.18 ([a994097](https://github.com/WebFiori/framework/commit/a994097c021b5575cab7f077f8d730736c3c1bbe))
+* **dev:** release 3.0.0-Beta.19 ([e917eb5](https://github.com/WebFiori/framework/commit/e917eb5ccd18ee8223a73ec9a2ac499a281f5764))
+* **dev:** release 3.0.0-Beta.19 ([5497825](https://github.com/WebFiori/framework/commit/549782509f23be3a90375debe0319b16e549a3aa))
+* **dev:** release 3.0.0-Beta.20 ([7afdb92](https://github.com/WebFiori/framework/commit/7afdb92618bdbc6e11adb29b001d9a9d0a8f4809))
+* **dev:** release 3.0.0-Beta.21 ([eef1270](https://github.com/WebFiori/framework/commit/eef1270f51089065f04e7f8843d9944d368a774e))
+* **dev:** release 3.0.0-Beta.22 ([0e428f0](https://github.com/WebFiori/framework/commit/0e428f03ef510cb9c33bbf940c92b12ff702c8dd))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* Fix target Branch ([a419a3e](https://github.com/WebFiori/framework/commit/a419a3e29bb416be328b19f1489788d51ebbcd4e))
+* Libraries Bump Up ([d79a44a](https://github.com/WebFiori/framework/commit/d79a44a7d2c3e062974031081c3f5dbc24812b56))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release 3.0.0-Beta.20 ([ddcf6b0](https://github.com/WebFiori/framework/commit/ddcf6b03010a0f011112bfa789f446b1949daaae))
+* release 3.0.0-Beta.20 ([c4fc053](https://github.com/WebFiori/framework/commit/c4fc053d45a5c8b24e963a625d0954c33af2c884))
+* release 3.0.0-Beta.20 ([6e72830](https://github.com/WebFiori/framework/commit/6e72830f0ec1f6943d84ee3266632d5c62e02832))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* release v3.0.0-Beta.20 ([630f512](https://github.com/WebFiori/framework/commit/630f512c6a036e111a7b146cacbd7d72dfe0da06))
+* release v3.0.0-Beta.21 ([56f0bdc](https://github.com/WebFiori/framework/commit/56f0bdc7917faa97c8d3e4b73076d91213b85366))
+* release v3.0.0-Beta.22 ([4271914](https://github.com/WebFiori/framework/commit/4271914182564a0c917a185010933f7f76b86f5d))
+* release v3.0.0-Beta.23 ([3f74229](https://github.com/WebFiori/framework/commit/3f74229b2b38449330763f40883a6ce383eda504))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Import ([4cd7cf3](https://github.com/WebFiori/framework/commit/4cd7cf313f836231c76b4533bacf7f7283589052))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated .gitattributes ([3b2334c](https://github.com/WebFiori/framework/commit/3b2334ce9b29d1be3b71049b3caf759a00c84724))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([e48f333](https://github.com/WebFiori/framework/commit/e48f3336d8c0b1910e18b8baa5ea40be28c9e50d))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies + Framework Version ([9f5dd93](https://github.com/WebFiori/framework/commit/9f5dd9374ebf818605b6ae4acff3d5b95237d1ff))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([834782c](https://github.com/WebFiori/framework/commit/834782c8fd1ae846ffbaa72b8ee76e5fc7796f56))
+* Updated Framework Version ([7f84cf6](https://github.com/WebFiori/framework/commit/7f84cf65da991a63daccc2cb0896b78b98d578c5))
+* Updated Framework Version ([361e5d5](https://github.com/WebFiori/framework/commit/361e5d545a274043efceab4087d4db7769990d60))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Libraries Versions ([248d1da](https://github.com/WebFiori/framework/commit/248d1dab9efffd44ac5728d005dde58470d2fef1))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.22](https://github.com/WebFiori/framework/compare/v3.0.1-Beta.21...v3.0.0-Beta.22) (2024-12-24)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Fix Assignment Issue ([34a522f](https://github.com/WebFiori/framework/commit/34a522ff53e8ad7bc8bc1287bc0c7595a4d7e254))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Registering Middleware ([6cc7ce1](https://github.com/WebFiori/framework/commit/6cc7ce1e39bf0aebfe0eb5ff91360e4c4ed04f05))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.0.0-Beta.17 ([a3e5983](https://github.com/WebFiori/framework/commit/a3e598305d75f2fa87d6148520d88f4235a53253))
+* **dev:** release 3.0.0-Beta.18 ([a994097](https://github.com/WebFiori/framework/commit/a994097c021b5575cab7f077f8d730736c3c1bbe))
+* **dev:** release 3.0.0-Beta.19 ([e917eb5](https://github.com/WebFiori/framework/commit/e917eb5ccd18ee8223a73ec9a2ac499a281f5764))
+* **dev:** release 3.0.0-Beta.19 ([5497825](https://github.com/WebFiori/framework/commit/549782509f23be3a90375debe0319b16e549a3aa))
+* **dev:** release 3.0.0-Beta.20 ([7afdb92](https://github.com/WebFiori/framework/commit/7afdb92618bdbc6e11adb29b001d9a9d0a8f4809))
+* **dev:** release 3.0.0-Beta.21 ([eef1270](https://github.com/WebFiori/framework/commit/eef1270f51089065f04e7f8843d9944d368a774e))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* Fix target Branch ([a419a3e](https://github.com/WebFiori/framework/commit/a419a3e29bb416be328b19f1489788d51ebbcd4e))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release 3.0.0-Beta.20 ([ddcf6b0](https://github.com/WebFiori/framework/commit/ddcf6b03010a0f011112bfa789f446b1949daaae))
+* release 3.0.0-Beta.20 ([c4fc053](https://github.com/WebFiori/framework/commit/c4fc053d45a5c8b24e963a625d0954c33af2c884))
+* release 3.0.0-Beta.20 ([6e72830](https://github.com/WebFiori/framework/commit/6e72830f0ec1f6943d84ee3266632d5c62e02832))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* release v3.0.0-Beta.20 ([630f512](https://github.com/WebFiori/framework/commit/630f512c6a036e111a7b146cacbd7d72dfe0da06))
+* release v3.0.0-Beta.21 ([56f0bdc](https://github.com/WebFiori/framework/commit/56f0bdc7917faa97c8d3e4b73076d91213b85366))
+* release v3.0.0-Beta.22 ([4271914](https://github.com/WebFiori/framework/commit/4271914182564a0c917a185010933f7f76b86f5d))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated .gitattributes ([3b2334c](https://github.com/WebFiori/framework/commit/3b2334ce9b29d1be3b71049b3caf759a00c84724))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([e48f333](https://github.com/WebFiori/framework/commit/e48f3336d8c0b1910e18b8baa5ea40be28c9e50d))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies + Framework Version ([9f5dd93](https://github.com/WebFiori/framework/commit/9f5dd9374ebf818605b6ae4acff3d5b95237d1ff))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([7f84cf6](https://github.com/WebFiori/framework/commit/7f84cf65da991a63daccc2cb0896b78b98d578c5))
+* Updated Framework Version ([361e5d5](https://github.com/WebFiori/framework/commit/361e5d545a274043efceab4087d4db7769990d60))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Libraries Versions ([248d1da](https://github.com/WebFiori/framework/commit/248d1dab9efffd44ac5728d005dde58470d2fef1))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.21](https://github.com/WebFiori/framework/compare/v3.0.1-Beta.20...v3.0.0-Beta.21) (2024-12-24)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Fix Assignment Issue ([34a522f](https://github.com/WebFiori/framework/commit/34a522ff53e8ad7bc8bc1287bc0c7595a4d7e254))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Registering Middleware ([6cc7ce1](https://github.com/WebFiori/framework/commit/6cc7ce1e39bf0aebfe0eb5ff91360e4c4ed04f05))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.0.0-Beta.17 ([a3e5983](https://github.com/WebFiori/framework/commit/a3e598305d75f2fa87d6148520d88f4235a53253))
+* **dev:** release 3.0.0-Beta.18 ([a994097](https://github.com/WebFiori/framework/commit/a994097c021b5575cab7f077f8d730736c3c1bbe))
+* **dev:** release 3.0.0-Beta.19 ([e917eb5](https://github.com/WebFiori/framework/commit/e917eb5ccd18ee8223a73ec9a2ac499a281f5764))
+* **dev:** release 3.0.0-Beta.19 ([5497825](https://github.com/WebFiori/framework/commit/549782509f23be3a90375debe0319b16e549a3aa))
+* **dev:** release 3.0.0-Beta.20 ([7afdb92](https://github.com/WebFiori/framework/commit/7afdb92618bdbc6e11adb29b001d9a9d0a8f4809))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* Fix target Branch ([a419a3e](https://github.com/WebFiori/framework/commit/a419a3e29bb416be328b19f1489788d51ebbcd4e))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release 3.0.0-Beta.20 ([ddcf6b0](https://github.com/WebFiori/framework/commit/ddcf6b03010a0f011112bfa789f446b1949daaae))
+* release 3.0.0-Beta.20 ([c4fc053](https://github.com/WebFiori/framework/commit/c4fc053d45a5c8b24e963a625d0954c33af2c884))
+* release 3.0.0-Beta.20 ([6e72830](https://github.com/WebFiori/framework/commit/6e72830f0ec1f6943d84ee3266632d5c62e02832))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* release v3.0.0-Beta.20 ([630f512](https://github.com/WebFiori/framework/commit/630f512c6a036e111a7b146cacbd7d72dfe0da06))
+* release v3.0.0-Beta.21 ([56f0bdc](https://github.com/WebFiori/framework/commit/56f0bdc7917faa97c8d3e4b73076d91213b85366))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated .gitattributes ([3b2334c](https://github.com/WebFiori/framework/commit/3b2334ce9b29d1be3b71049b3caf759a00c84724))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([e48f333](https://github.com/WebFiori/framework/commit/e48f3336d8c0b1910e18b8baa5ea40be28c9e50d))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([7f84cf6](https://github.com/WebFiori/framework/commit/7f84cf65da991a63daccc2cb0896b78b98d578c5))
+* Updated Framework Version ([361e5d5](https://github.com/WebFiori/framework/commit/361e5d545a274043efceab4087d4db7769990d60))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Libraries Versions ([248d1da](https://github.com/WebFiori/framework/commit/248d1dab9efffd44ac5728d005dde58470d2fef1))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.20](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.19...v3.0.0-Beta.20) (2024-12-16)
+
+
+### Miscellaneous Chores
+
+* Fix target Branch ([a419a3e](https://github.com/WebFiori/framework/commit/a419a3e29bb416be328b19f1489788d51ebbcd4e))
+* release 3.0.0-Beta.20 ([ddcf6b0](https://github.com/WebFiori/framework/commit/ddcf6b03010a0f011112bfa789f446b1949daaae))
+* release 3.0.0-Beta.20 ([c4fc053](https://github.com/WebFiori/framework/commit/c4fc053d45a5c8b24e963a625d0954c33af2c884))
+* release 3.0.0-Beta.20 ([6e72830](https://github.com/WebFiori/framework/commit/6e72830f0ec1f6943d84ee3266632d5c62e02832))
+* release v3.0.0-Beta.20 ([630f512](https://github.com/WebFiori/framework/commit/630f512c6a036e111a7b146cacbd7d72dfe0da06))
+* Updated Framework Version ([361e5d5](https://github.com/WebFiori/framework/commit/361e5d545a274043efceab4087d4db7769990d60))
+
+## [3.0.0-Beta.19](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.19...v3.0.0-Beta.19) (2024-12-09)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Fix Assignment Issue ([34a522f](https://github.com/WebFiori/framework/commit/34a522ff53e8ad7bc8bc1287bc0c7595a4d7e254))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Registering Middleware ([6cc7ce1](https://github.com/WebFiori/framework/commit/6cc7ce1e39bf0aebfe0eb5ff91360e4c4ed04f05))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.0.0-Beta.17 ([a3e5983](https://github.com/WebFiori/framework/commit/a3e598305d75f2fa87d6148520d88f4235a53253))
+* **dev:** release 3.0.0-Beta.18 ([a994097](https://github.com/WebFiori/framework/commit/a994097c021b5575cab7f077f8d730736c3c1bbe))
+* **dev:** release 3.0.0-Beta.19 ([5497825](https://github.com/WebFiori/framework/commit/549782509f23be3a90375debe0319b16e549a3aa))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([e48f333](https://github.com/WebFiori/framework/commit/e48f3336d8c0b1910e18b8baa5ea40be28c9e50d))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.19](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.18...v3.0.0-Beta.19) (2024-12-09)
+
+
+### Miscellaneous Chores
+
+* release v3.0.0-Beta.19 ([e8d5314](https://github.com/WebFiori/framework/commit/e8d531433f6afada6684050013b4169b3d8d547b))
+* Updated Dependencies ([8284dc6](https://github.com/WebFiori/framework/commit/8284dc655e8e92aafc3fb6a1bd88861254da0fe1))
+* Updated Version Number ([280f418](https://github.com/WebFiori/framework/commit/280f418df38a2eb019b6f2a5d0c1b8b8d00133d3))
+
+## [3.0.0-Beta.18](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.17...v3.0.0-Beta.18) (2024-12-04)
+
+
+### Bug Fixes
+
+* Correction to File Path ([df3eacf](https://github.com/WebFiori/framework/commit/df3eacfb150a43020794545f51ee1379256a46fc))
+* Fix to Undefined Constant ([d605a5b](https://github.com/WebFiori/framework/commit/d605a5be85cd8623a2114b8c0756372c82bd7c9b))
+
+
+### Miscellaneous Chores
+
+* release v3.0.0-Beta.18 ([5a588eb](https://github.com/WebFiori/framework/commit/5a588eb52815889a8409a07a30c4e6f0defe3269))
+* Updated Framework Version ([783f4be](https://github.com/WebFiori/framework/commit/783f4be57869ae93eab8c0b49fe2ede5cc7fbba8))
+* Updated Release Please Config ([1a8b4e5](https://github.com/WebFiori/framework/commit/1a8b4e55f9a5496aac47e30228613d8a24068914))
+
+## [3.0.0-Beta.17](https://github.com/WebFiori/framework/compare/v3.1.0-Beta.14...v3.0.0-Beta.17) (2024-12-03)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([60aa746](https://github.com/WebFiori/framework/commit/60aa746bf39ccf7cbdda5bd9c24a6ed408d2732c))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* **dev:** release 3.1.0-Beta.14 ([ba5a5e3](https://github.com/WebFiori/framework/commit/ba5a5e30b4033d3f2486cbab578545aadbed67b0))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* release v3.0.0-Beta.17 ([3c0c639](https://github.com/WebFiori/framework/commit/3c0c639a72f9dd08bec7a150e33af2bb18e9728a))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Update Libraries Versions ([46f4d56](https://github.com/WebFiori/framework/commit/46f4d56aa8bc911393ed80d4e57368472dbcdd24))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependences ([a160f0f](https://github.com/WebFiori/framework/commit/a160f0fcc7cb0b2570c9487090b2bcc3e0ad658e))
+* Updated Dependencies ([97bb7a2](https://github.com/WebFiori/framework/commit/97bb7a220a9c8a81fd70a4c2d80d891e4f4c7eb2))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([0403027](https://github.com/WebFiori/framework/commit/0403027fc02bfbba13dd1c899ae073f43c925cd8))
+* Updated Framework Version ([f7c0f7f](https://github.com/WebFiori/framework/commit/f7c0f7f0dad2900d988b3866c2a035b0b1c10e7b))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.1.0-Beta.14](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.14...v3.1.0-Beta.14) (2024-11-27)
+
+
+### Features
+
+* Added Ability to Enable or Disable Cache ([434fd72](https://github.com/WebFiori/framework/commit/434fd726657d7e4967681933bd718d60f68f2a76))
+
+
+### Miscellaneous Chores
+
+* Updated Dependencies Versions ([07252d0](https://github.com/WebFiori/framework/commit/07252d09f40af27cc04494ea7081a41fe0fe2ede))
+* Updated Framework Version ([d44cedc](https://github.com/WebFiori/framework/commit/d44cedc29ce9097403bdb263c279812f78d7581b))
+
+## [3.0.0-Beta.14](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.14...v3.0.0-Beta.14) (2024-11-21)
+
+
+### Features
+
+* Added a Method to Load Multiple Files ([89d0363](https://github.com/WebFiori/framework/commit/89d0363bb81a32032e938da71a19ec959c48e2bf))
+* Added a Way to Handle Configuration Errors ([76f1539](https://github.com/WebFiori/framework/commit/76f153933680c4ae4d7b067e8fca95273412ab2d))
+* Added Additional Logging Methods to Tasks Manager ([afc9b46](https://github.com/WebFiori/framework/commit/afc9b4697b58dbeb0cb7df26e9303fc1e720ecce))
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Added Support for Loading Non-PSR-4 Compliant Classes ([a9772b4](https://github.com/WebFiori/framework/commit/a9772b49fc94b1a524e4555a18135461e1ef88ac))
+* Added Support for Setting Env Vars Using `putenv()` ([2895d6f](https://github.com/WebFiori/framework/commit/2895d6fd7df6060ebae227867dba719864b3578a))
+* Added Support for Writing Unit Test Classes for APIs ([baefa85](https://github.com/WebFiori/framework/commit/baefa855b76a7f42fb2ca0323888d0bc7d7d1f96))
+* **autoloader:** Added a Method to Check Validity of Namespace ([e749a3a](https://github.com/WebFiori/framework/commit/e749a3aafa0d6c1a11da7d486cb68ad8b048b4b7))
+* Automation of Writing Unit Test Cases for APIs ([5bab349](https://github.com/WebFiori/framework/commit/5bab349082328e668f13c5192dd5555b99201fa9))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Bug Fixes
+
+* Add Missing Returns ([9dcd9bf](https://github.com/WebFiori/framework/commit/9dcd9bf2670116a514169abcfdd5af72d4b12d11))
+* Added Check for Empty File Path ([b046fdf](https://github.com/WebFiori/framework/commit/b046fdf98768a63d882d102c1d20cc01b4f8a288))
+* Added Handling Code for Session Serialization Errors ([a2c7955](https://github.com/WebFiori/framework/commit/a2c7955888483c4eb8e446c1b5bd8794331a174a))
+* Added Missing Namespace ([069364a](https://github.com/WebFiori/framework/commit/069364a4566dc15f917ae0469fb8548ae5411771))
+* **autoload:** Add File Name an NS ([eb4d5b9](https://github.com/WebFiori/framework/commit/eb4d5b93f6ea4dc12e5809a6fde63c9f2d4fa928))
+* **autoload:** Check NS with Path ([a3d4c6e](https://github.com/WebFiori/framework/commit/a3d4c6e2e52eae4f6c421f533b7316b8562b1bf8))
+* **cli:** Rename of Class `CommandArgument` to `Argument` ([7f67a0f](https://github.com/WebFiori/framework/commit/7f67a0f61886159261c4749955d34a4187e76cbc))
+* **config:** Fix to JSON Configuration Style ([4dda36c](https://github.com/WebFiori/framework/commit/4dda36c14c8f8a77479bebb24b7b504e4bf02817))
+* Fix to `RunSQLQueryCommand` ([87dc2e3](https://github.com/WebFiori/framework/commit/87dc2e3a2dbf9f25dc81db2e9af9123aef198d4c))
+* Fix to a Bug in Creating Test Case ([0e4b8e5](https://github.com/WebFiori/framework/commit/0e4b8e5ff0c307e45bd5fb3a2acbfacc82f9d373))
+* Fix to Bug in Loading Themes ([ce67490](https://github.com/WebFiori/framework/commit/ce674903b6358824c61360a2ae335b8399c38309))
+* Fix to Create CLI Command ([82c7a88](https://github.com/WebFiori/framework/commit/82c7a888a0a5140d1381b9855ecf2762eb52659b))
+* Fix to Initial Namespace ([6e0e08a](https://github.com/WebFiori/framework/commit/6e0e08ace2b63c2cb959a236342014962c6a3b01))
+* Fix to Line Numbers in Exception Logging ([781a233](https://github.com/WebFiori/framework/commit/781a233a9e0bdb95c4d1a40b96358d608e07de0e))
+* Fix to Reading Extra Connection Props ([a6c5b92](https://github.com/WebFiori/framework/commit/a6c5b9269ac6f7a354f944f0bbc9557f6a73dd1f))
+* Fix to Running SQL Query from File ([0c8bb61](https://github.com/WebFiori/framework/commit/0c8bb613dbdec50c06f80ee0e4d9850602d8a71b))
+* Fix to Setting Middleware Name ([3a02a60](https://github.com/WebFiori/framework/commit/3a02a60d0ed3a2decf0059ce889325fc02f64893))
+* Fix to Uninitialized Variable ([905c3c7](https://github.com/WebFiori/framework/commit/905c3c7b8232a8f1ee6171fa309c2489b1bdd141))
+* Made `init` Static ([e04233a](https://github.com/WebFiori/framework/commit/e04233a0b4b65b903d92029dcb80ec4814dd5a08))
+* Remove Unused Import ([ed43960](https://github.com/WebFiori/framework/commit/ed43960b90052084b7a95a9ac182619af1244a3f))
+* **themes:** Fix to Problems in Loading Theme ([7a331ff](https://github.com/WebFiori/framework/commit/7a331ff9484fe13fcbd7a7c653321b3e9d233fba))
+* **ui:** Fix to Bug In Web Page Initialization ([8645c2a](https://github.com/WebFiori/framework/commit/8645c2a024276c70a96b0ebc3b83480649bd09d7))
+* **ui:** Fix to Load Language After Page Initialization ([38b0843](https://github.com/WebFiori/framework/commit/38b084385251e8b5bfdae2c8ade3ab0219bba046))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Cleanup ([0d5f798](https://github.com/WebFiori/framework/commit/0d5f7983426f7d766e79e6932ec47cf9ac7853dd))
+* Code Quality Improvements ([80c7853](https://github.com/WebFiori/framework/commit/80c7853f737b16e605e11fd9bcf56f1ecc24223a))
+* Code Quality Improvments ([f8e9ed9](https://github.com/WebFiori/framework/commit/f8e9ed98f1ac4b1c86cceff30a92ffd6107a05d2))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* **dev:** release 3.0.0-Beta.14 ([8c3dd76](https://github.com/WebFiori/framework/commit/8c3dd7651f604414c5e5ccfd8567d907545d5513))
+* Fix Imports ([7386f92](https://github.com/WebFiori/framework/commit/7386f9242351673588eaefe6c0de02c7e467f62a))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* **release-please:** Added Additional Sections ([40dcfa4](https://github.com/WebFiori/framework/commit/40dcfa4bad0f8b42a34e0541ef558cd78f37b2ce))
+* Remove Redeclaration ([f41549d](https://github.com/WebFiori/framework/commit/f41549da7a7570ec9984a53f16abf863a716e55d))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Run CS Fixer ([ca8e690](https://github.com/WebFiori/framework/commit/ca8e690d7e8dcc737d4fe125ea828ec4ef146035))
+* Update composer.json ([819c26d](https://github.com/WebFiori/framework/commit/819c26d8fd7f23a057a76fa923b62d0a2281721d))
+* Updated .gitattribute ([63ba6d8](https://github.com/WebFiori/framework/commit/63ba6d890b82280d87d002f8c3fcfee1493ea2ff))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated CI Config ([a7175a4](https://github.com/WebFiori/framework/commit/a7175a4442cb6d5d4031d03cf228fc43439b504b))
+* Updated Composer Config ([cf26913](https://github.com/WebFiori/framework/commit/cf2691382d6d883f2f06b25e789b9a30524758cd))
+* Updated Core Framework Libraries ([9220fa4](https://github.com/WebFiori/framework/commit/9220fa4c77c668793962afc427495adcd6c8ca55))
+* Updated Core Libraries ([c21a48f](https://github.com/WebFiori/framework/commit/c21a48f6586068b4cab3c223465e4b3c60849752))
+* Updated Core Libraries ([fda39a9](https://github.com/WebFiori/framework/commit/fda39a9168b6e8cebda1408e0de4f0f3815845f5))
+* Updated Core Libraries ([4aa9670](https://github.com/WebFiori/framework/commit/4aa96707feabd9518a788d4393442b0287f0a375))
+* Updated Core Libraries Versions ([dcb1a15](https://github.com/WebFiori/framework/commit/dcb1a15882cac069cb7439101b8e096018c1cfc1))
+* Updated Core Library Version ([db4d223](https://github.com/WebFiori/framework/commit/db4d223a4cb2b8bc40a45fa69e4d67b358d1f29a))
+* Updated Dependencies ([aef4319](https://github.com/WebFiori/framework/commit/aef4319b1dd10cd4f208e943e638b4b364b04cd0))
+* Updated Dependencies Version ([0d3ead5](https://github.com/WebFiori/framework/commit/0d3ead5cad177efd50e4b285222fcdbaf8beab66))
+* Updated Errors Handling Library ([5cf44a9](https://github.com/WebFiori/framework/commit/5cf44a9b5ecae3ac5ed3888c18c33e5415055703))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
+* Updated Framework Version ([fb91c15](https://github.com/WebFiori/framework/commit/fb91c15587fb006f096b90a2f2b01e5b9ffb7c47))
+* Updated Framework Version ([a817e8f](https://github.com/WebFiori/framework/commit/a817e8feec235cec29e6c20e00732a25d1c80534))
+* Updated Framework Version ([1672faf](https://github.com/WebFiori/framework/commit/1672faf3f12ef217915d5ea62d6fae9e01c9db28))
+* Updated Framework Version ([8b013ae](https://github.com/WebFiori/framework/commit/8b013ae6d622823f4abd935cbda45d0a718030a1))
+* Updated Framework Version ([7841fd2](https://github.com/WebFiori/framework/commit/7841fd266b92146c4eb800adc1b859c9e022dd25))
+* Updated Framework Version Number ([2f8d814](https://github.com/WebFiori/framework/commit/2f8d814fd477b3c8699ada67c2c6b47a04f29b10))
+* Updated Framework Version Number ([84c7857](https://github.com/WebFiori/framework/commit/84c785722512e04c106412f3612f9ef59758ed40))
+* Updated Framework Version Number ([72cb62d](https://github.com/WebFiori/framework/commit/72cb62d198cef51275c282142edacb13b1a5bcd4))
+* Updated Framework Version Number ([bc89447](https://github.com/WebFiori/framework/commit/bc8944771dd1bd629f65a0df9e4067ea76e141da))
+* Updated Framework Version Number ([a5177bb](https://github.com/WebFiori/framework/commit/a5177bbb1de08d1cf6748ec09ec6d9e53fa20d10))
+* Updated Version Number ([d75c9d0](https://github.com/WebFiori/framework/commit/d75c9d0c9547d2e4ce3edbac839a1f712a9f90a4))
+
+## [3.0.0-Beta.14](https://github.com/WebFiori/framework/compare/v3.0.0-Beta.13...v3.0.0-Beta.14) (2024-11-21)
+
+
+### Features
+
+* Added More Abstraction to Cache Feature ([f51b7b9](https://github.com/WebFiori/framework/commit/f51b7b9d74ef992625a697faa09e71e1c7873f22))
+* Caching (Initial Prototype) ([4a063f3](https://github.com/WebFiori/framework/commit/4a063f3b1070b04bf81adf1ac2ea2089002adf84))
+* Routes Caching ([bbbacff](https://github.com/WebFiori/framework/commit/bbbacffd93174662a6359dc3b6c51a3e1db74dd6))
+
+
+### Miscellaneous Chores
+
+* Added Documentation ([697155f](https://github.com/WebFiori/framework/commit/697155f3904a7fbaac37421bc0b75e31d1fd932a))
+* Added Please Release Manifest and Config ([25970da](https://github.com/WebFiori/framework/commit/25970da8ea98c77a3bf9dd44ae443e8fc5cbb7c6))
+* Added Release Please Config & Manifest ([3b6273c](https://github.com/WebFiori/framework/commit/3b6273c644189f8e52a22b38041921eeab15c7f3))
+* Added Release Please to Workflow ([6da66a3](https://github.com/WebFiori/framework/commit/6da66a3eed187878aaa5557765537e65a9f00853))
+* Change Target Branch for Release Please ([452b9ff](https://github.com/WebFiori/framework/commit/452b9ff4f3919d6416c4ce55316a5b1325482437))
+* Configuration for Please Release ([33caa13](https://github.com/WebFiori/framework/commit/33caa13908911242236e7f22e7ce603f41c63207))
+* release 3.0.0-Beta.14 ([872a0ec](https://github.com/WebFiori/framework/commit/872a0ec0cf732dbe1e2ef3e11d51d79d68b2fb8b))
+* Remove Unused Imports ([53288a9](https://github.com/WebFiori/framework/commit/53288a9063a672bb37da06e6d6e15a492d57b45b))
+* Run CS Fixer ([13f2dde](https://github.com/WebFiori/framework/commit/13f2dde9bc289ea682a045a8c8ab10c7edaf8891))
+* Updated CI Config ([2f14e35](https://github.com/WebFiori/framework/commit/2f14e354fb6d0017197def88049e71e7a3f46f95))
+* Updated Framework Version ([f27a583](https://github.com/WebFiori/framework/commit/f27a583ffa12f4d8aecb5682a2e58c78f191c095))
diff --git a/README.md b/README.md
index f37fc3572..0b18fa9a9 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
-
+
@@ -29,11 +29,11 @@ WebFiori Framework is a mini web development framework which is built using PHP
## Supported PHP Versions
| Build Status |
|:-----------:|
-|
|
-|
|
-|
|
-|
|
-|
|
+|
|
+|
|
+|
|
+|
|
+|
|
## Key Features
diff --git a/app/apis/RoutingTestClass.php b/app/apis/RoutingTestClass.php
index 6b9f7822c..3bbee5eb7 100644
--- a/app/apis/RoutingTestClass.php
+++ b/app/apis/RoutingTestClass.php
@@ -4,14 +4,14 @@
use webfiori\http\Response;
class RoutingTestClass {
- public function __construct(string $param = null, string $second = null) {
+ public function __construct(?string $param = null, ?string $second = null) {
Response::write("I'm inside the class.");
}
public function doSomething() {
Response::clear();
Response::write("I'm doing something.");
}
- public function doSomethingExtra(string $p1 = null, string $p2 = null) {
+ public function doSomethingExtra(?string $p1 = null, ?string $p2 = null) {
Response::clear();
Response::write("I'm doing something.");
}
diff --git a/app/database/migrations/.gitkeep b/app/database/migrations/.gitkeep
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/app/database/migrations/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/database/migrations/commands/.gitkeep b/app/database/migrations/commands/.gitkeep
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/app/database/migrations/commands/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/database/migrations/emptyRunner/XRunner.php b/app/database/migrations/emptyRunner/XRunner.php
new file mode 100644
index 000000000..32894978b
--- /dev/null
+++ b/app/database/migrations/emptyRunner/XRunner.php
@@ -0,0 +1,12 @@
+ 'true'
+ ]);
+ parent::__construct(APP_PATH.'database'.DS.'migrations'.DS.'multi', '\\app\\database\\migrations\\multi', $conn);
+ }
+}
diff --git a/app/database/migrations/multiDownErr/Migration000.php b/app/database/migrations/multiDownErr/Migration000.php
new file mode 100644
index 000000000..9aca3997b
--- /dev/null
+++ b/app/database/migrations/multiDownErr/Migration000.php
@@ -0,0 +1,32 @@
+do();
+ }
+}
diff --git a/app/database/migrations/multiDownErr/Migration002.php b/app/database/migrations/multiDownErr/Migration002.php
new file mode 100644
index 000000000..65f799a2e
--- /dev/null
+++ b/app/database/migrations/multiDownErr/Migration002.php
@@ -0,0 +1,32 @@
+ 'true'
+ ]);
+ parent::__construct(APP_PATH.'database'.DS.'migrations'.DS.'multiDownErr', '\\app\\database\\migrations\\multiDownErr', $conn);
+ }
+}
diff --git a/app/database/migrations/multiErr/Migration000.php b/app/database/migrations/multiErr/Migration000.php
new file mode 100644
index 000000000..47046d8ee
--- /dev/null
+++ b/app/database/migrations/multiErr/Migration000.php
@@ -0,0 +1,32 @@
+x();
+ }
+ /**
+ * Performs the action that will revert back the migration.
+ *
+ * @param Database $schema The database at which the migration will be applied to.
+ */
+ public function down(Database $schema) {
+ $schema->y();
+ }
+}
diff --git a/app/database/migrations/multiErr/Migration001.php b/app/database/migrations/multiErr/Migration001.php
new file mode 100644
index 000000000..100a4aa69
--- /dev/null
+++ b/app/database/migrations/multiErr/Migration001.php
@@ -0,0 +1,32 @@
+ 'true'
+ ]);
+ parent::__construct(APP_PATH.'database'.DS.'migrations'.DS.'multiErr', '\\app\\database\\migrations\\multiErr', $conn);
+ }
+}
diff --git a/app/database/migrations/noConn/Migration000.php b/app/database/migrations/noConn/Migration000.php
new file mode 100644
index 000000000..4c08a292a
--- /dev/null
+++ b/app/database/migrations/noConn/Migration000.php
@@ -0,0 +1,32 @@
+x();
+ }
+ /**
+ * Performs the action that will revert back the migration.
+ *
+ * @param Database $schema The database at which the migration will be applied to.
+ */
+ public function down(Database $schema) {
+ $schema->y();
+ }
+}
diff --git a/app/database/migrations/noConn/XRunner.php b/app/database/migrations/noConn/XRunner.php
new file mode 100644
index 000000000..786bacd29
--- /dev/null
+++ b/app/database/migrations/noConn/XRunner.php
@@ -0,0 +1,12 @@
+
-
-
-
-
-
-
- ./webfiori/framework/Access.php
- ./webfiori/framework/DB.php
- ./webfiori/framework/Language.php
-
- ./webfiori/framework/config/JsonDriver.php
- ./webfiori/framework/config/ClassDriver.php
-
- ./webfiori/framework/Privilege.php
- ./webfiori/framework/PrivilegesGroup.php
- ./webfiori/framework/User.php
- ./webfiori/framework/Theme.php
-
- ./webfiori/framework/ui/BeforeRenderCallback.php
- ./webfiori/framework/ui/WebPage.php
-
- ./webfiori/framework/scheduler/BaseTask.php
- ./webfiori/framework/scheduler/AbstractTask.php
- ./webfiori/framework/scheduler/TasksManager.php
- ./webfiori/framework/scheduler/TaskArgument.php
-
- ./webfiori/framework/router/RouterUri.php
- ./webfiori/framework/router/Router.php
-
- ./webfiori/framework/session/Session.php
- ./webfiori/framework/session/SessionsManager.php
- ./webfiori/framework/session/DefaultSessionStorage.php
- ./webfiori/framework/session/DatabaseSessionStorage.php
- ./webfiori/framework/session/SessionDB.php
-
- ./webfiori/framework/cli/commands/AddCommand.php
- ./webfiori/framework/cli/commands/CreateCommand.php
- ./webfiori/framework/cli/commands/ListRoutesCommand.php
- ./webfiori/framework/cli/commands/ListThemesCommand.php
- ./webfiori/framework/cli/commands/RunSQLQueryCommand.php
- ./webfiori/framework/cli/commands/SchedulerCommand.php
- ./webfiori/framework/cli/commands/SettingsCommand.php
- ./webfiori/framework/cli/commands/UpdateSettingsCommand.php
- ./webfiori/framework/cli/commands/UpdateTableCommand.php
- ./webfiori/framework/cli/commands/VersionCommand.php
- ./webfiori/framework/cli/commands/WHelpCommand.php
- ./webfiori/framework/cli/CLITestCase.php
- ./webfiori/framework/cli/CLIUtils.php
-
- ./webfiori/framework/cli/helpers/ClassInfoReader.php
- ./webfiori/framework/cli/helpers/CreateBackgroundTask.php
- ./webfiori/framework/cli/helpers/CreateCLIClassHelper.php
- ./webfiori/framework/cli/helpers/CreateClassHelper.php
- ./webfiori/framework/cli/helpers/CreateDBAccessHelper.php
- ./webfiori/framework/cli/helpers/CreateFullRESTHelper.php
- ./webfiori/framework/cli/helpers/CreateMiddleware.php
- ./webfiori/framework/cli/helpers/CreateTableObj.php
- ./webfiori/framework/cli/helpers/CreateThemeHelper.php
- ./webfiori/framework/cli/helpers/CreateWebService.php
- ./webfiori/framework/cli/helpers/TableObjHelper.php
- ./webfiori/framework/cli/helpers/CreateAPITestCase.php
-
- ./webfiori/framework/writers/CLICommandClassWriter.php
- ./webfiori/framework/writers/ClassWriter.php
- ./webfiori/framework/writers/DBClassWriter.php
- ./webfiori/framework/writers/LangClassWriter.php
- ./webfiori/framework/writers/MiddlewareClassWriter.php
- ./webfiori/framework/writers/SchedulerTaskClassWriter.php
- ./webfiori/framework/writers/TableClassWriter.php
- ./webfiori/framework/writers/ThemeClassWriter.php
- ./webfiori/framework/writers/ThemeComponentWriter.php
- ./webfiori/framework/writers/WebServiceWriter.php
- ./webfiori/framework/writers/APITestCaseWriter.php
-
-
-
-
-
-
-
-
- ./tests/webfiori/framework/test
-
-
- ./tests/webfiori/framework/test/DBTest.php
-
-
- ./tests/webfiori/framework/test/langs
-
-
- ./tests/webfiori/framework/test/router
-
-
- ./tests/webfiori/framework/test/mail
-
-
- ./tests/webfiori/framework/test/writers
-
-
- ./tests/webfiori/framework/test/cli
-
-
- ./tests/webfiori/framework/test/theme
-
-
- ./tests/webfiori/framework/test/scheduler
-
-
- ./tests/webfiori/framework/test/session
-
-
-
-
\ No newline at end of file
diff --git a/release-commit.php b/release-commit.php
new file mode 100644
index 000000000..3cb8f8f04
--- /dev/null
+++ b/release-commit.php
@@ -0,0 +1,9 @@
+ true,
- 'root' => __DIR__,
+ 'root' => $ROOT,
'on-load-failure' => 'do-nothing'
]);
fprintf(STDOUT,'Autoloader Initialized.'."\n");
fprintf(STDOUT,"---------------------------------\n");
fprintf(STDOUT,"Initializing application...\n");
+App::initiate('app', 'public', $ROOT);
App::start();
fprintf(STDOUT,'Done.'."\n");
-fprintf(STDOUT,'Root Directory: \''.ClassLoader::get()->root().'\'.'."\n");
+fprintf(STDOUT,'Autoload Root Directory: \''.ClassLoader::get()->root().'\'.'."\n");
define('TESTS_PATH', ClassLoader::get()->root().$DS.TESTS_DIRECTORY);
fprintf(STDOUT,'Tests Path: '.TESTS_PATH."\n");
fprintf(STDOUT,'App Path: '.APP_PATH."\n");
@@ -95,7 +106,18 @@
App::getConfig()->remove();
JsonDriver::setConfigFileName('super-confx.json');
App::getConfig()->remove();
+ $conn = new ConnectionInfo('mssql',SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $runner = new MigrationsRunner(APP_PATH, '', $conn);
+ try {
+ $runner->dropMigrationsTable();
+ } catch (\Exception $exc) {
+
+ }
+
});
fprintf(STDOUT, "Registering shutdown function completed.\n");
fprintf(STDOUT,"---------------------------------\n");
fprintf(STDOUT,"Starting to run tests...\n");
+fprintf(STDOUT,"---------------------------------\n");
\ No newline at end of file
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
new file mode 100644
index 000000000..ca225d2ad
--- /dev/null
+++ b/tests/phpunit.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+ ../webfiori/framework/Access.php
+ ../webfiori/framework/DB.php
+ ../webfiori/framework/Language.php
+
+ ../webfiori/framework/config/JsonDriver.php
+ ../webfiori/framework/config/ClassDriver.php
+
+ ../webfiori/framework/Privilege.php
+ ../webfiori/framework/PrivilegesGroup.php
+ ../webfiori/framework/User.php
+ ../webfiori/framework/Theme.php
+
+ ../webfiori/framework/ui/BeforeRenderCallback.php
+ ../webfiori/framework/ui/WebPage.php
+
+ ../webfiori/framework/scheduler/BaseTask.php
+ ../webfiori/framework/scheduler/AbstractTask.php
+ ../webfiori/framework/scheduler/TasksManager.php
+ ../webfiori/framework/scheduler/TaskArgument.php
+
+ ../webfiori/framework/router/RouterUri.php
+ ../webfiori/framework/router/Router.php
+
+ ../webfiori/framework/middleware/AbstractMiddleware.php
+ ../webfiori/framework/middleware/MiddlewareManager.php
+ ../webfiori/framework/middleware/CacheMiddleware.php
+ ../webfiori/framework/middleware/StartSessionMiddleware.php
+
+ ../webfiori/framework/session/Session.php
+ ../webfiori/framework/session/SessionsManager.php
+ ../webfiori/framework/session/DefaultSessionStorage.php
+ ../webfiori/framework/session/DatabaseSessionStorage.php
+ ../webfiori/framework/session/SessionDB.php
+
+ ../webfiori/framework/cli/commands/AddCommand.php
+ ../webfiori/framework/cli/commands/CreateCommand.php
+ ../webfiori/framework/cli/commands/ListRoutesCommand.php
+ ../webfiori/framework/cli/commands/ListThemesCommand.php
+ ../webfiori/framework/cli/commands/RunSQLQueryCommand.php
+ ../webfiori/framework/cli/commands/SchedulerCommand.php
+ ../webfiori/framework/cli/commands/SettingsCommand.php
+ ../webfiori/framework/cli/commands/UpdateSettingsCommand.php
+ ../webfiori/framework/cli/commands/UpdateTableCommand.php
+ ../webfiori/framework/cli/commands/VersionCommand.php
+ ../webfiori/framework/cli/commands/WHelpCommand.php
+ ../webfiori/framework/cli/commands/RunMigrationsCommand.php
+ ../webfiori/framework/cli/CLITestCase.php
+ ../webfiori/framework/cli/CLIUtils.php
+
+ ../webfiori/framework/cli/helpers/ClassInfoReader.php
+ ../webfiori/framework/cli/helpers/CreateBackgroundTask.php
+ ../webfiori/framework/cli/helpers/CreateCLIClassHelper.php
+ ../webfiori/framework/cli/helpers/CreateClassHelper.php
+ ../webfiori/framework/cli/helpers/CreateDBAccessHelper.php
+ ../webfiori/framework/cli/helpers/CreateFullRESTHelper.php
+ ../webfiori/framework/cli/helpers/CreateMiddleware.php
+ ../webfiori/framework/cli/helpers/CreateTableObj.php
+ ../webfiori/framework/cli/helpers/CreateThemeHelper.php
+ ../webfiori/framework/cli/helpers/CreateWebService.php
+ ../webfiori/framework/cli/helpers/TableObjHelper.php
+ ../webfiori/framework/cli/helpers/CreateAPITestCase.php
+ ../webfiori/framework/cli/helpers/CreateMigration.php
+
+
+ ../webfiori/framework/writers/CLICommandClassWriter.php
+ ../webfiori/framework/writers/ClassWriter.php
+ ../webfiori/framework/writers/DBClassWriter.php
+ ../webfiori/framework/writers/LangClassWriter.php
+ ../webfiori/framework/writers/MiddlewareClassWriter.php
+ ../webfiori/framework/writers/SchedulerTaskClassWriter.php
+ ../webfiori/framework/writers/TableClassWriter.php
+ ../webfiori/framework/writers/ThemeClassWriter.php
+ ../webfiori/framework/writers/ThemeComponentWriter.php
+ ../webfiori/framework/writers/WebServiceWriter.php
+ ../webfiori/framework/writers/APITestCaseWriter.php
+ ../webfiori/framework/writers/DatabaseMigrationWriter.php
+
+
+
+
+
+
+
+
+ ./webfiori/framework/test
+
+
+ ./webfiori/framework/test/DBTest.php
+
+
+ ./webfiori/framework/test/langs
+
+
+ ./webfiori/framework/test/router
+
+
+ ./webfiori/framework/test/mail
+
+
+ ./webfiori/framework/test/writers
+
+
+ ./webfiori/framework/test/cli
+
+
+ ./webfiori/framework/test/theme
+
+
+ ./webfiori/framework/test/scheduler
+
+
+ ./webfiori/framework/test/session
+
+
+
\ No newline at end of file
diff --git a/tests/webfiori/framework/test/PageTest.php b/tests/webfiori/framework/test/PageTest.php
index f499416a4..161aaf89c 100644
--- a/tests/webfiori/framework/test/PageTest.php
+++ b/tests/webfiori/framework/test/PageTest.php
@@ -320,7 +320,7 @@ public function testDefaults00() {
$this->assertEquals('ltr',$page->getWritingDir());
$this->assertNotNull($page->getTranslation());
$this->assertEquals('https://127.0.0.1/',$page->getCanonical());
- $this->assertEquals('http://127.0.0.1',$page->getBase());
+ $this->assertEquals('https://127.0.0.1',$page->getBase());
}
/**
* @test
diff --git a/tests/webfiori/framework/test/cli/AddCommandTest.php b/tests/webfiori/framework/test/cli/AddCommandTest.php
index 946eca911..50bd9b198 100644
--- a/tests/webfiori/framework/test/cli/AddCommandTest.php
+++ b/tests/webfiori/framework/test/cli/AddCommandTest.php
@@ -191,7 +191,7 @@ public function testAddLang00() {
"Success: Language added. Also, a class for the language is created at \"".APP_DIR."\langs\" for that language.\n"
], $runner->getOutput());
$this->assertTrue(class_exists('\\app\\langs\\LangFK'));
- $this->removeClass('\\app\\langs\\LanguageFK');
+ $this->removeClass('\\app\\langs\\LangFK');
Controller::getDriver()->initialize();
}
/**
diff --git a/tests/webfiori/framework/test/cli/CreateCLICommandTest.php b/tests/webfiori/framework/test/cli/CreateCLICommandTest.php
index 2aa0e0988..3c85f7386 100644
--- a/tests/webfiori/framework/test/cli/CreateCLICommandTest.php
+++ b/tests/webfiori/framework/test/cli/CreateCLICommandTest.php
@@ -3,10 +3,11 @@
use webfiori\cli\CLICommand;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
/**
* @author Ibrahim
*/
-class CreateCLICommandTest extends CreateTestCase {
+class CreateCLICommandTest extends CLITestCase {
/**
* @test
*/
@@ -39,7 +40,8 @@ public function testCreateCommand00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\commands'\n",
"Enter a name for the command:\n",
diff --git a/tests/webfiori/framework/test/cli/CreateCommandTest.php b/tests/webfiori/framework/test/cli/CreateCommandTest.php
index 3a1ee73af..7262db33a 100644
--- a/tests/webfiori/framework/test/cli/CreateCommandTest.php
+++ b/tests/webfiori/framework/test/cli/CreateCommandTest.php
@@ -2,19 +2,20 @@
namespace webfiori\framework\test\cli;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
/**
* Description of TestCreateCommand
*
* @author Ibrahim
*/
-class CreateCommandTest extends CreateTestCase {
+class CreateCommandTest extends CLITestCase {
/**
* @test
*/
public function testCreate00() {
$runner = App::getRunner();
$runner->setInputs([
- '10',
+ '11',
]);
$runner->setArgsVector([
'webfiori',
@@ -33,7 +34,8 @@ public function testCreate00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
], $runner->getOutput());
}
/**
@@ -61,7 +63,8 @@ public function testCreate01() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
], $runner->getOutput());
}
}
diff --git a/tests/webfiori/framework/test/cli/CreateDBAccessTest.php b/tests/webfiori/framework/test/cli/CreateDBAccessTest.php
index 1939052eb..fc90595a0 100644
--- a/tests/webfiori/framework/test/cli/CreateDBAccessTest.php
+++ b/tests/webfiori/framework/test/cli/CreateDBAccessTest.php
@@ -3,13 +3,14 @@
use webfiori\database\ConnectionInfo;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
/**
* Description of CreateDBAccessTest
*
* @author Ibrahim
*/
-class CreateDBAccessTest extends CreateTestCase {
+class CreateDBAccessTest extends CLITestCase {
/**
* @test
*/
@@ -41,7 +42,7 @@ public function test00() {
"Entity class name:\n",
"Entity namespace: Enter = 'app\\entity'\n",
"Would you like to have update methods for every single column?(y/N)\n",
- "Info: New class was created at \"app\database\".\n"
+ "Info: New class was created at \"". ROOT_PATH.DS."app".DS."database\".\n"
], $runner->getOutput());
$clazz = '\\app\\database\\EmployeeOperationsDB';
$this->assertTrue(class_exists($clazz));
@@ -76,7 +77,7 @@ public function test01() {
"Entity class name:\n",
"Entity namespace: Enter = 'app\\entity'\n",
"Would you like to have update methods for every single column?(y/N)\n",
- "Info: New class was created at \"app\database\\empl\".\n"
+ "Info: New class was created at \"". ROOT_PATH.DS."app".DS."database".DS."empl\".\n"
], $runner->getOutput());
$clazz = '\\app\\database\\empl\\EmployeeSDB';
$this->assertTrue(class_exists($clazz));
@@ -119,7 +120,7 @@ public function test02() {
"Entity class name:\n",
"Entity namespace: Enter = 'app\\entity'\n",
"Would you like to have update methods for every single column?(y/N)\n",
- "Info: New class was created at \"app\database\".\n"
+ "Info: New class was created at \"". ROOT_PATH.DS."app".DS."database\".\n"
], $runner->getOutput());
$clazz = '\\app\\database\\Position2xDB';
$this->assertTrue(class_exists($clazz));
diff --git a/tests/webfiori/framework/test/cli/CreateEntityTest.php b/tests/webfiori/framework/test/cli/CreateEntityTest.php
index 3bad134dd..b04c372d0 100644
--- a/tests/webfiori/framework/test/cli/CreateEntityTest.php
+++ b/tests/webfiori/framework/test/cli/CreateEntityTest.php
@@ -3,8 +3,9 @@
use app\database\TestTable;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
-class CreateEntityTest extends CreateTestCase {
+class CreateEntityTest extends CLITestCase {
/**
* @test
*/
@@ -89,7 +90,8 @@ public function testCreateEntity01() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"We need from you to give us entity class information.\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\\entity'\n",
diff --git a/tests/webfiori/framework/test/cli/CreateMiddlewareTest.php b/tests/webfiori/framework/test/cli/CreateMiddlewareTest.php
index 635f00871..d0bca45f3 100644
--- a/tests/webfiori/framework/test/cli/CreateMiddlewareTest.php
+++ b/tests/webfiori/framework/test/cli/CreateMiddlewareTest.php
@@ -2,6 +2,7 @@
namespace webfiori\framework\test\cli;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
use webfiori\framework\middleware\AbstractMiddleware;
/**
@@ -9,7 +10,7 @@
*
* @author Ibrahim
*/
-class CreateMiddlewareTest extends CreateTestCase {
+class CreateMiddlewareTest extends CLITestCase {
/**
* @test
*/
@@ -42,7 +43,8 @@ public function testCreateMiddleware00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\middleware'\n",
"Enter a name for the middleware:\n",
diff --git a/tests/webfiori/framework/test/cli/CreateMigrationTest.php b/tests/webfiori/framework/test/cli/CreateMigrationTest.php
new file mode 100644
index 000000000..ad35b7baa
--- /dev/null
+++ b/tests/webfiori/framework/test/cli/CreateMigrationTest.php
@@ -0,0 +1,76 @@
+getMName();
+ $clazz = '\\app\\database\\migrations\\'.$name;
+ $order = $this->getOrder();
+
+ $this->assertEquals([
+ 'Info: New class was created at "'. APP_PATH .'database'.DS.'migrations".'."\n",
+ "Info: Migration Name: $name\n",
+ "Info: Migration Order: $order\n"
+ ], $this->executeMultiCommand([
+ CreateCommand::class,
+ '--c' => 'migration',
+ '--defaults'
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ $this->assertTrue(class_exists($clazz));
+ $this->removeClass($clazz);
+ }
+ /**
+ * @test
+ */
+ public function testCreateMigration01() {
+ $name = 'CoolMigration';
+ $defaultName = $this->getMName();
+ $clazz = '\\app\\database\\migrations\\'.$name;
+ $order = $this->getOrder();
+
+ $this->assertEquals([
+ "Migration namespace: Enter = 'app\database\migrations'\n",
+ "Provide an optional name for the class that will have migration logic:\n",
+ "Enter an optional name for the migration: Enter = '$defaultName'\n",
+ "Enter an optional execution order for the migration: Enter = '$order'\n",
+ 'Info: New class was created at "'. APP_PATH .'database'.DS.'migrations".'."\n",
+ "Info: Migration Name: Great One\n",
+ "Info: Migration Order: 11\n"
+ ], $this->executeMultiCommand([
+ CreateCommand::class,
+ '--c' => 'migration',
+ ], [
+ "\n",
+ $name,
+ "Great One",
+ "11"
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ $this->assertTrue(class_exists($clazz));
+ $this->removeClass($clazz);
+ }
+ private function getOrder() {
+ $runner = new MigrationsRunner(APP_PATH.DS.'database'.DS.'migrations', '\\app\\database\\migrations', null);
+ return count($runner->getMigrations());
+ }
+ private function getMName() {
+ $runner = new MigrationsRunner(APP_PATH.DS.'database'.DS.'migrations', '\\app\\database\\migrations', null);
+ $count = count($runner->getMigrations());
+ if ($count < 10) {
+ return 'Migration00'.$count;
+ } else if ($count < 100) {
+ return 'Migration0'.$count;
+ }
+ return 'Migration'.$count;
+ }
+}
\ No newline at end of file
diff --git a/tests/webfiori/framework/test/cli/CreateRESTTest.php b/tests/webfiori/framework/test/cli/CreateRESTTest.php
index df2538ef3..8493fa928 100644
--- a/tests/webfiori/framework/test/cli/CreateRESTTest.php
+++ b/tests/webfiori/framework/test/cli/CreateRESTTest.php
@@ -2,14 +2,16 @@
namespace webfiori\framework\test\cli;
use webfiori\database\ConnectionInfo;
+use webfiori\file\File;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
/**
* Description of CreateRESTTest
*
* @author Ibrahim
*/
-class CreateRESTTest extends CreateTestCase {
+class CreateRESTTest extends CLITestCase {
/**
* @test
*/
@@ -59,7 +61,7 @@ public function test00() {
]);
$this->assertEquals(0, $runner->start());
$this->assertEquals(array_merge([
- "Warning: No database connections found in the class \"app\AppConfig\"!\n",
+ "Warning: No database connections found in application configuration.\n",
"Info: Run the command \"add\" to add connections.\n",
"Database type:\n",
"0: mysql\n",
@@ -127,6 +129,7 @@ public function test00() {
foreach ($apiClazzes as $clazz) {
$this->assertTrue(class_exists($clazz));
+ $this->assertTrue(File::isFileExist(ROOT_PATH.DS. str_replace('\\', DS, $clazz).'.php'));
}
$this->assertTrue(class_exists($tableClazz));
$this->assertTrue(class_exists($entityClazz));
@@ -190,7 +193,7 @@ public function test01() {
]);
$this->assertEquals(0, $runner->start());
$this->assertEquals(array_merge([
- "Warning: No database connections found in the class \"app\AppConfig\"!\n",
+ "Warning: No database connections found in application configuration.\n",
"Info: Run the command \"add\" to add connections.\n","Database type:\n",
"0: mysql\n",
"1: mssql\n",
diff --git a/tests/webfiori/framework/test/cli/CreateTableTest.php b/tests/webfiori/framework/test/cli/CreateTableTest.php
index 357aa3971..44e82fa9f 100644
--- a/tests/webfiori/framework/test/cli/CreateTableTest.php
+++ b/tests/webfiori/framework/test/cli/CreateTableTest.php
@@ -28,13 +28,14 @@ class CreateTableTest extends TestCase {
"8: varbinary\n",
"9: date\n",
"10: datetime2\n",
- "11: time\n",
- "12: money\n",
- "13: bit\n",
- "14: decimal\n",
- "15: float\n",
- "16: boolean\n",
- "17: bool\n",
+ "11: datetime\n",
+ "12: time\n",
+ "13: money\n",
+ "14: bit\n",
+ "15: decimal\n",
+ "16: float\n",
+ "17: boolean\n",
+ "18: bool\n",
];
const MYSQL_COLS_TYPES = [
"Enter a name for column key:\n",
diff --git a/tests/webfiori/framework/test/cli/CreateTaskTest.php b/tests/webfiori/framework/test/cli/CreateTaskTest.php
index 5db238627..b057f3adf 100644
--- a/tests/webfiori/framework/test/cli/CreateTaskTest.php
+++ b/tests/webfiori/framework/test/cli/CreateTaskTest.php
@@ -2,6 +2,7 @@
namespace webfiori\framework\test\cli;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
use webfiori\framework\scheduler\AbstractTask;
/**
@@ -9,7 +10,7 @@
*
* @author Ibrahim
*/
-class CreateTaskTest extends CreateTestCase {
+class CreateTaskTest extends CLITestCase {
/**
* @test
*/
@@ -42,7 +43,8 @@ public function test00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\\tasks'\n",
"Enter a name for the task:\n",
@@ -91,7 +93,8 @@ public function test01() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\\tasks'\n",
"Error: A class in the given namespace which has the given name was found.\n",
@@ -144,7 +147,8 @@ public function test02() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\\tasks'\n",
"Enter a name for the task:\n",
diff --git a/tests/webfiori/framework/test/cli/CreateTestCase.php b/tests/webfiori/framework/test/cli/CreateTestCase.php
deleted file mode 100644
index 21c51da13..000000000
--- a/tests/webfiori/framework/test/cli/CreateTestCase.php
+++ /dev/null
@@ -1,12 +0,0 @@
-remove();
- }
-}
diff --git a/tests/webfiori/framework/test/cli/CreateThemeTest.php b/tests/webfiori/framework/test/cli/CreateThemeTest.php
index c5712d15b..322360fa8 100644
--- a/tests/webfiori/framework/test/cli/CreateThemeTest.php
+++ b/tests/webfiori/framework/test/cli/CreateThemeTest.php
@@ -2,6 +2,7 @@
namespace webfiori\framework\test\cli;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
use webfiori\framework\ThemeLoader;
/**
@@ -9,7 +10,7 @@
*
* @author Ibrahim
*/
-class CreateThemeTest extends CreateTestCase {
+class CreateThemeTest extends CLITestCase {
/**
* @test
*/
@@ -39,7 +40,8 @@ public function testCreateTheme00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'themes'\n",
'Creating theme at "'.ROOT_PATH.DS.'themes'.DS."fiori\"...\n",
diff --git a/tests/webfiori/framework/test/cli/CreateWebServiceTest.php b/tests/webfiori/framework/test/cli/CreateWebServiceTest.php
index 0c625f42d..3c03df069 100644
--- a/tests/webfiori/framework/test/cli/CreateWebServiceTest.php
+++ b/tests/webfiori/framework/test/cli/CreateWebServiceTest.php
@@ -2,16 +2,18 @@
namespace webfiori\framework\test\cli;
use webfiori\framework\App;
+use webfiori\framework\cli\CLITestCase;
use webfiori\http\AbstractWebService;
use webfiori\http\ParamOption;
use webfiori\http\ParamType;
use webfiori\http\RequestMethod;
use webfiori\http\RequestParameter;
+use const ROOT_PATH;
/**
*
* @author Ibrahim
*/
-class CreateWebServiceTest extends CreateTestCase {
+class CreateWebServiceTest extends CLITestCase {
/**
* @test
*/
@@ -52,7 +54,8 @@ public function test00() {
"7: Database access class based on table.\n",
"8: Complete REST backend (Database table, entity, database access and web services).\n",
"9: Web service test case.\n",
- "10: Quit. <--\n",
+ "10: Database migration.\n",
+ "11: Quit. <--\n",
"Enter a name for the new class:\n",
"Enter an optional namespace for the class: Enter = 'app\apis'\n",
"Enter a name for the new web service:\n",
@@ -63,9 +66,10 @@ public function test00() {
"2: GET <--\n",
"3: HEAD\n",
"4: OPTIONS\n",
- "5: POST\n",
- "6: PUT\n",
- "7: TRACE\n",
+ "5: PATCH\n",
+ "6: POST\n",
+ "7: PUT\n",
+ "8: TRACE\n",
"Would you like to add another request method?(y/N)\n",
"Would you like to add request parameters to the service?(y/N)\n",
"Enter a name for the request parameter:\n",
@@ -123,7 +127,7 @@ public function test01() {
'Service\'s Desc',
'',
'y',
- '5',
+ '6',
'n',
'y',
'a-number',
@@ -151,9 +155,10 @@ public function test01() {
"2: GET <--\n",
"3: HEAD\n",
"4: OPTIONS\n",
- "5: POST\n",
- "6: PUT\n",
- "7: TRACE\n",
+ "5: PATCH\n",
+ "6: POST\n",
+ "7: PUT\n",
+ "8: TRACE\n",
"Would you like to add another request method?(y/N)\n",
"Request method:\n",
"0: CONNECT\n",
@@ -161,9 +166,10 @@ public function test01() {
"2: GET <--\n",
"3: HEAD\n",
"4: OPTIONS\n",
- "5: POST\n",
- "6: PUT\n",
- "7: TRACE\n",
+ "5: PATCH\n",
+ "6: POST\n",
+ "7: PUT\n",
+ "8: TRACE\n",
"Would you like to add another request method?(y/N)\n",
"Would you like to add request parameters to the service?(y/N)\n",
"Enter a name for the request parameter:\n",
@@ -220,7 +226,7 @@ public function test02() {
'Service\'s Desc',
'',
'y',
- '5',
+ '6',
'n',
'y',
'a-number',
@@ -248,9 +254,10 @@ public function test02() {
"2: GET <--\n",
"3: HEAD\n",
"4: OPTIONS\n",
- "5: POST\n",
- "6: PUT\n",
- "7: TRACE\n",
+ "5: PATCH\n",
+ "6: POST\n",
+ "7: PUT\n",
+ "8: TRACE\n",
"Would you like to add another request method?(y/N)\n",
"Request method:\n",
"0: CONNECT\n",
@@ -258,9 +265,10 @@ public function test02() {
"2: GET <--\n",
"3: HEAD\n",
"4: OPTIONS\n",
- "5: POST\n",
- "6: PUT\n",
- "7: TRACE\n",
+ "5: PATCH\n",
+ "6: POST\n",
+ "7: PUT\n",
+ "8: TRACE\n",
"Would you like to add another request method?(y/N)\n",
"Would you like to add request parameters to the service?(y/N)\n",
"Enter a name for the request parameter:\n",
diff --git a/tests/webfiori/framework/test/cli/DBClassWritterTest.php b/tests/webfiori/framework/test/cli/DBClassWritterTest.php
index 02aec1a4b..a7a7e7817 100644
--- a/tests/webfiori/framework/test/cli/DBClassWritterTest.php
+++ b/tests/webfiori/framework/test/cli/DBClassWritterTest.php
@@ -4,6 +4,7 @@
use tables\EmployeeInfoTable;
use tables\PositionInfoTable;
use tables\UserInfoTable;
+use webfiori\framework\cli\CLITestCase;
use webfiori\framework\writers\DBClassWriter;
/**
@@ -11,7 +12,7 @@
*
* @author Ibrahim
*/
-class DBClassWritterTest extends CreateTestCase {
+class DBClassWritterTest extends CLITestCase {
/**
* @test
*/
diff --git a/tests/webfiori/framework/test/cli/HelpCommandTest.php b/tests/webfiori/framework/test/cli/HelpCommandTest.php
new file mode 100644
index 000000000..7ae239616
--- /dev/null
+++ b/tests/webfiori/framework/test/cli/HelpCommandTest.php
@@ -0,0 +1,37 @@
+assertEquals([
+ "WebFiori Framework (c) Version ". WF_VERSION." ".WF_VERSION_TYPE."\n\n\n",
+ "Usage:\n",
+ " command [arg1 arg2=\"val\" arg3...]\n\n",
+ "Global Arguments:\n",
+ " --ansi:[Optional] Force the use of ANSI output.\n",
+ "Available Commands:\n",
+ " help: Display CLI Help. To display help for specific command, use the argument \"--command-name\" with this command.\n",
+ " v: Display framework version info.\n",
+ " show-settings: Display application configuration.\n",
+ " scheduler: Run tasks scheduler.\n",
+ " create: Creates a system entity (middleware, web service, background process ...).\n",
+ " add: Add a database connection or SMTP account.\n",
+ " list-routes: List all created routes and which resource they point to.\n",
+ " list-themes: List all registered themes.\n",
+ " run-query: Execute SQL query on specific database.\n",
+ " update-settings: Update application settings which are stored in specific configuration driver.\n",
+ " update-table: Update a database table.\n",
+ " migrations: Execute database migrations.\n",
+ ], $this->executeMultiCommand([
+ 'help',
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+}
diff --git a/tests/webfiori/framework/test/cli/RunMigrationsCommantTest.php b/tests/webfiori/framework/test/cli/RunMigrationsCommantTest.php
new file mode 100644
index 000000000..90a518d79
--- /dev/null
+++ b/tests/webfiori/framework/test/cli/RunMigrationsCommantTest.php
@@ -0,0 +1,809 @@
+assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: No migrations found in the namespace '\app\database\migrations'.\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--connection' => 'ABC'
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations01() {
+ App::getConfig()->removeAllDBConnections();
+ $clazz = $this->createMigration();
+ $this->assertTrue(class_exists($clazz));
+
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--connection' => 'ABC'
+ ]);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Info: No connections were found in application configuration.\n",
+ ], $output);
+
+ $this->assertEquals(-1, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations02() {
+ $conn = new ConnectionInfo('mysql', 'root', '123456', 'testing_db');
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration();
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--connection' => 'ABC'
+ ]);
+ $this->removeClass($clazz);
+ App::getConfig()->removeAllDBConnections();
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Error: No connection was found which has the name 'ABC'.\n",
+ ], $output);
+
+ $this->assertEquals(-1, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations03() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, 'x123456', SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('PO', 'MyPOMigration');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ ], [
+ '7',
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Error: Invalid answer.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Error: Unable to connect to database: 18456 - [Microsoft][ODBC Driver ".ODBC_VERSION." for SQL Server][SQL Server]Login failed for user 'sa'.\n",
+ ], $output);
+ $this->assertEquals(-1, $this->getExitCode());
+
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations04() {
+ $this->assertEquals([
+ "Error: The argument --runner has invalid value: Class \"\app\database\migrations\" does not exist.\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations',
+ ]));
+ $this->assertEquals(-1, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations05() {
+ $this->assertEquals([
+ "Error: The argument --runner has invalid value: Exception: \"Call to private webfiori\\framework\App::__construct() from scope webfiori\\framework\cli\commands\RunMigrationsCommand\".\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\webfiori\\framework\\App',
+ ]));
+ $this->assertEquals(-1, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations06() {
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\\emptyRunner' for migrations...\n",
+ "Info: No migrations found in the namespace '\app\database\migrations\\emptyRunner'.\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\emptyRunner\XRunner',
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations07() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, 'testing_dbx', SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('Cool', 'CoolOne');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ ], [
+ '7',
+ ''
+ ]);
+ $this->removeClass($clazz);
+ App::getConfig()->removeAllDBConnections();
+ $err = ODBC_VERSION == 17 ? "Error: Unable to connect to database: 4060 - [Microsoft][ODBC Driver ".ODBC_VERSION." for SQL Server][SQL Server]Cannot open database \"testing_dbx\" requested by the login. The login failed.\n"
+ : "Error: Unable to connect to database: 18456 - [Microsoft][ODBC Driver ".ODBC_VERSION." for SQL Server][SQL Server]Login failed for user 'sa'.\n";
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Error: Invalid answer.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ $err,
+ ], $output);
+ $this->assertEquals(-1, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations08() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $this->removeMigTable($conn);
+ $clazz = $this->createMigration('Super', 'SuperMigration');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ ], [
+ '7',
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Error: Invalid answer.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Starting to execute migrations...\n",
+ "Error: Failed to execute migration due to following:\n",
+ "208 - [Microsoft][ODBC Driver ".ODBC_VERSION." for SQL Server][SQL Server]Invalid object name 'migrations'. (Line 361)\n",
+ "Warning: Execution stopped.\n",
+ "Info: No migrations were executed.\n"
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations09() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration();
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--ini'
+ ], [
+ '7',
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeMigTable($conn);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Error: Invalid answer.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Starting to execute migrations...\n",
+ "Success: Migration 'Migration000' applied successfuly.\n",
+ "Info: Number of applied migrations: 1\n",
+ "Names of applied migrations:\n",
+ "- Migration000\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations10() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('Cool One', 'CLSOne');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--ini',
+ '--connection' => 'default-conn'
+ ], [
+
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeClass($clazz);
+ $this->removeMigTable($conn);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Starting to execute migrations...\n",
+ "Success: Migration 'Cool One' applied successfuly.\n",
+ "Info: Number of applied migrations: 1\n",
+ "Names of applied migrations:\n",
+ "- Cool One\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations11() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('Cool One', 'ColOne');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ini',
+ ], [
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeClass($clazz);
+ $this->removeMigTable($conn);
+ $this->assertEquals([
+ "Info: Using default namespace for migrations.\n",
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Starting to execute migrations...\n",
+ "Success: Migration 'Cool One' applied successfuly.\n",
+ "Info: Number of applied migrations: 1\n",
+ "Names of applied migrations:\n",
+ "- Cool One\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations12() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, '1234567890@Eux', SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('Oh S', 'OhSuperMg');
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ini',
+ ], [
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Info: Using default namespace for migrations.\n",
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Select database connection:\n",
+ "0: default-conn <--\n",
+ "Initializing migrations table...\n",
+ "Error: Unable to create migrations table due to following:\n",
+ "Unable to connect to database: 18456 - [Microsoft][ODBC Driver ".ODBC_VERSION." for SQL Server][SQL Server]Login failed for user 'sa'.\n",
+ ], $output);
+ $this->assertEquals(-1, $this->getExitCode());
+
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations13() {
+ $clazz = $this->createMigration();
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ini',
+ ], [
+ ''
+ ]);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Info: Using default namespace for migrations.\n",
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Info: No connections were found in application configuration.\n",
+ ], $output);
+ $this->assertEquals(-1, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations14() {
+ //$clazz = $this->createMigration();
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\noConn\XRunner',
+ '--ini'
+ ]);
+ //$this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\\noConn' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations\\noConn'.\n",
+ "Info: No connections were found in application configuration.\n",
+ ], $output);
+ $this->assertEquals(-1, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations15() {
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Starting to execute migrations...\n",
+ "Success: Migration 'First One' applied successfuly.\n",
+ "Success: Migration 'Second one' applied successfuly.\n",
+ "Success: Migration 'Third One' applied successfuly.\n",
+ "Info: Number of applied migrations: 3\n",
+ "Names of applied migrations:\n",
+ "- First One\n",
+ "- Second one\n",
+ "- Third One\n"
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\MultiRunner',
+ '--ini'
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ * @depends testRunMigrations15
+ */
+ public function testRunMigrations16() {
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Starting to execute migrations...\n",
+ "Info: No migrations were executed.\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\MultiRunner',
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ $this->removeMigTable();
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations17() {
+ $this->assertEquals([
+ "Error: The argument --runner has invalid value: \"\app\database\migrations\multi\Migration000\" is not an instance of \"MigrationsRunner\".\n",
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\Migration000',
+ ]));
+ $this->assertEquals(-1, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations18() {
+ $this->assertEquals([
+ "Info: Using default namespace for migrations.\n",
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: No migrations found in the namespace '\app\database\migrations'.\n"
+ ], $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRunMigrations19() {
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multiErr\MultiErrRunner',
+ '--ini'
+ ]);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multiErr' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multiErr'.\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Starting to execute migrations...\n",
+ "Success: Migration 'First One' applied successfuly.\n",
+ "Success: Migration 'Second one' applied successfuly.\n",
+ "Error: Failed to execute migration due to following:\n",
+ "Call to undefined method app\database\migrations\multiErr\Migration000::x() (Line 22)\n",
+ "Warning: Execution stopped.\n",
+ "Info: Number of applied migrations: 2\n",
+ "Names of applied migrations:\n",
+ "- First One\n",
+ "- Second one\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ $r = new MultiErrRunner();
+ $this->removeMigTable($r->getConnectionInfo());
+ }
+ /**
+ * @test
+ */
+ public function testRollback00() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+ $clazz = $this->createMigration('ABC', 'ABC');
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--rollback',
+ '--connection' => 'default-conn',
+ '--ini'
+ ], [
+ ''
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeMigTable($conn);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Initializing migrations table...\n",
+ "Success: Migrations table succesfully created.\n",
+ "Rolling back last executed migration...\n",
+ "Info: No migration rolled back.\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRollback01() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+
+ $ns = '\\app\\database\\migrations';
+ $clazz = $this->createAndRunMigration($conn, $ns, 'ABCD Cool', 'ABCCool');
+ App::getConfig()->addOrUpdateDBConnection($conn);
+
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--rollback',
+ '--connection' => 'default-conn',
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeMigTable($conn);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Rolling back last executed migration...\n",
+ "Success: Migration 'ABCD Cool' was successfully rolled back.\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRollback02() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+
+ $ns = '\\app\\database\\migrations';
+ $clazz = $this->createAndRunMigration($conn, $ns, 'ABCD Cool', 'ABCCool');
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--rollback',
+ '--connection' => 'default-conn',
+ ]);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => '\\app\\database\\migrations',
+ '--rollback',
+ '--connection' => 'default-conn',
+ ]);
+ App::getConfig()->removeAllDBConnections();
+ $this->removeMigTable($conn);
+ $this->removeClass($clazz);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations' for migrations...\n",
+ "Info: Found 1 migration(s) in the namespace '\app\database\migrations'.\n",
+ "Rolling back last executed migration...\n",
+ "Info: No migration rolled back.\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ }
+ /**
+ * @test
+ */
+ public function testRollback03() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\MultiRunner',
+ '--ini'
+ ]);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ ]);
+
+ App::getConfig()->removeAllDBConnections();
+
+
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back last executed migration...\n",
+ "Success: Migration 'Third One' was successfully rolled back.\n",
+ ], $output);
+
+ $this->assertEquals(0, $this->getExitCode());
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ ]);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back last executed migration...\n",
+ "Success: Migration 'Second one' was successfully rolled back.\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ ]);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back last executed migration...\n",
+ "Success: Migration 'First One' was successfully rolled back.\n",
+ ], $output);
+ $this->assertEquals(0, $this->getExitCode());
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ ]);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back last executed migration...\n",
+ "Info: No migration rolled back.\n",
+ ], $output);
+ $this->removeMigTable($conn);
+ }
+ /**
+ * @test
+ */
+ public function testRollback04() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\MultiRunner',
+ '--ini'
+ ]);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ '--all'
+ ]);
+
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back migrations...\n",
+ "Success: Migration 'Third One' was successfully rolled back.\n",
+ "Success: Migration 'Second one' was successfully rolled back.\n",
+ "Success: Migration 'First One' was successfully rolled back.\n",
+ ], $output);
+
+ $this->assertEquals(0, $this->getExitCode());
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multi\\MultiRunner',
+ '--rollback',
+ '--all'
+ ]);
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multi' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multi'.\n",
+ "Rolling back migrations...\n",
+ "Info: No migration rolled back.\n",
+ ], $output);
+ $this->removeMigTable($conn);
+ }
+ /**
+ * @test
+ */
+ public function testRollback05() {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ $conn->setName('default-conn');
+
+ App::getConfig()->addOrUpdateDBConnection($conn);
+ $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multiDownErr\MultiErrRunner',
+ '--ini'
+ ]);
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multiDownErr\MultiErrRunner',
+ '--rollback',
+ '--all'
+ ]);
+
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multiDownErr' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multiDownErr'.\n",
+ "Rolling back migrations...\n",
+ "Success: Migration 'Third One' was successfully rolled back.\n",
+ "Error: Failed to execute migration due to following:\n",
+ "Call to undefined method webfiori\database\migration\MigrationsRunner::do() (Line 30)\n",
+ "Warning: Execution stopped.\n",
+ ], $output);
+
+ $this->assertEquals(-1, $this->getExitCode());
+ $output = $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--runner' => '\\app\\database\\migrations\\multiDownErr\MultiErrRunner',
+ '--rollback',
+ '--all'
+ ]);
+
+ $this->assertEquals([
+ "Checking namespace '\app\database\migrations\multiDownErr' for migrations...\n",
+ "Info: Found 3 migration(s) in the namespace '\app\database\migrations\multiDownErr'.\n",
+ "Rolling back migrations...\n",
+ "Error: Failed to execute migration due to following:\n",
+ "Call to undefined method webfiori\database\migration\MigrationsRunner::do() (Line 30)\n",
+ "Warning: Execution stopped.\n",
+ ], $output);
+ $this->removeMigTable($conn);
+ }
+ private function createAndRunMigration(ConnectionInfo $connection, string $ns, ?string $name = null, ?string $className = null) : string {
+ $clazz = $this->createMigration($name, $className);
+ App::getConfig()->addOrUpdateDBConnection($connection);
+ $this->executeMultiCommand([
+ RunMigrationsCommand::class,
+ '--ns' => $ns,
+ '--connection' => $connection->getName(),
+ '--ini'
+ ]);
+ $this->assertTrue(class_exists($clazz));
+ App::getConfig()->removeDBConnection($connection->getName());
+ return $clazz;
+ }
+ private function createMigration(?string $name = null, ?string $className = null) : string {
+ $runner = new MigrationsRunner(APP_PATH.DS.'database'.DS.'migrations'.DS.'commands', '\\app\\database\\migrations\\commands', null);
+ $writer = new DatabaseMigrationWriter($runner);
+ if ($name !== null) {
+ $writer->setMigrationName($name);
+ }
+ if ($className !== null) {
+ $writer->setClassName($className);
+ }
+ $writer->writeClass();
+ return $writer->getName(true);
+ }
+ private function removeMigTable(?ConnectionInfo $conn = null) {
+ if ($conn === null) {
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
+ }
+ $runner = new MigrationsRunner(APP_PATH.DS.'database'.DS.'migrations'.DS.'commands', '\\app\\database\\migrations\\commands', $conn);
+ try{
+ $runner->dropMigrationsTable();
+ } catch (DatabaseException $ex) {
+
+ }
+ }
+}
diff --git a/tests/webfiori/framework/test/cli/SettingsCommandTest.php b/tests/webfiori/framework/test/cli/SettingsCommandTest.php
index d82b38f16..3f8344780 100644
--- a/tests/webfiori/framework/test/cli/SettingsCommandTest.php
+++ b/tests/webfiori/framework/test/cli/SettingsCommandTest.php
@@ -3,12 +3,15 @@
use PHPUnit\Framework\TestCase;
use webfiori\framework\App;
+use webfiori\framework\config\ClassDriver;
class SettingsCommandTest extends TestCase {
/**
* @test
*/
public function test00() {
+ App::setConfigDriver(ClassDriver::class);
+ App::getConfig()->remove();
App::getConfig()->initialize(true);
$runner = App::getRunner();
$runner->setInputs();
diff --git a/tests/webfiori/framework/test/cli/UpdateSettingsCommandTest.php b/tests/webfiori/framework/test/cli/UpdateSettingsCommandTest.php
index 32d3afe2e..340a0e205 100644
--- a/tests/webfiori/framework/test/cli/UpdateSettingsCommandTest.php
+++ b/tests/webfiori/framework/test/cli/UpdateSettingsCommandTest.php
@@ -17,7 +17,9 @@ class UpdateSettingsCommandTest extends TestCase {
* @test
*/
public function test00() {
+ App::getConfig()->remove();
JsonDriver::setConfigFileName('app-config.json');
+ App::getConfig()->initialize(true);
$runner = App::getRunner();
$runner->setArgsVector([
'webfiori',
diff --git a/tests/webfiori/framework/test/config/JsonDriverTest.php b/tests/webfiori/framework/test/config/JsonDriverTest.php
index c85e8ddfd..44476a302 100644
--- a/tests/webfiori/framework/test/config/JsonDriverTest.php
+++ b/tests/webfiori/framework/test/config/JsonDriverTest.php
@@ -38,7 +38,7 @@ public function test00() {
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
],
'CLI_HTTP_HOST' => [
- 'value' => 'example.com',
+ "value" => "127.0.0.1",
'description' => 'Host name that will be used when runing the application as command line utility.'
],
],$driver->getEnvVars());
@@ -147,7 +147,7 @@ public function testAddEnvVar00() {
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
],
"CLI_HTTP_HOST" => [
- "value" => "example.com",
+ "value" => "127.0.0.1",
"description" => "Host name that will be used when runing the application as command line utility."
]
], $driver->getEnvVars());
@@ -169,7 +169,7 @@ public function testAddEnvVar01() {
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
],
"CLI_HTTP_HOST" => [
- "value" => "example.com",
+ "value" => "127.0.0.1",
"description" => "Host name that will be used when runing the application as command line utility."
],
"COOL_OR_NOT" => [
@@ -204,7 +204,7 @@ public function testAddEnvVar02() {
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
],
"CLI_HTTP_HOST" => [
- "value" => "example.com",
+ "value" => "127.0.0.1",
"description" => "Host name that will be used when runing the application as command line utility."
],
"DO_IT" => [
diff --git a/tests/webfiori/framework/test/router/RouterUriTest.php b/tests/webfiori/framework/test/router/RouterUriTest.php
index dd5443d23..2e261fdd2 100644
--- a/tests/webfiori/framework/test/router/RouterUriTest.php
+++ b/tests/webfiori/framework/test/router/RouterUriTest.php
@@ -19,9 +19,9 @@ public function testAddToMiddleware00() {
$uri = new RouterUri('https://www3.programmingacademia.com:80/test', '');
MiddlewareManager::register(new TestMiddleware());
$uri->addMiddleware('global');
- $this->assertEquals(1, $uri->getMiddleware()->size());
+ $this->assertEquals(1, count($uri->getMiddleware()));
$uri->addMiddleware('Super Cool Middleware');
- $this->assertEquals(2, $uri->getMiddleware()->size());
+ $this->assertEquals(2, count($uri->getMiddleware()));
$this->assertFalse($uri->isDynamic());
}
/**
diff --git a/tests/webfiori/framework/test/session/SessionsManagerTest.php b/tests/webfiori/framework/test/session/SessionsManagerTest.php
index 604fde3e5..c48fcd735 100644
--- a/tests/webfiori/framework/test/session/SessionsManagerTest.php
+++ b/tests/webfiori/framework/test/session/SessionsManagerTest.php
@@ -161,8 +161,10 @@ public function testDatabaseSession00() {
*/
public function testDatabaseSession01() {
$this->expectException(DatabaseException::class);
- $this->expectExceptionMessage("208 - [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid object name 'session_data'.");
- $conn = new ConnectionInfo('mssql', 'sa', '1234567890@Eu', 'testing_db', 'localhost');
+ $this->expectExceptionMessage("208 - [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'session_data'.");
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
$conn->setName('sessions-connection');
App::getConfig()->addOrUpdateDBConnection($conn);
SessionsManager::setStorage(new DatabaseSessionStorage());
@@ -174,7 +176,9 @@ public function testDatabaseSession01() {
* @depends testInitSessionsDb
*/
public function testDatabaseSession02() {
- $conn = new ConnectionInfo('mssql', 'sa', '1234567890@Eu', 'testing_db', 'localhost');
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
$conn->setName('sessions-connection');
App::getConfig()->addOrUpdateDBConnection($conn);
SessionsManager::reset();
@@ -281,8 +285,10 @@ public function testDatabaseSession02() {
*/
public function testDropDbTables00() {
$this->expectException(DatabaseException::class);
- $this->expectExceptionMessage("208 - [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid object name 'session_data'.");
- $conn = new ConnectionInfo('mssql', 'sa', '1234567890@Eu', 'testing_db', 'localhost');
+ $this->expectExceptionMessage("208 - [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'session_data'.");
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
$conn->setName('sessions-connection');
App::getConfig()->addOrUpdateDBConnection($conn);
SessionsManager::reset();
@@ -309,7 +315,9 @@ public function testGetSessionIDFromRequest() {
* @depends testDatabaseSession01
*/
public function testInitSessionsDb() {
- $conn = new ConnectionInfo('mssql', 'sa', '1234567890@Eu', 'testing_db', 'localhost');
+ $conn = new ConnectionInfo('mssql', SQL_SERVER_USER, SQL_SERVER_PASS, SQL_SERVER_DB, SQL_SERVER_HOST, 1433, [
+ 'TrustServerCertificate' => 'true'
+ ]);
$conn->setName('sessions-connection');
App::getConfig()->addOrUpdateDBConnection($conn);
SessionsManager::reset();
diff --git a/tests/webfiori/framework/test/writers/DatabaseMigrationWriterTest.php b/tests/webfiori/framework/test/writers/DatabaseMigrationWriterTest.php
new file mode 100644
index 000000000..19f3e0a30
--- /dev/null
+++ b/tests/webfiori/framework/test/writers/DatabaseMigrationWriterTest.php
@@ -0,0 +1,141 @@
+removeClass($clazz);
+ $runner = new MigrationsRunner($path, $ns, null);
+ $writter = new DatabaseMigrationWriter($runner);
+ $this->assertEquals('Migration000', $writter->getName());
+ $this->assertEquals('app\\database\\migrations', $writter->getNamespace());
+ $this->assertEquals('', $writter->getSuffix());
+ $this->assertEquals([
+ "webfiori\database\Database",
+ "webfiori\database\migration\AbstractMigration",
+ ], $writter->getUseStatements());
+ $writter->writeClass();
+
+ $this->assertTrue(class_exists($clazz));
+ $runner = new MigrationsRunner($path, $ns, null);
+ $migrations = $runner->getMigrations();
+ $this->assertEquals(1, count($migrations));
+ $m00 = $migrations[0];
+ $this->assertTrue($m00 instanceof AbstractMigration);
+ $this->assertEquals('Migration000', $m00->getName());
+ $this->assertEquals(0, $m00->getOrder());
+ $this->removeClass($clazz);
+ }
+ /**
+ * @test
+ */
+ public function test01() {
+ $path = APP_PATH.DS.'database'.DS.'migrations';
+ $ns = '\\app\\database\\migrations';
+ $runner = new MigrationsRunner($path, $ns, null);
+ $writter = new DatabaseMigrationWriter($runner);
+ $writter->setClassName('MyMigration');
+ $writter->setMigrationName('A test migration.');
+ $writter->setMigrationOrder(3);
+ $this->assertEquals('MyMigration', $writter->getName());
+ $this->assertEquals('app\\database\\migrations', $writter->getNamespace());
+
+ $writter->writeClass();
+ $clazz = "\\app\\database\\migrations\\MyMigration";
+ $this->assertTrue(class_exists($clazz));
+ $runner = new MigrationsRunner($path, $ns, null);
+ $migrations = $runner->getMigrations();
+ $this->assertEquals(1, count($migrations));
+ $m00 = $migrations[0];
+ $this->assertTrue($m00 instanceof AbstractMigration);
+ $this->assertEquals('A test migration.', $m00->getName());
+ $this->assertEquals(3, $m00->getOrder());
+ $this->removeClass($clazz);
+ }
+ /**
+ * @test
+ */
+ public function test02() {
+ $path = APP_PATH.DS.'database'.DS.'migrations';
+ $ns = '\\app\\database\\migrations';
+ $runner = new MigrationsRunner($path, $ns, null);
+ $writter = new DatabaseMigrationWriter($runner);
+ $this->assertEquals('Migration000', $writter->getName());
+ $writter->writeClass();
+ $clazz = "\\app\\database\\migrations\\Migration000";
+ $this->assertTrue(class_exists($clazz));
+ $runner2 = new MigrationsRunner($path, $ns, null);
+ $migrations = $runner2->getMigrations();
+ $this->assertEquals(1, count($migrations));
+ $m00 = $migrations[0];
+ $this->assertTrue($m00 instanceof AbstractMigration);
+ $this->assertEquals('Migration000', $m00->getName());
+ $this->assertEquals(0, $m00->getOrder());
+
+ $writter2 = new DatabaseMigrationWriter($runner2);
+ $this->assertEquals('Migration001', $writter2->getName());
+ $writter2->writeClass();
+ $clazz2 = "\\app\\database\\migrations\\Migration001";
+ $this->assertTrue(class_exists($clazz));
+ $runner3 = new MigrationsRunner($path, $ns, null);
+ $migrations2 = $runner3->getMigrations();
+ $this->assertEquals(2, count($migrations2));
+ $m01 = $migrations2[1];
+ $this->assertTrue($m00 instanceof AbstractMigration);
+ $this->assertEquals('Migration001', $m01->getName());
+ $this->assertEquals(1, $m01->getOrder());
+ $this->removeClass($clazz);
+ $this->removeClass($clazz2);
+ }
+ /**
+ * @test
+ */
+ public function test03() {
+ $path = APP_PATH.DS.'database'.DS.'migrations';
+ $ns = '\\app\\database\\migrations';
+ for ($x = 0 ; $x < 110 ; $x++) {
+ $runner = new MigrationsRunner($path, $ns, null);
+ $writter = new DatabaseMigrationWriter($runner);
+ if ($x < 10) {
+ $name = 'Migration00'.$x;
+ } else if ($x < 100) {
+ $name = 'Migration0'.$x;
+ } else {
+ $name = 'Migration'.$x;
+ }
+ $this->assertEquals($name, $writter->getName());
+ $writter->writeClass();
+ $clazz = "\\app\\database\\migrations\\".$name;
+ $this->assertTrue(class_exists($clazz));
+ $xRunner = new MigrationsRunner($path, $ns, null);
+
+ $migrations = $xRunner->getMigrations();
+ $this->assertEquals($x + 1, count($migrations));
+ $m = $migrations[$x];
+ $this->assertTrue($m instanceof AbstractMigration);
+ $this->assertEquals($name, $m->getName());
+ $this->assertEquals($x, $m->getOrder());
+ }
+ foreach ($migrations as $m) {
+ $this->removeClass("\\app\\database\\migrations\\".$m->getName());
+ }
+ }
+ private function removeClass($classPath) {
+ $file = new File(ROOT_PATH.$classPath.'.php');
+ $file->remove();
+ }
+}
diff --git a/tests/webfiori/framework/test/writers/MiddlewareWritterTest.php b/tests/webfiori/framework/test/writers/MiddlewareWritterTest.php
index febbbdb0b..ca82771af 100644
--- a/tests/webfiori/framework/test/writers/MiddlewareWritterTest.php
+++ b/tests/webfiori/framework/test/writers/MiddlewareWritterTest.php
@@ -1,14 +1,14 @@
assertEquals('tests\\apis\\WebServiceTest', $w->getName(true));
$this->assertEquals(9, $w->getPhpUnitVersion());
+ $this->assertEquals(ROOT_PATH.DS.'tests'.DS.'apis'.DS.'WebServiceTest.php', $w->getAbsolutePath());
$w->writeClass();
$this->assertTrue(class_exists('\\'.$w->getName(true)));
- $this->removeClass($w->getAbsolutePath());
+ unlink($w->getAbsolutePath());
}
/**
* @test
@@ -31,9 +32,10 @@ public function test01() {
$w->setPath(ROOT_PATH.DS.'tests'.DS.'cool');
$this->assertEquals('tests\\cool\\CoolTest', $w->getName(true));
$w->writeClass();
+ $this->assertEquals(ROOT_PATH.DS.'tests'.DS.'cool'.DS.'CoolTest.php', $w->getAbsolutePath());
$this->assertTrue(file_exists($w->getAbsolutePath()));
require_once $w->getAbsolutePath();
$this->assertTrue(class_exists('\\'.$w->getName(true)));
- $this->removeClass($w->getAbsolutePath());
+ unlink($w->getAbsolutePath());
}
}
diff --git a/webfiori/framework/Access.php b/webfiori/framework/Access.php
index 22789d434..1ab19b434 100644
--- a/webfiori/framework/Access.php
+++ b/webfiori/framework/Access.php
@@ -183,7 +183,7 @@ public static function hasGroup(string $groupId): bool {
*
* @since 1.0
*/
- public static function hasPrivilege(string $id,string $groupId = null): bool {
+ public static function hasPrivilege(string $id, ?string $groupId = null): bool {
return Access::get()->hasPrivilegeHelper($id,$groupId);
}
/**
@@ -204,7 +204,7 @@ public static function hasPrivilege(string $id,string $groupId = null): bool {
*
* @since 1.0
*/
- public static function newGroup(string $groupId, $parentGroupId = null): bool {
+ public static function newGroup(string $groupId, ?string $parentGroupId = null): bool {
return Access::get()->createGroupHelper($groupId,$parentGroupId);
}
/**
@@ -269,7 +269,7 @@ public static function newPrivileges(string $groupId, array $prNamesArr): array
*
* @since 1.0
*/
- public static function privileges(string $groupId = null): array {
+ public static function privileges(?string $groupId = null): array {
return Access::get()->getPrivilegesHelper($groupId);
}
/**
@@ -356,7 +356,7 @@ private function checkID(string $id, PrivilegesGroup $group): bool {
return $bool;
}
- private function createGroupHelper($groupId, $parentGroupID = null): bool {
+ private function createGroupHelper($groupId, ?string $parentGroupID = null): bool {
$trimmedId = trim($groupId);
if ($this->validateId($trimmedId)) {
@@ -585,7 +585,7 @@ private function getPrivilegeHelper1(string $privilegeId, PrivilegesGroup $group
return null;
}
- private function getPrivilegesHelper($groupId = null): array {
+ private function getPrivilegesHelper(?string $groupId = null): array {
$prArr = [];
foreach ($this->userGroups as $group) {
@@ -601,7 +601,7 @@ private function getPrivilegesHelper($groupId = null): array {
* @param array $array
* @param string|null $groupId
*/
- private function getPrivilegesHelper1(PrivilegesGroup $group, array &$array, string $groupId = null) {
+ private function getPrivilegesHelper1(PrivilegesGroup $group, array &$array, ?string $groupId = null) {
if ($groupId === null) {
foreach ($group->privileges() as $pr) {
$array[] = $pr;
@@ -688,7 +688,7 @@ private function hasPrivilegeHelper($privilegeId, $groupId) {
* @param PrivilegesGroup $group
* @return bool
*/
- private function hasPrivilegeHelper1(string $prId, PrivilegesGroup $group, string $groupId = null) : bool {
+ private function hasPrivilegeHelper1(string $prId, PrivilegesGroup $group, ?string $groupId = null) : bool {
if ($groupId === null || $group->getID() != $groupId) {
if ($groupId !== null) {
return $this->isChildGroupHasPrivilege($prId, $groupId, $group);
diff --git a/webfiori/framework/App.php b/webfiori/framework/App.php
index 691ebddda..fac0232ad 100644
--- a/webfiori/framework/App.php
+++ b/webfiori/framework/App.php
@@ -26,10 +26,10 @@
use webfiori\framework\handlers\HTTPErrHandler;
use webfiori\framework\middleware\AbstractMiddleware;
use webfiori\framework\middleware\MiddlewareManager;
+use webfiori\framework\middleware\StartSessionMiddleware;
use webfiori\framework\router\Router;
use webfiori\framework\router\RouterUri;
use webfiori\framework\scheduler\TasksManager;
-use webfiori\framework\session\SessionsManager;
use webfiori\http\Request;
use webfiori\http\Response;
/**
@@ -61,6 +61,10 @@ class App {
*
*/
const STATUS_NONE = 'NONE';
+ /**
+ * A constant that indicates that the status of the class is initiated.
+ */
+ const STATUS_INITIATED = 'INITIATED';
/**
* An instance of autoloader class.
*
@@ -87,7 +91,7 @@ class App {
*
* @var string
*/
- private static $ConfigDriver = 'webfiori\\framework\\config\\ClassDriver';
+ private static $ConfigDriver = 'webfiori\\framework\\config\\JsonDriver';
/**
* A single instance of the class.
*
@@ -105,19 +109,7 @@ class App {
* @since 1.0
*/
private function __construct() {
- $this->checkStdInOut();
- $this->initFrameworkVersionInfo();
$this->checkAppDir();
- /**
- * Change encoding of mb_ functions to UTF-8
- */
- if (function_exists('mb_internal_encoding')) {
- $encoding = 'UTF-8';
- mb_internal_encoding($encoding);
- mb_http_output($encoding);
- mb_regex_encoding($encoding);
- }
- $this->initAutoLoader();
$this->setHandlers();
Controller::get()->updateEnv();
/**
@@ -130,13 +122,11 @@ private function __construct() {
*/
date_default_timezone_set(defined('DATE_TIMEZONE') ? DATE_TIMEZONE : 'Asia/Riyadh');
-
//Initialize CLI
self::getRunner();
$this->initThemesPath();
- $this->checkStandardLibs();
-
+
if (!class_exists(APP_DIR.'\ini\InitPrivileges')) {
Ini::get()->createIniClass('InitPrivileges', 'Initialize user groups and privileges.');
}
@@ -144,8 +134,6 @@ private function __construct() {
//This step must be done before initializing anything.
self::call(APP_DIR.'\ini\InitPrivileges::init');
-
-
$this->initMiddleware();
$this->initRoutes();
$this->initScheduler();
@@ -153,31 +141,23 @@ private function __construct() {
{
register_shutdown_function(function()
{
- SessionsManager::validateStorage();
$uriObj = Router::getRouteUri();
-
if ($uriObj !== null) {
- foreach ($uriObj->getMiddleware() as $mw) {
- $mw->afterSend(Request::get(), Response::get());
+ $mdArr = $uriObj->getMiddleware();
+
+ for ($x = count($mdArr) - 1 ; $x > 0 ; $x--) {
+ $mdArr[$x]->afterSend(Request::get(), Response::get());
}
}
});
- try {
- $sessionsCookiesHeaders = SessionsManager::getCookiesHeaders();
-
- foreach ($sessionsCookiesHeaders as $headerVal) {
- Response::addHeader('set-cookie', $headerVal);
- }
- } catch (Error $exc) {
- }
$uriObj = Router::getRouteUri();
if ($uriObj !== null) {
- $uriObj->getMiddleware()->insertionSort();
+ $mdArr = $uriObj->getMiddleware();
- foreach ($uriObj->getMiddleware() as $mw) {
- $mw->after(Request::get(), Response::get());
+ for ($x = count($mdArr) - 1 ; $x > 0 ; $x--) {
+ $mdArr[$x]->after(Request::get(), Response::get());
}
}
});
@@ -207,7 +187,7 @@ private function __construct() {
*
* @since 1.3.6
*/
- public static function autoRegister(string $folder, callable $regCallback, string $suffix = null, array $constructorParams = [], array $otherParams = []) {
+ public static function autoRegister(string $folder, callable $regCallback, ?string $suffix = null, array $constructorParams = [], array $otherParams = []) {
$dir = APP_PATH.$folder;
if (!File::isDirectory($dir)) {
@@ -264,10 +244,8 @@ public static function getClassLoader(): ClassLoader {
* the constructor of the class is not called. 'INITIALIZING' if the execution
* is happening inside the constructor of the class. 'INITIALIZED' once the
* code in the constructor is executed.
- *
- * @since 1.0
*/
- public static function getClassStatus() {
+ public static function getClassStatus() : string {
return self::$ClassStatus;
}
/**
@@ -294,7 +272,104 @@ public static function getConfig(): ConfigurationDriver {
public static function getConfigDriver() : string {
return self::$ConfigDriver;
}
-
+ private static function getRoot() {
+ //Following lines of code assumes that the class exist on the folder:
+ //\vendor\webfiori\framework\webfiori\framework
+ //Its used to construct the folder at which index file will exist at
+ $DS = DIRECTORY_SEPARATOR;
+ $vendorPath = $DS.'vendor'.$DS.'webfiori'.$DS.'framework'.$DS.'webfiori'.$DS.'framework';
+ $rootPath = substr(__DIR__, 0, strlen(__DIR__) - strlen($vendorPath));
+ return $rootPath;
+ }
+ /**
+ * Handel the request.
+ *
+ * This method should only be called after the application has been initialized.
+ * Its used to handle HTTP requests or start CLI processing.
+ */
+ public static function handle() {
+
+ if (self::$ClassStatus == self::STATUS_NONE) {
+ $publicFolderName = 'public';
+ self::initiate('app', $publicFolderName, self::getRoot().DIRECTORY_SEPARATOR.$publicFolderName);
+ }
+ if (self::$ClassStatus == self::STATUS_INITIATED) {
+ self::start();
+ }
+ if (self::$ClassStatus == self::STATUS_INITIALIZED) {
+ if (App::getRunner()->isCLI() === true) {
+ App::getRunner()->start();
+ } else {
+ //route user request.
+ Router::route(Request::getRequestedURI());
+ Response::send();
+ }
+ }
+ }
+ /**
+ * Initiate main components of the application.
+ *
+ * This method is intended to be called in the index file of the project.
+ * It should be first thing to be called.
+ *
+ * @param string $appFolder The name of the folder at which the application
+ * is created at.
+ *
+ * @param string $publicFolder A string that represent the name of the public
+ * folder such as 'public'.
+ *
+ * @param string $indexDir The directory at which index file exist at.
+ * Usually, its the value of the constant __DIR__.
+ */
+ public static function initiate(string $appFolder = 'app', string $publicFolder = 'public', string $indexDir = __DIR__) {
+ /**
+ * Change encoding of mb_ functions to UTF-8
+ */
+ if (function_exists('mb_internal_encoding')) {
+ $encoding = 'UTF-8';
+ mb_internal_encoding($encoding);
+ mb_http_output($encoding);
+ mb_regex_encoding($encoding);
+ }
+ if (!defined('DS')) {
+ /**
+ * Directory separator.
+ */
+ define('DS', DIRECTORY_SEPARATOR);
+ }
+ if (!defined('ROOT_PATH')) {
+ if ($indexDir == __DIR__) {
+ $indexDir = self::getRoot().DS.$publicFolder;
+ }
+ /**
+ * Path to source folder.
+ */
+ define('ROOT_PATH', substr($indexDir,0, strlen($indexDir) - strlen(DS.$publicFolder)));
+ }
+ if (!defined('APP_DIR')) {
+ /**
+ * Name of application directory.
+ */
+ define('APP_DIR', $appFolder);
+ }
+ if (!defined('APP_PATH')) {
+ /**
+ * Path to application directory.
+ */
+ define('APP_PATH', ROOT_PATH.DIRECTORY_SEPARATOR.APP_DIR.DS);
+ }
+ if (!defined('WF_CORE_PATH')) {
+ /**
+ * Path to WebFiori's core library.
+ */
+ define('WF_CORE_PATH', ROOT_PATH.DS.'vendor'.DS.'webfiori'.DS.'framework'.DS.'webfiori'.DS.'framework');
+ }
+ self::initAutoLoader();
+ self::checkStandardLibs();
+ self::checkStdInOut();
+ self::initFrameworkVersionInfo();
+ self::$ClassStatus = self::STATUS_INITIATED;
+ }
/**
* Returns an instance which represents the class that is used to run the
* terminal.
@@ -347,6 +422,7 @@ public static function getRunner() : Runner {
'\\webfiori\\framework\\cli\\commands\\RunSQLQueryCommand',
'\\webfiori\\framework\\cli\\commands\\UpdateSettingsCommand',
'\\webfiori\\framework\\cli\\commands\\UpdateTableCommand',
+ '\\webfiori\\framework\\cli\\commands\\RunMigrationsCommand',
];
foreach ($commands as $c) {
@@ -380,12 +456,12 @@ public static function setConfigDriver(string $clazz) {
* @since 1.0
*/
public static function start(): App {
- if (self::$ClassStatus == 'NONE') {
+ if (self::$ClassStatus == self::STATUS_NONE || self::$ClassStatus == self::STATUS_INITIATED) {
if (self::$LC === null) {
- self::$ClassStatus = 'INITIALIZING';
+ self::$ClassStatus = self::STATUS_INITIALIZING;
self::$LC = new App();
}
- } else if (self::$ClassStatus == 'INITIALIZING') {
+ } else if (self::$ClassStatus == self::STATUS_INITIALIZING) {
throw new InitializationException('Using the core class while it is not fully initialized.');
}
@@ -423,17 +499,13 @@ private static function call($func) {
} catch (Exception $ex) {
if (self::getRunner()->isCLI()) {
printf("WARNING: ".$ex->getMessage().' at '.$ex->getFile().':'.$ex->getLine()."\n");
+ } else {
+ throw new InitializationException($ex->getMessage(), $ex->getCode(), $ex);
}
}
}
private function checkAppDir() {
- if (!defined('DS')) {
- /**
- * Directory separator.
- */
- define('DS', DIRECTORY_SEPARATOR);
- }
-
+
if (!defined('APP_DIR')) {
/**
* The name of the directory at which the developer will have his own application
@@ -468,7 +540,7 @@ private function checkAppDir() {
* @throws InitializationException
* @since 1.3.5
*/
- private function checkStandardLibs() {
+ private static function checkStandardLibs() {
$standardLibsClasses = [
'webfiori/collections' => 'webfiori\\collections\\Node',
'webfiori/ui' => 'webfiori\\ui\\HTMLNode',
@@ -478,7 +550,8 @@ private function checkStandardLibs() {
'webfiori/file' => 'webfiori\\file\\File',
'webfiori/mailer' => 'webfiori\\email\\SMTPAccount',
'webfiori/cli' => 'webfiori\\cli\\CLICommand',
- 'webfiori/err' => 'webfiori\\error\\ErrorHandlerException'
+ 'webfiori/err' => 'webfiori\\error\\ErrorHandlerException',
+ 'webfiori/cache' => 'webfiori\\cache\\Cache'
];
foreach ($standardLibsClasses as $lib => $class) {
@@ -491,7 +564,7 @@ private function checkStandardLibs() {
/**
* Checks and initialize standard input and output streams.
*/
- private function checkStdInOut() {
+ private static function checkStdInOut() {
/*
* first, check for php streams if they are open or not.
*/
@@ -532,12 +605,13 @@ private function checkStdInOut() {
* @throws FileException
* @throws Exception
*/
- private function initAutoLoader() {
+ private static function initAutoLoader() {
/**
* Initialize autoloader.
*/
if (!class_exists('webfiori\framework\autoload\ClassLoader',false)) {
- require_once WF_CORE_PATH.DIRECTORY_SEPARATOR.'autoload'.DIRECTORY_SEPARATOR.'ClassLoader.php';
+ $autoloader = WF_CORE_PATH.DIRECTORY_SEPARATOR.'autoload'.DIRECTORY_SEPARATOR.'ClassLoader.php';
+ require_once $autoloader;
}
self::$AU = ClassLoader::get();
@@ -547,13 +621,23 @@ private function initAutoLoader() {
}
self::call(APP_DIR.'\ini\InitAutoLoad::init');
}
- private function initFrameworkVersionInfo() {
+ /**
+ * Initialize global constants which has information about framework version.
+ *
+ * The constants which are defined by this method include the following:
+ *
+ * - WF_VERSION: A string such as '3.0.0'.
+ * - WF_VERSION_TYPE: Type of the release such as 'RC', 'Alpha' or 'Stable'.
+ * - WF_RELEASE_DATE: The date at which the specified version was created at.
+ *
+ */
+ public static function initFrameworkVersionInfo() {
/**
* A constant that represents version number of the framework.
*
* @since 2.1
*/
- define('WF_VERSION', '3.0.0-Beta.13');
+ define('WF_VERSION', '3.0.0-Beta.26');
/**
* A constant that tells the type of framework version.
*
@@ -569,7 +653,7 @@ private function initFrameworkVersionInfo() {
*
* @since 2.1
*/
- define('WF_RELEASE_DATE', '2024-10-29');
+ define('WF_RELEASE_DATE', '2025-04-07');
}
/**
@@ -584,6 +668,7 @@ private function initMiddleware() {
if (!class_exists(APP_DIR.'\ini\InitMiddleware')) {
Ini::get()->createIniClass('InitMiddleware', 'Register middleware which are created outside the folder \'[APP_DIR]/middleware\'.');
}
+ MiddlewareManager::register(new StartSessionMiddleware());
self::call(APP_DIR.'\ini\InitMiddleware::init');
}
/**
diff --git a/webfiori/framework/Ini.php b/webfiori/framework/Ini.php
index e92a8b8a7..ef8fb62f7 100644
--- a/webfiori/framework/Ini.php
+++ b/webfiori/framework/Ini.php
@@ -13,6 +13,7 @@
use webfiori\file\exceptions\FileException;
use webfiori\file\File;
use webfiori\framework\config\ClassDriver;
+use webfiori\json\Json;
/**
* A class which is used to create application initialization classes.
*
@@ -25,6 +26,7 @@ class Ini {
private $docEmptyLine;
private $docEnd;
private $docStart;
+ private static $DIR_TO_CREATE;
/**
* An instance of the class.
*
@@ -150,11 +152,16 @@ public static function get(): Ini {
return self::$singleton;
}
public static function mkdir($dir) {
+ self::$DIR_TO_CREATE = $dir;
if (!is_dir($dir)) {
- set_error_handler(function (int $errno, string $errstr)
- {
+ set_error_handler(function (int $errno, string $errstr) {
http_response_code(500);
- die('Unable to create one or more of application directories due to an error: "Code: '.$errno.', Message: '.$errstr.'"');
+ header('content-type:application/json');
+ die('{'
+ . '"message":"Unable to create application directory due to an error: '.$errstr.'",'
+ . '"code":'.$errno.','
+ . '"dir":"'.Json::escapeJSONSpecialChars(self::$DIR_TO_CREATE).'"'
+ . '}');
});
mkdir($dir);
restore_error_handler();
diff --git a/webfiori/framework/Lang.php b/webfiori/framework/Lang.php
index ba693dbfd..f925a616b 100644
--- a/webfiori/framework/Lang.php
+++ b/webfiori/framework/Lang.php
@@ -219,7 +219,7 @@ public function getCode() : string {
* @param string $dir A directory to the language variable (such as 'pages/login/login-label').
* This also can be a string similar to 'pages.login.login-label'.
*
- * @param string $langCode An optional language code. If provided, the
+ * @param string|null $langCode An optional language code. If provided, the
* method will attempt to replace active language with the provided
* one. If not provided, the method
* will attempt to load a translation based on the session or default
@@ -233,7 +233,7 @@ public function getCode() : string {
* @throws MissingLangException
* @since 1.0
*/
- public static function getLabel(string $dir, string $langCode = null) {
+ public static function getLabel(string $dir, ?string $langCode = null) {
if ($langCode === null) {
$session = SessionsManager::getActiveSession();
diff --git a/webfiori/framework/PrivilegesGroup.php b/webfiori/framework/PrivilegesGroup.php
index 81248f9ea..b55d15318 100644
--- a/webfiori/framework/PrivilegesGroup.php
+++ b/webfiori/framework/PrivilegesGroup.php
@@ -311,7 +311,7 @@ public function setName(string $name) : bool {
*
* @since 1.1
*/
- public function setParentGroup(PrivilegesGroup $group = null) : bool {
+ public function setParentGroup(?PrivilegesGroup $group = null) : bool {
if ($group !== null) {
if ($group !== $this && $group->getID() != $this->getID()) {
$this->parentGroup = $group;
diff --git a/webfiori/framework/ThemeLoader.php b/webfiori/framework/ThemeLoader.php
index 7ae18ffd3..f03ca68f3 100644
--- a/webfiori/framework/ThemeLoader.php
+++ b/webfiori/framework/ThemeLoader.php
@@ -175,7 +175,7 @@ public static function resetLoaded() {
*
* @since 1.0
*/
- public static function usingTheme(string $themeName = null) {
+ public static function usingTheme(?string $themeName = null) {
$trimmedName = trim((string)$themeName);
if (strlen($trimmedName) != 0) {
@@ -253,7 +253,9 @@ private static function scanDir($filesInDir, $pathToScan, $dirName) {
if ($fileExt == '.php') {
$cName = str_replace('.php', '', $fileName);
+ ob_start();
$ns = require_once $pathToScan.DS.$fileName;
+ ob_end_clean();
$aNs = gettype($ns) == 'string' ? $ns.'\\' : '\\';
$aCName = $aNs.$cName;
diff --git a/webfiori/framework/User.php b/webfiori/framework/User.php
index 975e54087..be3d9d1de 100644
--- a/webfiori/framework/User.php
+++ b/webfiori/framework/User.php
@@ -453,7 +453,7 @@ public function setLastLogin(string $date) {
*
* @since 1.6
*/
- public function setLastPasswordResetDate(string $date = null) {
+ public function setLastPasswordResetDate(?string $date = null) {
$this->lastPasswordReset = $date;
}
/**
diff --git a/webfiori/framework/autoload/ClassLoader.php b/webfiori/framework/autoload/ClassLoader.php
index 3a55cc044..980e2f049 100644
--- a/webfiori/framework/autoload/ClassLoader.php
+++ b/webfiori/framework/autoload/ClassLoader.php
@@ -318,7 +318,7 @@ public static function getCachePath() : string {
* loaded.
*
*/
- public static function getClassPath(string $className, string $namespace = null, bool $load = false): array {
+ public static function getClassPath(string $className, ?string $namespace = null, bool $load = false): array {
$retVal = [];
if ($load === true) {
@@ -397,7 +397,7 @@ public static function getLoadedClasses(): array {
*
* @throws Exception
*/
- public static function isLoaded(string $class, string $ns = null): bool {
+ public static function isLoaded(string $class, ?string $ns = null): bool {
foreach (self::getLoadedClasses() as $classArr) {
if ($ns !== null) {
if ($class == $classArr[ClassInfo::NAME]
diff --git a/webfiori/framework/cli/CLIUtils.php b/webfiori/framework/cli/CLIUtils.php
index 70604e760..7e4e52f3f 100644
--- a/webfiori/framework/cli/CLIUtils.php
+++ b/webfiori/framework/cli/CLIUtils.php
@@ -33,7 +33,7 @@ public static function getConnectionName(CLICommand $c) {
$dbConnectionsNames = array_keys($dbConnections);
if (count($dbConnectionsNames) == 0) {
- $c->warning('No database connections found in the class "'.APP_DIR.'\\AppConfig"!');
+ $c->warning('No database connections found in application configuration.');
$c->info('Run the command "add" to add connections.');
return null;
@@ -61,10 +61,9 @@ public static function getConnectionName(CLICommand $c) {
*
* @return string A string that represents a valid class name.
*/
- public static function readClassName(CLICommand $c, string $suffix = null, string $prompt = 'Enter class name:', string $errMsg = 'Invalid class name is given.') : string {
+ public static function readClassName(CLICommand $c, ?string $suffix = null, string $prompt = 'Enter class name:', string $errMsg = 'Invalid class name is given.') : string {
do {
- $c->readClassName($prompt, $suffix, $errMsg);
- $className = trim($c->getInput($prompt));
+ $className = $c->readClassName($prompt, $suffix, $errMsg);
if ($suffix !== null) {
$subSuffix = substr($className, strlen($className) - strlen($suffix));
@@ -149,4 +148,29 @@ public static function readTable(CLICommand $c) : Table {
return $tableObj;
}
+ /**
+ * Reads and returns the name of a database connection.
+ *
+ * This method will display a list of all stored connections in the configuration
+ * and returns one of them.
+ *
+ * @return string The name of selected connection. An empty string is returned
+ * if none is selected.
+ */
+ public function getConnection() : string {
+ $dbConnections = array_keys(App::getConfig()->getDBConnections());
+
+ if (count($dbConnections) != 0) {
+ $dbConnections[] = 'None';
+ $conn = $this->select('Select database connecion:', $dbConnections, count($dbConnections) - 1);
+
+ if ($conn != 'None') {
+ return $conn;
+ }
+ } else {
+ $this->warning('No database connections were found.');
+ }
+
+ return '';
+ }
}
diff --git a/webfiori/framework/cli/commands/CreateCommand.php b/webfiori/framework/cli/commands/CreateCommand.php
index 10b5d6795..b1695e8ae 100644
--- a/webfiori/framework/cli/commands/CreateCommand.php
+++ b/webfiori/framework/cli/commands/CreateCommand.php
@@ -20,6 +20,7 @@
use webfiori\framework\cli\helpers\CreateDBAccessHelper;
use webfiori\framework\cli\helpers\CreateFullRESTHelper;
use webfiori\framework\cli\helpers\CreateMiddleware;
+use webfiori\framework\cli\helpers\CreateMigration;
use webfiori\framework\cli\helpers\CreateTableObj;
use webfiori\framework\cli\helpers\CreateThemeHelper;
use webfiori\framework\cli\helpers\CreateWebService;
@@ -115,6 +116,17 @@ public function exec() : int {
if (!$create->readClassInfo()) {
return -1;
}
+ } else if ($answer == 'Database migration.') {
+ $create = new CreateMigration($this);
+ if ($create->isConfigured()) {
+ $create->writeClass();
+ $writer = $create->getWriter();
+ $this->info("Migration Name: ".$writer->getMigrationName());
+ $this->info("Migration Order: ".$writer->getMigrationOrder());
+ return 0;
+ } else {
+ return -1;
+ }
}
return 0;
@@ -131,6 +143,7 @@ private function getWhat() {
$options['db'] = 'Database access class based on table.';
$options['rest'] = 'Complete REST backend (Database table, entity, database access and web services).';
$options['api-test'] = 'Web service test case.';
+ $options['migration'] = 'Database migration.';
$options['q'] = 'Quit.';
$what = $this->getArgValue('--c');
$answer = null;
diff --git a/webfiori/framework/cli/commands/RunMigrationsCommand.php b/webfiori/framework/cli/commands/RunMigrationsCommand.php
new file mode 100644
index 000000000..88b7eea7e
--- /dev/null
+++ b/webfiori/framework/cli/commands/RunMigrationsCommand.php
@@ -0,0 +1,303 @@
+isArgProvided('--ini')) {
+ return 0;
+ }
+ if ($conn === null) {
+ $conn = $this->getDBConnection($runner);
+ }
+ if ($conn !== null) {
+
+ try {
+ $this->println("Initializing migrations table...");
+ $temp = $runner !== null ? $runner : new MigrationsRunner(APP_PATH, '\\'.APP_DIR, $conn);
+
+ $temp->createMigrationsTable();
+ $this->success("Migrations table succesfully created.");
+ } catch (\Throwable $ex) {
+ $this->error('Unable to create migrations table due to following:');
+ $this->println($ex->getMessage());
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+ }
+ private function getNS(?MigrationsRunner $runner = null) {
+ if ($this->isArgProvided('--ns')) {
+ return $this->getArgValue('--ns');
+ } else if ($runner !== null) {
+ return $runner->getMigrationsNamespace();
+ } else {
+ $this->info("Using default namespace for migrations.");
+ return '\\'.APP_DIR.'\\database\\migrations';
+ }
+ }
+
+ /**
+ * Execute the command.
+ *
+ * @return int 0 in case of success. Other value if failed.
+ */
+ public function exec() : int {
+
+ $runner = $this->getRunnerArg();
+
+ if (!($runner instanceof MigrationsRunner) && $runner !== null) {
+ return -1;
+ }
+ $ns = $this->getNS($runner);
+
+ if (!$this->hasMigrations($ns)) {
+ return 0;
+ }
+
+ $connection = $this->getDBConnection($runner);
+ if (!($connection instanceof ConnectionInfo)) {
+ return -1;
+ }
+
+ if ($this->checkMigrationsTable($runner, $connection) == -1) {
+ return -1;
+ }
+
+
+ try {
+ $runner = new MigrationsRunner(ROOT_PATH.DS. str_replace('\\', DS, $ns), $ns, $connection);
+ } catch (Throwable $ex) {
+ $this->error($ex->getMessage());
+ return -1;
+ }
+ if ($this->isArgProvided("--rollback")) {
+ return $this->rollbackMigration($runner);
+ } else {
+ return $this->executeMigrations($runner);
+ }
+ }
+ private function rollbackMigration(MigrationsRunner $runner) {
+ $isAll = $this->isArgProvided('--all');
+ $rolledCount = 0;
+ if ($isAll) {
+ $this->println("Rolling back migrations...");
+ do {
+ $migration = $this->doRollback($runner);
+ if ($migration === false) {
+ return -1;
+ }
+ $this->printInfo($migration, $rolledCount);
+ } while ($migration !== null);
+ } else {
+ $this->println("Rolling back last executed migration...");
+ $migration = $this->doRollback($runner);
+ if ($migration === false) {
+ return -1;
+ }
+ $this->printInfo($migration, $rolledCount);
+ }
+ if ($rolledCount == 0) {
+ $this->info("No migration rolled back.");
+ }
+
+
+ return 0;
+ }
+ private function doRollback(MigrationsRunner $runner) {
+ try {
+ return $runner->rollback();
+
+ } catch (Throwable $ex) {
+ $this->error('Failed to execute migration due to following:');
+ $this->println($ex->getMessage().' (Line '.$ex->getLine().')');
+ $this->warning('Execution stopped.');
+ return false;
+ }
+ }
+ private function printInfo(?AbstractMigration $migration, &$rolledCount = 0) {
+ if ($migration !== null) {
+ $rolledCount++;
+ $this->success("Migration '".$migration->getName()."' was successfully rolled back.");
+ }
+ }
+ private function executeMigrations(MigrationsRunner $runner) {
+ $this->println("Starting to execute migrations...");
+ $listOfApplied = [];
+ while ($this->applyNext($runner, $listOfApplied)){};
+
+ if (count($listOfApplied) != 0) {
+ $this->info("Number of applied migrations: ".count($listOfApplied));
+ $this->println("Names of applied migrations:");
+ $this->printList(array_map(function (AbstractMigration $migration) {
+ return $migration->getName();
+ }, $listOfApplied));
+ } else {
+ $this->info("No migrations were executed.");
+ }
+ return 0;
+ }
+ /**
+ * Returns the connection that will be used in running the migrations.
+ *
+ * The method will first check on the provided runner. If it has connection,
+ * it will be returned. Then it will check if the argument '--connection' is
+ * provided or not. If provided, the method will check if such connection
+ * exist in application configuration. If no connection was found, null
+ * is returned. If the argument '--connection' is not provided, the method will
+ * ask the user to select a connection from the connections which
+ * exist in application configuration.
+ *
+ * @param MigrationsRunner|null $runner If given and the connection is set
+ * on the instance, it will be returned.
+ *
+ * @return ConnectionInfo|null
+ */
+ private function getDBConnection(?MigrationsRunner $runner = null) {
+
+ if ($runner !== null) {
+ if ($runner->getConnectionInfo() !== null) {
+ return $runner->getConnectionInfo();
+ }
+ }
+ if (!$this->hasConnections()) {
+ return -1;
+ }
+ $dbConnections = array_keys(App::getConfig()->getDBConnections());
+
+ if ($this->isArgProvided('--connection')) {
+ $connection = $this->getArgValue('--connection');
+
+ if (!in_array($connection, $dbConnections)) {
+ $this->error("No connection was found which has the name '$connection'.");
+ return -1;
+ } else {
+ return App::getConfig()->getDBConnection($connection);
+ }
+ } else {
+ return CLIUtils::getConnectionName($this);
+ }
+ }
+ private function applyNext(MigrationsRunner $runner, &$listOfApplied) : bool {
+ try {
+ //$this->println("Executing migration...");
+ $applied = $runner->applyOne();
+
+ if ($applied !== null) {
+ $this->success("Migration '".$applied->getName()."' applied successfuly.");
+ $listOfApplied[] = $applied;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (Throwable $ex) {
+ $this->error('Failed to execute migration due to following:');
+ $this->println($ex->getMessage().' (Line '.$ex->getLine().')');
+ $this->warning('Execution stopped.');
+ return false;
+ }
+ }
+ /**
+ *
+ * @return MigrationsRunner|int|null
+ */
+ private function getRunnerArg() {
+ $runner = $this->getArgValue('--runner');
+
+ if ($runner === null) {
+ return null;
+ }
+
+ if (class_exists($runner)) {
+ try {
+ $runnerInst = new $runner();
+ } catch (Throwable $exc) {
+ $this->error('The argument --runner has invalid value: Exception: "'.$exc->getMessage().'".');
+ return -1;
+ }
+
+ if (!($runnerInst instanceof MigrationsRunner)) {
+ $this->error('The argument --runner has invalid value: "'.$runner.'" is not an instance of "MigrationsRunner".');
+ return -1;
+ } else {
+ return $runnerInst;
+ }
+ } else {
+ $this->error('The argument --runner has invalid value: Class "'.$runner.'" does not exist.');
+ return -1;
+ }
+ }
+ private function hasConnections() : bool {
+ $dbConnections = App::getConfig()->getDBConnections();
+ if (count($dbConnections) == 0) {
+ $this->info('No connections were found in application configuration.');
+ return false;
+ }
+ return true;
+ }
+ private function hasMigrations(string $namespace) : bool {
+ $tmpRunner = new MigrationsRunner(ROOT_PATH.DS.str_replace('\\', DS, $namespace), $namespace, null);
+ $this->println("Checking namespace '$namespace' for migrations...");
+ $count = count($tmpRunner->getMigrations());
+ if ($count == 0) {
+ $this->info("No migrations found in the namespace '$namespace'.");
+ return false;
+ }
+ $this->info("Found $count migration(s) in the namespace '$namespace'.");
+ return true;
+ }
+}
diff --git a/webfiori/framework/cli/commands/UpdateSettingsCommand.php b/webfiori/framework/cli/commands/UpdateSettingsCommand.php
index be850b020..b1345151d 100644
--- a/webfiori/framework/cli/commands/UpdateSettingsCommand.php
+++ b/webfiori/framework/cli/commands/UpdateSettingsCommand.php
@@ -33,7 +33,7 @@ public function __construct() {
.'Possible values are: version, app-name, scheduler-pass, page-title, '
.'page-description, primary-lang, title-sep, home-page, theme,'
.'admin-theme.', true),
- ], 'Update application settings which are stored in the class "AppConfig".');
+ ], 'Update application settings which are stored in specific configuration driver.');
}
public function exec() : int {
$options = [];
diff --git a/webfiori/framework/cli/helpers/ClassInfoReader.php b/webfiori/framework/cli/helpers/ClassInfoReader.php
index 933df8414..3606546a1 100644
--- a/webfiori/framework/cli/helpers/ClassInfoReader.php
+++ b/webfiori/framework/cli/helpers/ClassInfoReader.php
@@ -45,7 +45,7 @@ public function __construct(CLICommand $owner) {
*
* @return string A string that represents the name of the class.
*/
- public function getName($suffix = null, $errMsg = 'Invalid class name is given.') {
+ public function getName(?string $suffix = null, $errMsg = 'Invalid class name is given.') {
return $this->getOwner()->readClassName('Enter a name for the new class:', $suffix, $errMsg);
}
/**
@@ -74,7 +74,7 @@ public function getOwner() {
* @param string $defaultNs An optional default namespace to use in case the
* user did not provide a one. Note that this also will be the default path.
*
- * @param string $suffix An optional string which will be appended to the
+ * @param string|null $suffix An optional string which will be appended to the
* name of the class.
*
* @return array The method will return an array that contains 3 indices:
@@ -85,7 +85,7 @@ public function getOwner() {
* path: The location at which the class will be created.
*
*/
- public function readClassInfo($defaultNs = null, $suffix = null) {
+ public function readClassInfo(?string $defaultNs = null, ?string $suffix = null) {
$classExist = true;
do {
diff --git a/webfiori/framework/cli/helpers/CreateClassHelper.php b/webfiori/framework/cli/helpers/CreateClassHelper.php
index aad7e229b..d576d1458 100644
--- a/webfiori/framework/cli/helpers/CreateClassHelper.php
+++ b/webfiori/framework/cli/helpers/CreateClassHelper.php
@@ -43,7 +43,7 @@ class CreateClassHelper {
*
* @param ClassWriter $writer The writer that will hold class information.
*/
- public function __construct(CLICommand $command, ClassWriter $writer = null) {
+ public function __construct(CLICommand $command, ?ClassWriter $writer = null) {
$this->command = $command;
$this->classWriter = $writer;
$this->classInfoReader = new ClassInfoReader($this->command);
@@ -60,16 +60,14 @@ public function __construct(CLICommand $command, ClassWriter $writer = null) {
*
* @param string $confirmTxt The text of the question which will be asked.
*
- * @return boolean If the user choose 'y', the method will return true. If
- * he choose 'n', the method will return false.
- *
* @param boolean|null $default Default answer to use if empty input is given.
* It can be true for 'y' and false for 'n'. Default value is null which
* means no default will be used.
- *
- *
+ *
+ * @return boolean If the user choose 'y', the method will return true. If
+ * he choose 'n', the method will return false.
*/
- public function confirm(string $confirmTxt, $default = null) {
+ public function confirm(string $confirmTxt, ?bool $default = null) {
return $this->getCommand()->confirm($confirmTxt, $default);
}
/**
@@ -92,7 +90,7 @@ public function error(string $message) {
* @param string $suffix A string to append to the name of the class if it
* was not in provided name.
*/
- public function getClassInfo($defaultNs = null, $suffix = null) {
+ public function getClassInfo(?string $defaultNs = null, ?string $suffix = null) {
return $this->classInfoReader->readClassInfo($defaultNs, $suffix);
}
/**
@@ -126,7 +124,7 @@ public function getCommand() : CLICommand {
* user.
*
*/
- public function getInput(string $prompt, string $default = null, InputValidator $validator = null) {
+ public function getInput(string $prompt, ?string $default = null, ?InputValidator $validator = null) {
return $this->getCommand()->getInput($prompt, $default, $validator);
}
/**
diff --git a/webfiori/framework/cli/helpers/CreateDBAccessHelper.php b/webfiori/framework/cli/helpers/CreateDBAccessHelper.php
index a571a23ed..e993c8bce 100644
--- a/webfiori/framework/cli/helpers/CreateDBAccessHelper.php
+++ b/webfiori/framework/cli/helpers/CreateDBAccessHelper.php
@@ -53,7 +53,7 @@ public function getTable() : Table {
public function readDbClassInfo() {
$info = $this->getClassInfo(APP_DIR.'\\database', 'DB');
$this->getWriter()->setNamespace($info['namespace']);
- $this->getWriter()->setPath($info['namespace']);
+ $this->getWriter()->setPath(ROOT_PATH.DS.$info['namespace']);
$this->getWriter()->setClassName($info['name']);
$this->getWriter()->setConnection($this->getConnection());
}
diff --git a/webfiori/framework/cli/helpers/CreateFullRESTHelper.php b/webfiori/framework/cli/helpers/CreateFullRESTHelper.php
index af5c3b18d..25978d388 100644
--- a/webfiori/framework/cli/helpers/CreateFullRESTHelper.php
+++ b/webfiori/framework/cli/helpers/CreateFullRESTHelper.php
@@ -232,7 +232,7 @@ private function readTableInfo() {
$this->println("Now, time to collect database table information.");
$ns = CLIUtils::readNamespace($this->getCommand(), APP_DIR.'\\database', 'Provide us with a namespace for table class:');
$this->tableObjWriter->setNamespace($ns);
- $this->tableObjWriter->setPath($ns);
+ $this->tableObjWriter->setPath(ROOT_PATH.DS.$ns);
$create = new CreateTableObj($this->getCommand());
$create->getWriter()->setTable($this->tableObjWriter->getTable());
@@ -240,7 +240,7 @@ private function readTableInfo() {
$tableHelper->setTableName();
$tableHelper->setTableComment();
$tableHelper->getCreateHelper()->setNamespace($ns);
- $tableHelper->getCreateHelper()->setPath($ns);
+ $tableHelper->getCreateHelper()->setPath(ROOT_PATH.DS.$ns);
$tableHelper->getCreateHelper()->setClassName($this->tableObjWriter->getName());
$this->println('Now you have to add columns to the table.');
$tableHelper->addColumns();
@@ -319,7 +319,7 @@ private function writeServices() {
$writer->addUseStatement($t->getEntityMapper()->getEntityName(true));
$writer->addUseStatement(Json::class);
$writer->setNamespace($this->apisNs);
- $writer->setPath($this->apisNs);
+ $writer->setPath(ROOT_PATH.DS.$this->apisNs);
$writer->setClassName($sName);
$apiType = $serviceProps['type'];
diff --git a/webfiori/framework/cli/helpers/CreateMigration.php b/webfiori/framework/cli/helpers/CreateMigration.php
new file mode 100644
index 000000000..b624701c5
--- /dev/null
+++ b/webfiori/framework/cli/helpers/CreateMigration.php
@@ -0,0 +1,88 @@
+isConfigured = false;
+ if (!$command->isArgProvided('--defaults')) {
+ $ns = CLIUtils::readNamespace($command, $ns , 'Migration namespace:');
+ }
+
+ $runner = $this->initRunner($ns, $command);
+ if ($runner === null) {
+ $command->error("Unable to set migrations path.");
+ } else {
+ parent::__construct($command, new DatabaseMigrationWriter($runner));
+ $this->writer = $this->getWriter();
+ $this->setNamespace($ns);
+
+ $this->isConfigured = true;
+ if (!$command->isArgProvided('--defaults')) {
+ $this->setClassName($command->readClassName('Provide an optional name for the class that will have migration logic:', null));
+ $this->readClassInfo();
+
+ }
+ }
+ }
+ public function isConfigured() : bool {
+ return $this->isConfigured;
+ }
+ private function initRunner($ns, $command) {
+ $path = ROOT_PATH.DS. str_replace('\\', DS, $ns);
+ if (!is_dir($path)) {
+ $command->warning("The path '$path' does not exist.");
+ $create = $command->confirm("Would you like to create it?", true);
+
+ if ($create) {
+ if (!mkdir($path)) {
+ $command->error("Unable to create directory.");
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ return new MigrationsRunner($path, $ns, null);
+ }
+
+ private function readClassInfo() {
+
+ $name = $this->getInput('Enter an optional name for the migration:', $this->writer->getMigrationName());
+ $order = $this->getCommand()->readInteger('Enter an optional execution order for the migration:', $this->writer->getMigrationOrder());
+
+
+ $this->writer->setMigrationName($name);
+ $this->writer->setMigrationOrder($order);
+ }
+}
diff --git a/webfiori/framework/cli/helpers/TableObjHelper.php b/webfiori/framework/cli/helpers/TableObjHelper.php
index f1198a5ee..e79795738 100644
--- a/webfiori/framework/cli/helpers/TableObjHelper.php
+++ b/webfiori/framework/cli/helpers/TableObjHelper.php
@@ -337,7 +337,7 @@ public function setTableComment() {
* @param string $defaultName A string to set as default table name in case
* of hitting 'enter' without providing a value.
*/
- public function setTableName($defaultName = null) {
+ public function setTableName(?string $defaultName = null) {
$invalidTableName = true;
$helper = $this->getCreateHelper();
diff --git a/webfiori/framework/config/ClassDriver.php b/webfiori/framework/config/ClassDriver.php
index ca230c84a..570c0f488 100644
--- a/webfiori/framework/config/ClassDriver.php
+++ b/webfiori/framework/config/ClassDriver.php
@@ -67,7 +67,7 @@ public static function a($file, $str, $tabSize = 0) {
* @param string $description An optional description to describe the porpuse
* of the constant.
*/
- public function addEnvVar(string $name, $value = null, string $description = null) {
+ public function addEnvVar(string $name, mixed $value = null, ?string $description = null) {
$this->configVars['env-vars'][$name] = [
'value' => $value,
'description' => $description
@@ -889,12 +889,16 @@ private function initDefaultConfig() {
'version-info' => [
'version' => '1.0',
'version-type' => 'Stable',
- 'release-date' => '2021-01-10'
+ 'release-date' => date('Y-m-d')
],
'env-vars' => [
'WF_VERBOSE' => [
'value' => false,
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
+ ],
+ "CLI_HTTP_HOST" => [
+ "value" => "127.0.0.1",
+ "description" => "Host name that will be used when runing the application as command line utility."
]
],
'site' => [
diff --git a/webfiori/framework/config/ConfigurationDriver.php b/webfiori/framework/config/ConfigurationDriver.php
index d41af29a0..f807ecd07 100644
--- a/webfiori/framework/config/ConfigurationDriver.php
+++ b/webfiori/framework/config/ConfigurationDriver.php
@@ -27,7 +27,7 @@ interface ConfigurationDriver {
* @param string $description An optional description to describe the porpuse
* of the constant.
*/
- public function addEnvVar(string $name, $value = null, string $description = null);
+ public function addEnvVar(string $name, mixed $value = null, ?string $description = null);
/**
* Adds new database connections information or update existing connections.
*
diff --git a/webfiori/framework/config/Controller.php b/webfiori/framework/config/Controller.php
index 843e81c54..0229e65c0 100644
--- a/webfiori/framework/config/Controller.php
+++ b/webfiori/framework/config/Controller.php
@@ -36,7 +36,7 @@ public function __construct() {
*
* @param string|null $description An optional text that describes the variable.
*/
- public function addEnvVar(string $name, $value, string $description = null) {
+ public function addEnvVar(string $name, $value, ?string $description = null) {
$this->getDriver()->addEnvVar($name, $value, $description);
}
/**
diff --git a/webfiori/framework/config/JsonDriver.php b/webfiori/framework/config/JsonDriver.php
index bd56c0433..5d6ad10ed 100644
--- a/webfiori/framework/config/JsonDriver.php
+++ b/webfiori/framework/config/JsonDriver.php
@@ -64,7 +64,7 @@ public function __construct() {
'description' => 'Configure the verbosity of error messsages at run-time. This should be set to true in testing and false in production.'
], 'none', 'same'),
"CLI_HTTP_HOST" => new Json([
- "value" => "example.com",
+ "value" => "127.0.0.1",
"description" => "Host name that will be used when runing the application as command line utility."
], 'none', 'same')
], 'none', 'same'),
@@ -87,7 +87,7 @@ public function __construct() {
* @param string $description An optional description to describe the porpuse
* of the constant.
*/
- public function addEnvVar(string $name, $value = null, string $description = null) {
+ public function addEnvVar(string $name, mixed $value = null, ?string $description = null) {
$this->json->get('env-vars')->add($name, new Json([
'value' => $value,
'description' => $description
diff --git a/webfiori/framework/middleware/CacheMiddleware.php b/webfiori/framework/middleware/CacheMiddleware.php
new file mode 100644
index 000000000..7d7767a18
--- /dev/null
+++ b/webfiori/framework/middleware/CacheMiddleware.php
@@ -0,0 +1,115 @@
+setPriority(50);
+ $this->addToGroups(['web']);
+ $this->fromCache = false;
+ }
+ /**
+ * Checks if the response is loaded from the cache or caching must be performed.
+ *
+ * @param Request $request An object that represents the request that
+ * will be received.
+ *
+ * @param Response $response An object that represents the response
+ * that will be sent back.
+ *
+ */
+ public function after(Request $request, Response $response) {
+
+ if (!$this->fromCache) {
+ $uriObj = Router::getRouteUri();
+
+ if ($uriObj !== null) {
+ $key = $this->getKey();
+ $data = [
+ 'headers' => $response->getHeaders(),
+ 'http-code' => $response->getCode(),
+ 'body' => $response->getBody()
+ ];
+ Cache::set($key, $data, $uriObj->getCacheDuration());
+ }
+ }
+ }
+ /**
+ * This method will do nothing.
+ *
+ * @param Request $request An object that represents the request that
+ * will be received.
+ *
+ * @param Response $response An object that represents the response
+ * that will be sent back.
+ */
+ public function afterSend(Request $request, Response $response) {
+
+ }
+ /**
+ * Attempt to load an item from the cache and send the response back if
+ * cached.
+ *
+ * @param Request $request An object that represents the request that
+ * will be received.
+ *
+ * @param Response $response An object that represents the response
+ * that will be sent back.
+ */
+ public function before(Request $request, Response $response) {
+ $key = $this->getKey();
+ $data = Cache::get($key);
+
+ if ($data !== null) {
+ $this->fromCache = true;
+ $response->write($data['body']);
+ $response->setCode($data['http-code']);
+ foreach ($data['headers'] as $headerObj) {
+ $response->addHeader($headerObj->getName(), $headerObj->getValue());
+ }
+ $response->send();
+ }
+ }
+ /**
+ * Creates the key of cache item.
+ *
+ * This method will attempt to use 3 items to create a unique key. The items include:
+ *
+ * - Requested URI
+ * - Session ID (if applicable)
+ * - Authorization header (if applicable)
+ *
+ *
+ * @return string
+ */
+ public function getKey() : string {
+ $key = Request::getUri()->getUri(true, true);
+
+ //Following steps are used to make cached response unique per user.
+ $session = SessionsManager::getActiveSession();
+ if ($session !== null) {
+ $key .= $session->getId();
+ }
+ $authHeader = Request::getAuthHeader();
+ if ($authHeader !== null) {
+ $key .= $authHeader->getScheme().$authHeader->getCredentials();
+ }
+ //End
+ return $key;
+ }
+}
diff --git a/webfiori/framework/middleware/MiddlewareManager.php b/webfiori/framework/middleware/MiddlewareManager.php
index 493cc0a62..17e4e6313 100644
--- a/webfiori/framework/middleware/MiddlewareManager.php
+++ b/webfiori/framework/middleware/MiddlewareManager.php
@@ -10,44 +10,40 @@
*/
namespace webfiori\framework\middleware;
-use webfiori\collections\LinkedList;
+use Exception;
+
/**
* This class is used to manage the operations which are related to middleware.
*
* @author Ibrahim
*
- * @since 1.0
- *
- * @since 2.0.0
*/
class MiddlewareManager {
private static $inst;
/**
*
- * @var LinkedList
+ * @var array
*/
private $middlewareList;
private function __construct() {
- $this->middlewareList = new LinkedList();
+ $this->middlewareList = [];
}
/**
- * Returns a set of middlewares that belongs to a specific group.
+ * Returns a set of middleware that belongs to a specific group.
*
* @param string $groupName The name of the group.
*
- * @return LinkedList The method will return a linked list with all
+ * @return array The method will return a linked list with all
* middleware in the group. If no group which has the given name exist, the
* list will be empty.
- *
- * @since 1.0
*/
- public static function getGroup(string $groupName) {
- $list = new LinkedList();
+ public static function getGroup(string $groupName) : array {
+ $list = [];
$mdList = self::get()->middlewareList;
foreach ($mdList as $mw) {
if (in_array($groupName, $mw->getGroups())) {
- $list->add($mw);
+ $list[] = $mw;
}
}
@@ -61,8 +57,6 @@ public static function getGroup(string $groupName) {
* @return AbstractMiddleware|null If a middleware with the given name is
* found, the method will return it. Other than that, the method will return
* null.
- *
- * @since 1.0
*/
public static function getMiddleware(string $name) {
$mdList = self::get()->middlewareList;
@@ -76,27 +70,37 @@ public static function getMiddleware(string $name) {
/**
* Register a new middleware.
*
- * @param AbstractMiddleware $middleware The middleware that will be registered.
- *
- * @since 1.0
+ * @param AbstractMiddleware|string $middleware The middleware that will be registered.
*/
- public static function register(AbstractMiddleware $middleware) {
- self::get()->middlewareList->add($middleware);
+ public static function register($middleware) : bool {
+ if (gettype($middleware) == 'string') {
+ try {
+ $middleware = new $middleware();
+ } catch (Exception $exc) {
+ return false;
+ }
+ }
+ if ($middleware instanceof AbstractMiddleware) {
+ self::get()->middlewareList[] = $middleware;
+ return true;
+ }
+ return false;
}
/**
* Removes a middleware given its name.
*
* @param string $name The name of the middleware.
- *
- * @since 1.0
*/
public static function remove(string $name) {
$manager = self::get();
- $mw = $manager->getMiddleware($name);
-
- if ($mw instanceof AbstractMiddleware) {
- $manager->middlewareList->remove($manager->middlewareList->indexOf($mw));
+ $newList = [];
+
+ foreach ($manager->middlewareList as $md) {
+ if ($md->getName() != $name) {
+ $newList[] = $md;
+ }
}
+ $manager->middlewareList = $newList;
}
/**
*
diff --git a/webfiori/framework/middleware/StartSessionMiddleware.php b/webfiori/framework/middleware/StartSessionMiddleware.php
new file mode 100644
index 000000000..93e298c0c
--- /dev/null
+++ b/webfiori/framework/middleware/StartSessionMiddleware.php
@@ -0,0 +1,42 @@
+setPriority(PHP_INT_MAX);
+ $this->addToGroup('web');
+ }
+ public function after(Request $request, Response $response) {
+ try {
+ $sessionsCookiesHeaders = SessionsManager::getCookiesHeaders();
+
+ foreach ($sessionsCookiesHeaders as $headerVal) {
+ Response::addHeader('set-cookie', $headerVal);
+ }
+ } catch (Error $exc) {
+ }
+ }
+
+ public function afterSend(Request $request, Response $response) {
+ SessionsManager::validateStorage();
+ }
+
+ public function before(Request $request, Response $response) {
+ SessionsManager::start('wf-session');
+ }
+}
diff --git a/webfiori/framework/router/RouteOption.php b/webfiori/framework/router/RouteOption.php
index 0a3827d74..4bcbd3835 100644
--- a/webfiori/framework/router/RouteOption.php
+++ b/webfiori/framework/router/RouteOption.php
@@ -28,6 +28,10 @@ class RouteOption {
* An option which is used to indicate if path is case sensitive or not.
*/
const CASE_SENSITIVE = 'case-sensitive';
+ /**
+ * An option which is used to set the duration of route cache in seconds.
+ */
+ const CACHE_DURATION = 'cache-ttl';
/**
* An option which is used to set an array as closure parameters (applies to routes of type closure only)
*/
diff --git a/webfiori/framework/router/Router.php b/webfiori/framework/router/Router.php
index b8f6a4350..fc1e514a9 100644
--- a/webfiori/framework/router/Router.php
+++ b/webfiori/framework/router/Router.php
@@ -507,14 +507,16 @@ public static function incSiteMapRoute() {
Response::addHeader('content-type','text/xml');
};
self::closure([
- 'path' => '/sitemap.xml',
- 'route-to' => $sitemapFunc,
- 'in-sitemap' => true
+ RouteOption::PATH => '/sitemap.xml',
+ RouteOption::TO => $sitemapFunc,
+ RouteOption::SITEMAP => true,
+ RouteOption::CACHE_DURATION => 86400//1 day
]);
self::closure([
- 'path' => '/sitemap',
- 'route-to' => $sitemapFunc,
- 'in-sitemap' => true
+ RouteOption::PATH => '/sitemap',
+ RouteOption::TO => $sitemapFunc,
+ RouteOption::SITEMAP => true,
+ RouteOption::CACHE_DURATION => 86400//1 day
]);
}
/**
@@ -529,7 +531,8 @@ public static function notFound() {
* Adds new route to a web page.
*
* Note that the route which created using this method will be added to
- * 'global' and 'web' middleware groups.
+ * 'global' and 'web' middleware groups. Additionally, the routes will
+ * be cached for one hour.
*
* @param array $options An associative array that contains route
* options. Available options are:
@@ -755,13 +758,14 @@ private function addRouteHelper0($options): bool {
$asApi = $options[RouteOption::API];
$closureParams = $options[RouteOption::CLOSURE_PARAMS] ;
$path = $options[RouteOption::PATH];
+ $cache = $options[RouteOption::CACHE_DURATION];
if ($routeType == self::CLOSURE_ROUTE && !is_callable($routeTo)) {
return false;
}
$routeUri = new RouterUri($this->getBase().$path, $routeTo,$caseSensitive, $closureParams);
$routeUri->setAction($options[RouteOption::ACTION]);
-
+ $routeUri->setCacheDuration($cache);
if (!$this->hasRouteHelper($routeUri)) {
if ($asApi === true) {
$routeUri->setType(self::API_ROUTE);
@@ -928,6 +932,12 @@ private function checkOptionsArr(array $options): array {
} else {
$caseSensitive = true;
}
+
+ if (isset($options[RouteOption::CACHE_DURATION])) {
+ $cacheDuration = $options[RouteOption::CACHE_DURATION];
+ } else {
+ $cacheDuration = 0;
+ }
$routeType = $options[RouteOption::TYPE] ?? Router::CUSTOMIZED;
@@ -978,7 +988,8 @@ private function checkOptionsArr(array $options): array {
RouteOption::VALUES => $varValues,
RouteOption::MIDDLEWARE => $mdArr,
RouteOption::REQUEST_METHODS => $this->getRequestMethodsHelper($options),
- RouteOption::ACTION => $action
+ RouteOption::ACTION => $action,
+ RouteOption::CACHE_DURATION => $cacheDuration
];
}
private function copyOptionsToSub($options, &$subRoute) {
@@ -1367,7 +1378,6 @@ private function resolveUrlHelper(string $uri, bool $loadResource = true) {
private function routeFound(RouterUri $route, bool $loadResource) {
if ($route->isRequestMethodAllowed()) {
$this->uriObj = $route;
- $route->getMiddleware()->insertionSort(false);
foreach ($route->getMiddleware() as $mw) {
$mw->before(Request::get(), Response::get());
@@ -1376,7 +1386,6 @@ private function routeFound(RouterUri $route, bool $loadResource) {
if ($route->getType() == self::API_ROUTE && !defined('API_CALL')) {
define('API_CALL', true);
}
-
if (is_callable($route->getRouteTo())) {
if ($loadResource === true) {
call_user_func_array($route->getRouteTo(),$route->getClosureParams());
@@ -1453,6 +1462,7 @@ private function routeFound(RouterUri $route, bool $loadResource) {
* @throws RoutingException
*/
private function searchRoute(RouterUri $routeUri, string $uri, bool $loadResource, bool $withVars = false): bool {
+
$pathArray = $routeUri->getPathArray();
$requestMethod = Request::getMethod();
$indexToSearch = 'static';
@@ -1600,7 +1610,10 @@ private static function view(array $options): bool {
if (gettype($options) == 'array') {
$options[RouteOption::TYPE] = Router::VIEW_ROUTE;
self::addToMiddlewareGroup($options, 'web');
-
+ if (!isset($options[RouteOption::CACHE_DURATION])) {
+ //Cache pages for 1 hour by default
+ $options[RouteOption::CACHE_DURATION] = 3600;
+ }
return Router::getInstance()->addRouteHelper1($options);
}
diff --git a/webfiori/framework/router/RouterUri.php b/webfiori/framework/router/RouterUri.php
index d96e4a42b..01e6471bf 100644
--- a/webfiori/framework/router/RouterUri.php
+++ b/webfiori/framework/router/RouterUri.php
@@ -12,7 +12,6 @@
use Closure;
use InvalidArgumentException;
-use webfiori\collections\LinkedList;
use webfiori\framework\middleware\MiddlewareManager;
use webfiori\http\Uri;
use webfiori\ui\HTMLNode;
@@ -46,6 +45,8 @@ class RouterUri extends Uri {
*/
private $action;
private $assignedMiddlewareList;
+ private $sortedMiddleeareList;
+ private $cacheDuration;
/**
*
* @var array
@@ -131,12 +132,34 @@ public function __construct(string $requestedUri, $routeTo, bool $caseSensitive
$this->isCS = $caseSensitive;
$this->setType(Router::CUSTOMIZED);
$this->setRoute($routeTo);
- $this->assignedMiddlewareList = new LinkedList();
+ $this->assignedMiddlewareList = [];
+ $this->sortedMiddleeareList = [];
$this->setClosureParams($closureParams);
$this->incInSiteMap = false;
$this->languages = [];
$this->addMiddleware('global');
+ $this->setCacheDuration(0);
+ }
+ /**
+ * Returns the duration of URI cache.
+ *
+ * @return int The duration of URI cache. Default value is zero which indicates
+ * that no caching will happen.
+ */
+ public function getCacheDuration() : int {
+ return $this->cacheDuration;
+ }
+ /**
+ * Sets the duration of URI cache.
+ *
+ * @param int $val A positive value that represent cache duration in seconds.
+ * If 0 is given, it indicates that no caching will happen.
+ */
+ public function setCacheDuration(int $val) {
+ if ($val >= 0) {
+ $this->cacheDuration = $val;
+ }
}
/**
* Adds a language to the set of languages at which the resource that the URI
@@ -168,12 +191,12 @@ public function addMiddleware(string $name) {
$group = MiddlewareManager::getGroup($name);
foreach ($group as $mw) {
- $this->assignedMiddlewareList->add($mw);
+ $this->assignedMiddlewareList[] = $mw;
}
return;
}
- $this->assignedMiddlewareList->add($mw);
+ $this->assignedMiddlewareList[] = $mw;
}
/**
* Returns the name of the action that will be called in the controller.
@@ -238,12 +261,19 @@ public function getLanguages() : array {
/**
* Returns a list that holds objects for the middleware.
*
- * @return LinkedList
+ * @return array
*
- * @since 1.4.0
*/
- public function getMiddleware() : LinkedList {
- return $this->assignedMiddlewareList;
+ public function getMiddleware() : array {
+ if (count($this->assignedMiddlewareList) != count($this->sortedMiddleeareList)) {
+ $compareFunc = function ($a, $b) {
+ return $a->compare($b);
+ };
+ $this->sortedMiddleeareList = $this->assignedMiddlewareList;
+
+ usort($this->sortedMiddleeareList, $compareFunc);
+ }
+ return $this->sortedMiddleeareList;
}
/**
diff --git a/webfiori/framework/scheduler/TasksManager.php b/webfiori/framework/scheduler/TasksManager.php
index 181b0db5b..191d59abf 100644
--- a/webfiori/framework/scheduler/TasksManager.php
+++ b/webfiori/framework/scheduler/TasksManager.php
@@ -164,7 +164,7 @@ public static function activeTask() {
*
* @since 1.0
*/
- public static function createTask(string $when = '*/5 * * * *', string $taskName = '', callable $function = null, array $funcParams = []) : bool {
+ public static function createTask(string $when = '*/5 * * * *', string $taskName = '', ?callable $function = null, array $funcParams = []) : bool {
try {
$task = new BaseTask($when);
@@ -260,7 +260,7 @@ public static function dayOfWeek() : int {
*
* @since 1.0.1
*/
- public static function execLog(bool $bool = null) : bool {
+ public static function execLog(?bool $bool = null) : bool {
if ($bool !== null) {
self::get()->setLogEnabledHelper($bool);
}
@@ -644,7 +644,7 @@ public static function reset() {
*
* @since 1.0.6
*/
- public static function run(string $pass = '', string $taskName = null, bool $force = false, SchedulerCommand $command = null) {
+ public static function run(string $pass = '', ?string $taskName = null, bool $force = false, ?SchedulerCommand $command = null) {
self::get()->command = $command;
self::log('Running task(s) check...');
$activeSession = SessionsManager::getActiveSession();
@@ -929,7 +929,7 @@ private function logTaskExecution($task,$forced = false) {
* @param bool $xForce
* @param SchedulerCommand|null $command
*/
- private static function runTaskHelper(array &$retVal, AbstractTask $task, bool $xForce, SchedulerCommand $command = null) {
+ private static function runTaskHelper(array &$retVal, AbstractTask $task, bool $xForce, ?SchedulerCommand $command = null) {
if ($task->isTime() || $xForce) {
if ($command !== null) {
$task->setCommand($command);
@@ -965,7 +965,7 @@ private static function runTaskHelper(array &$retVal, AbstractTask $task, bool $
* @param AbstractTask|null $task
* @since 1.0.4
*/
- private function setActiveTaskHelper(AbstractTask $task = null) {
+ private function setActiveTaskHelper(?AbstractTask $task = null) {
$this->activeTask = $task;
if ($task !== null) {
diff --git a/webfiori/framework/session/Session.php b/webfiori/framework/session/Session.php
index b05112763..8833a16ed 100644
--- a/webfiori/framework/session/Session.php
+++ b/webfiori/framework/session/Session.php
@@ -276,7 +276,7 @@ public function deserialize(string $serialized): bool {
* @return string A new random session ID.
*
*/
- public static function generateSessionID(string $sessionName = null): string {
+ public static function generateSessionID(?string $sessionName = null): string {
$date = date('Y-m-d\TH:i:sO');
$hash = hash('sha256', $date);
$salt = time() + call_user_func(self::$randFunc, 0, 100);
diff --git a/webfiori/framework/ui/StarterPage.php b/webfiori/framework/ui/StarterPage.php
index 7081cd905..eec5aab1b 100644
--- a/webfiori/framework/ui/StarterPage.php
+++ b/webfiori/framework/ui/StarterPage.php
@@ -29,6 +29,10 @@ public function __construct() {
$this->getDocument()->getDocumentRoot()->setStyle([
'background-color' => '#e0f2b4'
]);
+ $this->getChildByID(self::MAIN_ELEMENTS[2])->setStyle([
+ 'background' => 'rgb(213,238,153)',
+ 'background' => 'radial-gradient(circle, rgba(213,238,153,0.5550420851934523) 26%, rgba(4,101,37,0.45700286950717783) 68%)'
+ ]);
$this->setTitle('Welcome to WebFiori');
$div = $this->insert('div');
$div->addChild('img', [
@@ -80,7 +84,10 @@ public function __construct() {
private function createCard($link, $icon, $cardTitle, $paragraph, \webfiori\ui\HTMLNode $el) {
$card = $el->addChild('v-card', [
'hover',
- 'height' => '220px'
+ 'height' => '220px',
+ 'style' => [
+ 'background' => 'rgba(255,255,255,.6)'
+ ]
]);
$card->addChild('v-card-title')->addChild('v-icon',[
'style' => 'margin:10px'
diff --git a/webfiori/framework/ui/WebPage.php b/webfiori/framework/ui/WebPage.php
index c1eb7f334..bebd05585 100644
--- a/webfiori/framework/ui/WebPage.php
+++ b/webfiori/framework/ui/WebPage.php
@@ -685,7 +685,7 @@ public function include(string $path, array $args = []) : HTMLNode {
*
* @since 1.0
*/
- public function includeI18nLables($bool = null) : bool {
+ public function includeI18nLables(?bool $bool = null) : bool {
if ($bool !== null) {
$this->includeLables = $bool === true;
}
@@ -1002,7 +1002,7 @@ public function setLang(string $lang = 'EN') {
*
* @see Theme::usingTheme()
*/
- public function setTheme($themeNameOrClass = null) {
+ public function setTheme(?string $themeNameOrClass = null) {
if ($themeNameOrClass !== null && strlen(trim($themeNameOrClass)) == 0) {
return;
}
@@ -1188,7 +1188,7 @@ private function checkLang() {
}
$this->setLang($langCodeFromSession);
}
- private function getConfigVar(string $meth, string $default = null, array $params = []) {
+ private function getConfigVar(string $meth, ?string $default = null, array $params = []) {
try {
return call_user_func_array([App::getConfig(), $meth], $params);
} catch (InitializationException $ex) {
diff --git a/webfiori/framework/ui/ui-functions.php b/webfiori/framework/ui/ui-functions.php
index d6404330c..ef20cb3fb 100644
--- a/webfiori/framework/ui/ui-functions.php
+++ b/webfiori/framework/ui/ui-functions.php
@@ -40,7 +40,7 @@
*
* @throws MissingLangException
*/
-function label(string $path, string $langCode = null) {
+function label(string $path, ?string $langCode = null) {
return Lang::getLabel($path, $langCode);
}
/**
diff --git a/webfiori/framework/writers/APITestCaseWriter.php b/webfiori/framework/writers/APITestCaseWriter.php
index c319d4a33..f440f1450 100644
--- a/webfiori/framework/writers/APITestCaseWriter.php
+++ b/webfiori/framework/writers/APITestCaseWriter.php
@@ -27,11 +27,11 @@ class APITestCaseWriter extends ClassWriter {
*
* @param WebServicesManager|null $manager Web services manager instance.
*
- * @param AbstractWebService $service The web service at which the test case
+ * @param AbstractWebService|string|null $service The web service at which the test case
* will be based on.
*/
- public function __construct(WebServicesManager $manager = null, $service = null) {
- parent::__construct('WebService', ROOT_PATH.'\\tests\\apis', 'tests\\apis');
+ public function __construct(?WebServicesManager $manager = null, null|string|AbstractWebService $service = null) {
+ parent::__construct('WebService', ROOT_PATH.DS.'tests'.DS.'apis', 'tests\\apis');
$this->setSuffix('Test');
if ($manager !== null) {
diff --git a/webfiori/framework/writers/ClassWriter.php b/webfiori/framework/writers/ClassWriter.php
index bb636e0e3..6b4ae9571 100644
--- a/webfiori/framework/writers/ClassWriter.php
+++ b/webfiori/framework/writers/ClassWriter.php
@@ -131,12 +131,13 @@ public function append($strOrArr, $tabsCount = 0) {
* indices of the array are parameters names and values are types of
* parameters.
*
- * @param string|null $returns An optional name of return type.
+ * @param string|null $returns An optional name of return type. This can be
+ * a string such as 'int|null|Object'.
*
* @return string The method will create method definition string and return
* it.
*/
- public function f($funcName, $argsArr = [], $returns = null) {
+ public function f($funcName, $argsArr = [], ?string $returns = null) {
$argsPart = '(';
foreach ($argsArr as $argName => $argType) {
@@ -370,7 +371,7 @@ public function setPath(string $path) : bool {
if (strlen($trimmed) == 0) {
return false;
}
- $this->path = $path;
+ $this->path = str_replace('\\', DS, str_replace('/', DS, $trimmed));
return true;
}
diff --git a/webfiori/framework/writers/DBClassWriter.php b/webfiori/framework/writers/DBClassWriter.php
index 22f5c975a..378125c05 100644
--- a/webfiori/framework/writers/DBClassWriter.php
+++ b/webfiori/framework/writers/DBClassWriter.php
@@ -42,8 +42,8 @@ class DBClassWriter extends ClassWriter {
* @param Table $table The table instance at which the class will build
* database operations based on.
*/
- public function __construct($className = 'NewDBOperationsClass', $ns = '\\', Table $table = null) {
- parent::__construct($className, $ns, $ns);
+ public function __construct(?string $className = 'NewDBOperationsClass', string $ns = '\\', ?Table $table = null) {
+ parent::__construct($className, ROOT_PATH.DS.$ns, $ns);
if ($table !== null) {
$this->setTable($table);
@@ -238,7 +238,7 @@ public function writeClassBody() {
], 2);
}
$this->append([
- "\$this->register('".str_replace("\\", "\\\\", $this->getPath())."');",
+ "\$this->register('".str_replace("\\", "\\\\", $this->getNamespace())."');",
], 2);
$this->append('}', 1);
@@ -353,7 +353,7 @@ private function writeColUpdate(Column $colObj, $key) {
$this->append($paramsComment, 1);
if (strpos($phpType, '|null') !== false) {
- $phpType = substr($phpType,0, strlen($phpType) - strlen('|null'));
+ $phpType = '?'.substr($phpType,0, strlen($phpType) - strlen('|null'));
}
$this->append([
" */",
diff --git a/webfiori/framework/writers/DatabaseMigrationWriter.php b/webfiori/framework/writers/DatabaseMigrationWriter.php
new file mode 100644
index 000000000..078e7c3ee
--- /dev/null
+++ b/webfiori/framework/writers/DatabaseMigrationWriter.php
@@ -0,0 +1,106 @@
+getMigrations());
+ $this->setMigrationOrder($count);
+ if ($count < 10) {
+ $name = 'Migration00'.$count;
+ } else if ($count < 100) {
+ $name = 'Migration0'.$count;
+ } else {
+ $name = 'Migration'.$count;
+ }
+ }
+
+ $this->setMigrationName($name);
+
+ parent::__construct($name, APP_PATH.'database'.DS.'migrations', APP_DIR.'\\database\\migrations');
+ $this->addUseStatement([
+ Database::class,
+ AbstractMigration::class,
+ ]);
+
+ }
+ public function getMigrationName() : string {
+ return $this->name;
+ }
+ public function getMigrationOrder() : int {
+ return $this->order;
+ }
+ public function setMigrationName(string $name) {
+ $this->name = $name;
+ }
+ public function setMigrationOrder(int $order) {
+ $this->order = $order;
+ }
+
+ public function writeClassBody() {
+ $this->append([
+ '/**',
+ ' * Creates new instance of the class.',
+ ' */',
+ $this->f('__construct'),
+
+ ], 1);
+ $this->append("parent::__construct('".$this->getMigrationName()."', ".$this->getMigrationOrder().");", 2);
+ $this->append('}', 1);
+ $this->append('/**', 1);
+ $this->append(' * Performs the action that will apply the migration.', 1);
+ $this->append(' * ', 1);
+ $this->append(' * @param Database $schema The database at which the migration will be applied to.', 1);
+ $this->append(' */', 1);
+ $this->append($this->f('up', ['schema' => 'Database']), 1);
+ $this->append('//TODO: Implement the action which will apply the migration to database.', 2);
+ $this->append('}', 1);
+ $this->append('/**', 1);
+ $this->append(' * Performs the action that will revert back the migration.', 1);
+ $this->append(' * ', 1);
+ $this->append(' * @param Database $schema The database at which the migration will be applied to.', 1);
+ $this->append(' */', 1);
+ $this->append($this->f('down', ['schema' => 'Database']), 1);
+ $this->append('//TODO: Implement the action which will revert back the migration.', 2);
+ $this->append('}', 1);
+ $this->append('}');
+ }
+ public function writeClassComment() {
+ $classTop = [
+ '/**',
+ ' * A database migration class.',
+ ' */'
+ ];
+ $this->append($classTop);
+ }
+
+ public function writeClassDeclaration() {
+ $this->append('class '.$this->getName().' extends AbstractMigration {');
+ }
+}
diff --git a/webfiori/framework/writers/TableClassWriter.php b/webfiori/framework/writers/TableClassWriter.php
index 9f40c52ac..19b6bba9e 100644
--- a/webfiori/framework/writers/TableClassWriter.php
+++ b/webfiori/framework/writers/TableClassWriter.php
@@ -75,7 +75,7 @@ class TableClassWriter extends ClassWriter {
*
* @since 1.0
*/
- public function __construct($tableObj = null) {
+ public function __construct(?Table $tableObj = null) {
parent::__construct('NewTable', APP_PATH.'database', APP_DIR.'\\database');
$this->setSuffix('Table');
diff --git a/webfiori/framework/writers/WebServiceWriter.php b/webfiori/framework/writers/WebServiceWriter.php
index 5537d2177..abfb26960 100644
--- a/webfiori/framework/writers/WebServiceWriter.php
+++ b/webfiori/framework/writers/WebServiceWriter.php
@@ -46,7 +46,7 @@ class WebServiceWriter extends ClassWriter {
* provided, the constant ROOT_PATH is used.
*
*/
- public function __construct($webServicesObj = null) {
+ public function __construct(?AbstractWebService $webServicesObj = null) {
parent::__construct('NewWebService', APP_PATH.'apis', APP_DIR.'\\apis');
$this->setSuffix('Service');