From ae50a61616f79388427dcc7db89b661c4ba9cdd8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:18:51 -0500 Subject: [PATCH 01/36] [#.x] - adjusting .gitignore --- .gitignore | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6ffe3aa..8932225 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -/cache/ -/config/development/ +.cache +.config +.local +composer.lock +vendor/ +.bash_history From c4b40d94a8152dea75afa96eab11da523c5f904a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:18:58 -0500 Subject: [PATCH 02/36] [#.x] - minor changes --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e62ae1..65bea64 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# rest-api-v6 +# REST API with Phalcon v6 + A REST API developed with Phalcon v6 From b61bd850690bd5c28a13f44e933c54403d42b253 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:19:08 -0500 Subject: [PATCH 03/36] [#.x] - added config files --- config/.env.ci | 16 ++++++++++++++++ config/.env.example | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 config/.env.ci create mode 100644 config/.env.example diff --git a/config/.env.ci b/config/.env.ci new file mode 100644 index 0000000..09b4fab --- /dev/null +++ b/config/.env.ci @@ -0,0 +1,16 @@ +PROJECT_NAME="rest" + +# Mariadb +DB_HOST="127.0.0.1" +DB_PORT=3306 +DB_USER="root" +DB_PASS="secret" +DB_NAME="phalcon" +DB_CHARSET="utf8" + +# Redis +DATA_REDIS_HOST="app-cache" +DATA_REDIS_PORT=6379 +DATA_REDIS_NAME="0" + +XDEBUG_MODE=coverage diff --git a/config/.env.example b/config/.env.example new file mode 100644 index 0000000..37fccc8 --- /dev/null +++ b/config/.env.example @@ -0,0 +1,16 @@ +PROJECT_NAME="rest" + +# Mariadb +DB_HOST="app-db" +DB_PORT=3306 +DB_USER="root" +DB_PASS="secret" +DB_NAME="phalcon" +DB_CHARSET= "utf8" + +# Redis +DATA_REDIS_HOST="app-cache" +DATA_REDIS_PORT=6379 +DATA_REDIS_NAME="0" + +XDEBUG_MODE=coverage From dc0807dc7d610af4a609753812879de1356281f0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:19:22 -0500 Subject: [PATCH 04/36] [#.x] - output folder for tests --- tests/_output/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/_output/.gitkeep diff --git a/tests/_output/.gitkeep b/tests/_output/.gitkeep new file mode 100644 index 0000000..e69de29 From 0e118fa4d6bc5ba3a21e5ead38595884077cd3dd Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:19:41 -0500 Subject: [PATCH 05/36] [#.x] - added output folder to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8932225..8d1fcc0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock vendor/ .bash_history +tests/_output From a38aec346b1328bd43dad5013eb2e07a9b06a3e7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:20:27 -0500 Subject: [PATCH 06/36] [#.x] - added docker-compose file and dockerfile for the dev environment --- docker-compose.yml | 100 ++++++++++++++++++ resources/docker/Dockerfile | 95 +++++++++++++++++ resources/docker/config/bashrc | 76 +++++++++++++ resources/docker/config/nginx/default.conf | 56 ++++++++++ resources/docker/config/nginx/nginx.conf | 53 ++++++++++ resources/docker/config/php/fpm-pool.conf | 20 ++++ resources/docker/config/php/php.ini | 12 +++ resources/docker/config/public/index.php | 3 + .../docker/config/supervisor/supervisor.conf | 23 ++++ 9 files changed, 438 insertions(+) create mode 100644 docker-compose.yml create mode 100644 resources/docker/Dockerfile create mode 100644 resources/docker/config/bashrc create mode 100644 resources/docker/config/nginx/default.conf create mode 100644 resources/docker/config/nginx/nginx.conf create mode 100644 resources/docker/config/php/fpm-pool.conf create mode 100644 resources/docker/config/php/php.ini create mode 100644 resources/docker/config/public/index.php create mode 100644 resources/docker/config/supervisor/supervisor.conf diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a5468e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,100 @@ +services: + + app-8.2: + build: + dockerfile: ./resources/docker/Dockerfile + args: + PHP_VERSION: 8.2 + hostname: rest-api-app-8.2 + container_name: "${PROJECT_NAME}-api-8.2" + tty: true + working_dir: /app + volumes: + - ./:/app + depends_on: + - app-db + - app-cache + networks: + - app-network + environment: + - APP_ENV=development + - APP_ENV_ADAPTER=dotenv + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/fpm-ping"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + + app-8.3: + build: + dockerfile: ./resources/docker/Dockerfile + args: + PHP_VERSION: 8.3 + hostname: rest-api-app-8.3 + container_name: "${PROJECT_NAME}-api-8.3" + tty: true + working_dir: /app + volumes: + - ./:/app + depends_on: + - app-db + - app-cache + networks: + - app-network + environment: + - APP_ENV=development + - APP_ENV_ADAPTER=dotenv + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/fpm-ping"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + + app-8.4: + build: + dockerfile: ./resources/docker/Dockerfile + args: + PHP_VERSION: 8.4 + hostname: rest-api-app-8.4 + container_name: "${PROJECT_NAME}-api-8.4" + tty: true + working_dir: /app + volumes: + - ./:/app + depends_on: + - app-db + - app-cache + networks: + - app-network + environment: + - APP_ENV=development + - APP_ENV_ADAPTER=dotenv + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/fpm-ping"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + + app-db: + container_name: "${PROJECT_NAME}-db" + image: mariadb:10.6 + environment: + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_USER=phalcon + - MYSQL_DATABASE=phalcon + - MYSQL_PASSWORD=secret + networks: + - app-network + + app-cache: + container_name: "${PROJECT_NAME}-cache" + image: redis:7-alpine + networks: + - app-network + +networks: + app-network: + driver: bridge diff --git a/resources/docker/Dockerfile b/resources/docker/Dockerfile new file mode 100644 index 0000000..9c1ec1f --- /dev/null +++ b/resources/docker/Dockerfile @@ -0,0 +1,95 @@ +ARG PHP_VERSION=8.4 + +FROM php:${PHP_VERSION}-fpm + +ARG UID=1000 +ARG GID=1000 +ARG USER=phalcon +ARG GROUP=phalcon + +# hadolint ignore=DL3022 +COPY --from=ghcr.io/mlocati/php-extension-installer \ + /usr/bin/install-php-extensions \ + /usr/local/bin/ + +# This is the folder structure from where compose is run from (root) +COPY resources/docker/config/ /config/ + +SHELL [ "/bin/bash", "-o", "pipefail", "-c" ] + +RUN set -eux \ +# Add user and group \ + && getent group "${GROUP}" || groupadd -g "${GID}" "${GROUP}" \ + && id -u "${USER}" &>/dev/null || useradd -l -u "${UID}" -g "${GID}" -d /app "${USER}" \ + && usermod -s /bin/bash "${USER}" \ + && mkdir -p /app /app/public /app/storage /run/nginx /run/supervisor \ + && mv /config/public/index.php /app/public/index.php \ + && chown "${USER}":"${GROUP}" /app \ + && chmod 0770 /app \ + && apt update \ +# Install applications \ + && apt install --no-install-recommends --no-install-suggests -q -y \ + apt-utils \ + git \ + nano \ + nginx \ + ssh \ + supervisor \ + unzip \ + zip \ +# Install base extensions \ + && install-php-extensions \ + igbinary \ + pcov \ + pdo_mysql \ + redis \ + xdebug \ + xsl \ + zip \ +# Configure PHP-FPM and PHP \ + && mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \ + && mv /config/php/fpm-pool.conf /usr/local/etc/php-fpm.d/www.conf \ + && mv /config/php/php.ini /usr/local/etc/php/conf.d/80-custom.ini \ + && sed -i -e "s/group = www-data/group = ${GROUP}/" /usr/local/etc/php-fpm.d/www.conf \ + && sed -i -e "s/listen.group = www-data/listen.group = ${GROUP}/" /usr/local/etc/php-fpm.d/www.conf \ +# Configure nginx \ + && rm -f /etc/nginx/sites-enabled/default \ + && mv /config/nginx/nginx.conf /etc/nginx/nginx.conf \ + && mv /config/nginx/default.conf /etc/nginx/conf.d/default.conf \ + && sed -i -e "s/user nobody nobody;/user www-data ${GROUP};/" /etc/nginx/nginx.conf \ +# Configure supervisord \ + && mv /config/supervisor/supervisor.conf /etc/supervisor/supervisord.conf \ +# Configure bashrc and permissions \ + && echo "" >> /etc/bash.bashrc \ + && cat /config/bashrc >> /etc/bash.bashrc \ + && rm -fR /config \ + && chown -R ${USER}:${GROUP} /app \ + && chgrp -R ${GROUP} /var/log/nginx /run/nginx /run/supervisor \ + && chmod -R 0775 /app /var/log/nginx /run/nginx /run/supervisor \ +# Cleanup \ + && apt autoremove --purge -y \ + && apt autoclean -y \ + && apt clean -y \ + && rm -rf /tmp/* /var/tmp/* \ + && find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \ + && find /var/cache -type f -delete \ + && find /var/log -type f -delete + +# hadolint ignore=DL3022 +COPY --from=composer/composer:2 \ + --chown=${USER}:${GROUP} \ + --chmod=0770 \ + /usr/bin/composer \ + /usr/local/bin/composer + +# Configure a healthcheck to validate that everything is up&running +HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1:80/fpm-ping || exit 1 + +WORKDIR /app + +USER ${USER} + +EXPOSE 80 + +# Let supervisord start nginx & php-fpm +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/resources/docker/config/bashrc b/resources/docker/config/bashrc new file mode 100644 index 0000000..24a49c0 --- /dev/null +++ b/resources/docker/config/bashrc @@ -0,0 +1,76 @@ +#!/bin/bash + +# Easier navigation: .., ..., ...., ....., ~ and - +alias ..="cd .." +alias ...="cd ../.." +alias ....="cd ../../.." +alias .....="cd ../../../.." +alias ~="cd ~" # `cd` is probably faster to type though +alias -- -="cd -" + +# Shortcuts +alias g="git" +alias h="history" + +# Detect which `ls` flavor is in use +if ls --color > /dev/null 2>&1; then # GNU `ls` + colorflag="--color" +else # OS X `ls` + colorflag="-G" +fi + +# List all files colorized in long format +# shellcheck disable=SC2139 +alias l="ls -lF ${colorflag}" + +# List all files colorized in long format, including dot files +# shellcheck disable=SC2139 +alias la="ls -laF ${colorflag}" + +# List only directories +# shellcheck disable=SC2139 +alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" + +# See: https://superuser.com/a/656746/280737 +alias ll='LC_ALL="C.UTF-8" ls -alF' + +# Always use color output for `ls` +# shellcheck disable=SC2139 +alias ls="command ls ${colorflag}" +export LS_COLORS='no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' + +# Always enable colored `grep` output +alias grep='grep --color=auto ' + +# Enable aliases to be sudo’ed +alias sudo='sudo ' + +# Get week number +alias week='date +%V' + +# Stopwatch +alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date' + +# Canonical hex dump; some systems have this symlinked +command -v hd > /dev/null || alias hd="hexdump -C" + +# vhosts +alias hosts='sudo nano /etc/hosts' + +# copy working directory +alias cwd='pwd | tr -d "\r\n" | xclip -selection clipboard' + +# copy file interactive +alias cp='cp -i' + +# move file interactive +alias mv='mv -i' + +# untar +alias untar='tar xvf' + +# Zephir related +PATH=$PATH;./vendor/bin/ + +PHP_VERSION=`php -r 'echo PHP_VERSION;'` +PS1='${debian_chroot:+($debian_chroot)}\u [$PHP_VERSION]@\w\$ ' diff --git a/resources/docker/config/nginx/default.conf b/resources/docker/config/nginx/default.conf new file mode 100644 index 0000000..ee90bc2 --- /dev/null +++ b/resources/docker/config/nginx/default.conf @@ -0,0 +1,56 @@ +# Default server definition +server { + listen [::]:80 default_server; + listen 80 default_server; + server_name _; + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + + root /app/public; + index index.php index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to index.php + try_files $uri $uri/ /index.php$is_args$args; + } + + # Redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/lib/nginx/html; + } + + # Pass the PHP scripts to PHP-FPM listening on php-fpm.sock + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + } + + # Set the cache-control headers on assets to cache for 5 days + location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ { + expires 5d; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } + + # Allow fpm ping and status from localhost + location ~ ^/(fpm-status|fpm-ping)$ { + access_log off; +# allow 127.0.0.1; +# deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + } +} diff --git a/resources/docker/config/nginx/nginx.conf b/resources/docker/config/nginx/nginx.conf new file mode 100644 index 0000000..e3952d5 --- /dev/null +++ b/resources/docker/config/nginx/nginx.conf @@ -0,0 +1,53 @@ +worker_processes auto; +user nobody nobody; +pid /run/nginx/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; +} + +http { + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Define custom log format to include response times + log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$request_time $upstream_response_time $pipe $upstream_cache_status'; + + ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, TLSv1, and TLSv1.1 for security + ssl_prefer_server_ciphers on; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + keepalive_timeout 65; + + # Write temporary files to /tmp so they can be created as a non-privileged user + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # Hide headers that identify the server to prevent information leakage + proxy_hide_header X-Powered-By; + fastcgi_hide_header X-Powered-By; + server_tokens off; + + # Enable gzip compression by default + gzip on; + gzip_proxied any; + # Based on CloudFlare's recommended settings + gzip_types text/richtext text/plain text/css text/x-script text/x-component text/x-java-source text/x-markdown application/javascript application/x-javascript text/javascript text/js image/x-icon image/vnd.microsoft.icon application/x-perl application/x-httpd-cgi text/xml application/xml application/rss+xml application/vnd.api+json application/x-protobuf application/json multipart/bag multipart/mixed application/xhtml+xml font/ttf font/otf font/x-woff image/svg+xml application/vnd.ms-fontobject application/ttf application/x-ttf application/otf application/x-otf application/truetype application/opentype application/x-opentype application/font-woff application/eot application/font application/font-sfnt application/wasm application/javascript-binast application/manifest+json application/ld+json application/graphql+json application/geo+json; + gzip_vary on; + gzip_disable "msie6"; + + # Include server configs + include /etc/nginx/conf.d/*.conf; +} diff --git a/resources/docker/config/php/fpm-pool.conf b/resources/docker/config/php/fpm-pool.conf new file mode 100644 index 0000000..38c5e4b --- /dev/null +++ b/resources/docker/config/php/fpm-pool.conf @@ -0,0 +1,20 @@ +[global] +; Log to stderr +error_log = /dev/stderr + +[www] +catch_workers_output = yes +clear_env = no +decorate_workers_output = no +group = www-data +listen = 127.0.0.1:9000 +listen.group = www-data +listen.owner = www-data +listen.mode = 0660 +ping.path = /fpm-ping +pm = ondemand +pm.max_children = 100 +pm.max_requests = 1000 +pm.process_idle_timeout = 10s; +pm.status_path = /fpm-status +user = www-data diff --git a/resources/docker/config/php/php.ini b/resources/docker/config/php/php.ini new file mode 100644 index 0000000..1a1be6f --- /dev/null +++ b/resources/docker/config/php/php.ini @@ -0,0 +1,12 @@ +[PHP] +date.timezone = UTC +memory_limit = 512M +max_execution_time = 120 +max_input_time = 120 +error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED +xmlrpc_errors = Off +report_memleaks = On +display_errors = On +display_startup_errors = On +log_errors = On +html_errors = Off diff --git a/resources/docker/config/public/index.php b/resources/docker/config/public/index.php new file mode 100644 index 0000000..83f1549 --- /dev/null +++ b/resources/docker/config/public/index.php @@ -0,0 +1,3 @@ + Date: Mon, 1 Sep 2025 13:20:52 -0500 Subject: [PATCH 07/36] [#.x] - added changelog and contributing guidelines --- CHANGELOG.md | 3 +++ CONTRIBUTING.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2e4c4a2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 + +Under development diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..42cceab --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to Phalcon + +Phalcon is an open source project and a volunteer effort. Phalcon welcomes contribution from everyone. Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. + +## Contributions + +Contributions to Phalcon should be made in the form of GitHub pull requests. Each pull request will be reviewed by a core contributor (someone with permission to merge patches). Feedback can be provided and potentially changes requested or the pull request will be merged. All contributions should +follow this format, even those from core contributors. + +## Questions & Support + +We use the GitHub issues for tracking bugs and feature requests and have limited bandwidth to address all of them. Thus we only accept bug reports, new feature requests and pull requests in GitHub. Our great community and contributors are happy to help you though! Please use these community resources for obtaining help. + +_Please use the [Documentation](https://phalcon.io/docs) before anything else. You can also use the search feature in our documents to find what you are looking for. If your question is still not answered, there are more options below._ + +* Questions should go to [GitHub Discussions](https://phalcon.io/discussions) +* Come join the Phalcon [Discord](https://phalcon.io/discord) +* Our social network accounts are: + * [Telegram](https://phalcon.io/telegram) + * [Gab](https://phalcon.io/gab) + * [MeWe](https://phalcon.io/mewe) + * [Twitter](https://phalcon.io/t) + * [Facebook](https://phalcon.io/fb) +* If you still believe that what you found is a bug, please + [open an issue](https://github.com/phalcon/phalcon/issues/new) + +Please report bugs when you've exhausted all of the above options. + +## Bug Report Checklist + +* Make sure you are using the latest released version of the composer packages. +* If you have found a bug it is important to add relevant reproducibility information to your issue to allow us to reproduce the bug and fix it quicker. Add a script, small program or repository providing the necessary code to make everyone reproduce the issue reported easily. +* Be sure that information such as OS, Phalcon version and PHP version are part of the bug report + +## Pull Request Checklist + +* Don't submit your pull requests to the `master` branch. Branch from the required branch and, if needed, rebase to the proper branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes +* Don't put submodule updates in your pull request unless they are to landed commits +* Add tests relevant to the fixed bug or new feature. Test classes should follow the [PSR-12 coding style guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-coding-style-guide.md). + +## Requesting Features + +If you have a change or new feature in mind, please fill out an NFR on GitHub. + + +Thanks! +Phalcon Team From 44b4dfbb9aaaaf55af3c7eccf278ae4fd8677339 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:21:14 -0500 Subject: [PATCH 08/36] [#.x] - ignoring .env --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8d1fcc0..e405cb2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ composer.lock vendor/ .bash_history tests/_output +.env From 4515895dbe665f237562e54d0a9797a2084ff540 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:21:54 -0500 Subject: [PATCH 09/36] [#.x] - initial folder structure --- bin/.gitkeep | 0 docs/.gitkeep | 0 public/index.php | 3 +++ src/.gitkeep | 0 4 files changed, 3 insertions(+) create mode 100644 bin/.gitkeep create mode 100644 docs/.gitkeep create mode 100644 public/index.php create mode 100644 src/.gitkeep diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..174d7fd --- /dev/null +++ b/public/index.php @@ -0,0 +1,3 @@ + Date: Mon, 1 Sep 2025 13:22:02 -0500 Subject: [PATCH 10/36] [#.x] - composer with necessary packages --- composer.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f97b7ef --- /dev/null +++ b/composer.json @@ -0,0 +1,44 @@ +{ + "name": "phalcon/rest-api-v6", + "type": "library", + "description": "Sample REST API application implemented with Phalcon v6", + "keywords": [ + "phalcon", + "framework", + "sample app", + "rest-api", + "rest", + "api" + ], + "homepage": "https://phalcon.io", + "license": "MIT", + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/rest-api-v6/graphs/contributors" + } + ], + "require": { + "ext-mbstring": "*", + "ext-pdo": "*", + "phalcon/phalcon": "^6.0.x-dev", + "robmorgan/phinx": "^0.16.10", + "vlucas/phpdotenv": "^5.6" + }, + "require-dev": { + "pds/skeleton": "^1.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.5", + "squizlabs/php_codesniffer": "^3.13" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true + }, + "replace": { + "symfony/polyfill-php80": "*", + "symfony/polyfill-ctype": "*", + "symfony/polyfill-mbstring": "*" + } +} From 64205f366c0dbfe4cd0fb9b4de83132692e735ea Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:24:36 -0500 Subject: [PATCH 11/36] [#.x] - hello world from index --- public/index.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/index.php b/public/index.php index 174d7fd..86e84a5 100644 --- a/public/index.php +++ b/public/index.php @@ -1,3 +1,5 @@ Date: Mon, 1 Sep 2025 13:39:11 -0500 Subject: [PATCH 12/36] [#.x] - adding cache folder for composer and adjusting gitignore --- .gitignore | 1 + composer.json | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e405cb2..958d87e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .cache .config .local +.composer composer.lock vendor/ .bash_history diff --git a/composer.json b/composer.json index f97b7ef..70b48fa 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "vlucas/phpdotenv": "^5.6" }, "require-dev": { + "pds/composer-script-names": "^1.0", "pds/skeleton": "^1.0", "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^11.5", @@ -34,11 +35,18 @@ "config": { "preferred-install": "dist", "sort-packages": true, - "optimize-autoloader": true + "optimize-autoloader": true, + "cache-dir": ".composer" }, "replace": { "symfony/polyfill-php80": "*", "symfony/polyfill-ctype": "*", "symfony/polyfill-mbstring": "*" + }, + "scripts": { + "analyze": "vendor/bin/phpstan analyze -c phpstan.neon", + "cs": "vendor/bin/phpcs --standard=phpcs.xml", + "cs-fix": "vendor/bin/phpcbf --standard=phpcs.xml", + "test-unit": "vendor/bin/phpunit -c phpunit.xml.dist" } } From c6bde68a8ef628dc133d5eeeea286054f051415c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:39:23 -0500 Subject: [PATCH 13/36] [#.x] - added codesniffer config --- phpcs.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 phpcs.xml diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..0af51b5 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,12 @@ + + + Phalcon REST API v6 Coding Standards + + + + + + + src + tests/Unit + From c8d58280df6d9c7288bf0fd8f3def4db31e1a3a5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:39:29 -0500 Subject: [PATCH 14/36] [#.x] - added phpstan config --- phpstan.neon | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..776ccd8 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: max + paths: + - src From fdf3b7c6fb2f51da1a2e3de9861724041fc54c46 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:39:42 -0500 Subject: [PATCH 15/36] [#.x] - added phpunit config --- phpunit.php | 25 +++++++++++++++++++++++++ phpunit.xml.dist | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 phpunit.php create mode 100644 phpunit.xml.dist diff --git a/phpunit.php b/phpunit.php new file mode 100644 index 0000000..9158029 --- /dev/null +++ b/phpunit.php @@ -0,0 +1,25 @@ + +# +# For the full copyright and license information, please view +# the LICENSE file that was distributed with this source code. + + +ini_set('xdebug.mode', 'coverage'); + +error_reporting(E_ALL); + +$autoloader = __DIR__ . '/vendor/autoload.php'; + +if (! file_exists($autoloader)) { + echo "Composer autoloader not found: $autoloader" . PHP_EOL; + echo "Please issue 'composer install' and try again." . PHP_EOL; + exit(1); +} + +require_once $autoloader; diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..174c263 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + ./tests/Unit/ + + + + + ./src + + + From 5551751ffa1bbd61c4ed57f5e069e08473b566cb Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:43:41 -0500 Subject: [PATCH 16/36] [#.x] - added github actions setup --- .github/workflows/main.yml | 144 +++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6b9135c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,144 @@ + +# This file is part of Phalcon. +# +# (c) Phalcon Team +# +# For the full copyright and license information, please view +# the LICENSE file that was distributed with this source code. + +name: REST API v6 CI +on: + push: + paths-ignore: + - '**.md' + - '**.txt' + pull_request: + workflow_dispatch: + +env: + fail-fast: true + + # PHP extensions required by Composer + EXTENSIONS: json, mbstring, pcov, pdo, pdo_mysql + +permissions: { } +jobs: + + # PHP CodeSniffer inspection + phpcs: + name: "Quality gate" + if: "!contains(github.event.head_commit.message, 'ci skip')" + + permissions: + contents: read + + runs-on: ubuntu-22.04 + + strategy: + fail-fast: true + matrix: + php: + - '8.2' + - '8.3' + - '8.4' + steps: + - uses: actions/checkout@v4 + + - name: "Setup PHP" + uses: shivammathur/setup-php@2.35.4 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.EXTENSIONS }} + tools: pecl + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Install development dependencies with Composer" + uses: "ramsey/composer-install@v3" + with: + composer-options: "--prefer-dist" + + - name: "PHPCS" + run: | + composer cs + + - name: "PHPStan" + run: | + composer analyze + + unit-tests: + needs: phpcs + + permissions: + contents: read # to fetch code (actions/checkout) + + name: Unit tests / PHP-${{ matrix.php }} + runs-on: ubuntu-22.04 + + strategy: + matrix: + php: + - '8.2' + - '8.3' + - '8.4' + + services: + mariadb: + image: mariadb:10.6 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: secret + MYSQL_USER: phalcon + MYSQL_DATABASE: phalcon + MYSQL_PASSWORD: secret + + steps: + - uses: actions/checkout@v4 + + - name: "Setup PHP" + uses: shivammathur/setup-php@2.35.4 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.EXTENSIONS }} + tools: pecl + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Install development dependencies with Composer" + uses: "ramsey/composer-install@v3" + with: + composer-options: "--prefer-dist" + + - name: "Setup Tests" + shell: bash + run: | + cp config/.env.ci .env + mkdir -p tests/_output/coverage/ + + - name: "Run Migrations" + if: always() + run: | + composer migrate + + - name: "Run Unit Tests" + if: always() + run: | + composer test-unit-coverage + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + projectBaseDir: ./ + args: > + -Dsonar.organization=${{ secrets.SONAR_ORGANIZATION }} + -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }} + -Dsonar.sources=src/ + -Dsonar.exclusions=vendor/**,cv/**,tests/** + -Dsonar.sourceEncoding=UTF-8 + -Dsonar.language=php + -Dsonar.tests=tests/ + -Dsonar.php.coverage.reportPaths=tests/_output/cov.xml From e08f51c8be369b971cda8e754153503ae48da17c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 1 Sep 2025 13:43:51 -0500 Subject: [PATCH 17/36] [#.x] - added more commands to composer --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 70b48fa..c064535 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,9 @@ "analyze": "vendor/bin/phpstan analyze -c phpstan.neon", "cs": "vendor/bin/phpcs --standard=phpcs.xml", "cs-fix": "vendor/bin/phpcbf --standard=phpcs.xml", - "test-unit": "vendor/bin/phpunit -c phpunit.xml.dist" + "migrate": "vendor/bin/phinx migrate", + "test-unit": "vendor/bin/phpunit -c phpunit.xml.dist --display-all-issues", + "test-unit-coverage": "vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover tests/_output/cov.xml --display-all-issues", + "test-unit-coverage-html": "vendor/bin/phpunit -c phpunit.xml.dist --coverage-html tests/_output/coverage --display-all-issues" } } From ceff2984facca14a982a112cbe0ab30cc10bf656 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 4 Sep 2025 17:01:40 -0500 Subject: [PATCH 18/36] [#.x] - correcting path --- resources/docker/config/bashrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/docker/config/bashrc b/resources/docker/config/bashrc index 24a49c0..6f95e2e 100644 --- a/resources/docker/config/bashrc +++ b/resources/docker/config/bashrc @@ -69,8 +69,7 @@ alias mv='mv -i' # untar alias untar='tar xvf' -# Zephir related -PATH=$PATH;./vendor/bin/ +PATH=$PATH:./vendor/bin/ PHP_VERSION=`php -r 'echo PHP_VERSION;'` PS1='${debian_chroot:+($debian_chroot)}\u [$PHP_VERSION]@\w\$ ' From fcddd72dcb53fa3300eae5e93f32d7dc7ac05df9 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 4 Sep 2025 17:12:04 -0500 Subject: [PATCH 19/36] [#.x] - minor adjustments to names and ini --- docker-compose.yml | 9 ++++++--- resources/docker/config/php/php.ini | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a5468e2..9aa2f4f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: dockerfile: ./resources/docker/Dockerfile args: PHP_VERSION: 8.2 - hostname: rest-api-app-8.2 + hostname: rest-api-app-82 container_name: "${PROJECT_NAME}-api-8.2" tty: true working_dir: /app @@ -17,6 +17,7 @@ services: networks: - app-network environment: + - PHP_IDE_CONFIG=serverName=rest-api-app-82 - APP_ENV=development - APP_ENV_ADAPTER=dotenv healthcheck: @@ -31,7 +32,7 @@ services: dockerfile: ./resources/docker/Dockerfile args: PHP_VERSION: 8.3 - hostname: rest-api-app-8.3 + hostname: rest-api-app-83 container_name: "${PROJECT_NAME}-api-8.3" tty: true working_dir: /app @@ -43,6 +44,7 @@ services: networks: - app-network environment: + - PHP_IDE_CONFIG=serverName=rest-api-app-83 - APP_ENV=development - APP_ENV_ADAPTER=dotenv healthcheck: @@ -57,7 +59,7 @@ services: dockerfile: ./resources/docker/Dockerfile args: PHP_VERSION: 8.4 - hostname: rest-api-app-8.4 + hostname: rest-api-app-84 container_name: "${PROJECT_NAME}-api-8.4" tty: true working_dir: /app @@ -69,6 +71,7 @@ services: networks: - app-network environment: + - PHP_IDE_CONFIG=serverName=rest-api-app-83 - APP_ENV=development - APP_ENV_ADAPTER=dotenv healthcheck: diff --git a/resources/docker/config/php/php.ini b/resources/docker/config/php/php.ini index 1a1be6f..3be8e2d 100644 --- a/resources/docker/config/php/php.ini +++ b/resources/docker/config/php/php.ini @@ -1,12 +1,13 @@ [PHP] -date.timezone = UTC -memory_limit = 512M -max_execution_time = 120 -max_input_time = 120 -error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED -xmlrpc_errors = Off -report_memleaks = On -display_errors = On -display_startup_errors = On -log_errors = On -html_errors = Off +date.timezone = UTC +memory_limit = 512M +max_execution_time = 120 +max_input_time = 120 +error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED +xmlrpc_errors = Off +report_memleaks = On +display_errors = On +display_startup_errors = On +log_errors = On +html_errors = Off +xdebug.mode = debug,develop,trace From ce9dcd062026afc5ccaa3954d71d185a8ffe42dd Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 8 Sep 2025 13:31:44 -0500 Subject: [PATCH 20/36] [#.x] - upgrading redis to v8 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9aa2f4f..b96a807 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,7 +94,7 @@ services: app-cache: container_name: "${PROJECT_NAME}-cache" - image: redis:7-alpine + image: redis:8-alpine networks: - app-network From 10970c1cc33c19257ac36cd979731cbdebe1f6c6 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 8 Sep 2025 14:00:54 -0500 Subject: [PATCH 21/36] [#.x] - added migrations/seeds folders for phinx --- resources/db/migrations/.gitkeep | 0 resources/db/seeds/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/db/migrations/.gitkeep create mode 100644 resources/db/seeds/.gitkeep diff --git a/resources/db/migrations/.gitkeep b/resources/db/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/db/seeds/.gitkeep b/resources/db/seeds/.gitkeep new file mode 100644 index 0000000..e69de29 From 39f158bebf23fc7f87eef7f86e25d7d8b2a9abf3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 8 Sep 2025 14:10:52 -0500 Subject: [PATCH 22/36] [#.x] - added migration for users table and phinx config --- phinx.php | 35 ++++++++++++ .../20250908190433_add_users_table.php | 57 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 phinx.php create mode 100644 resources/db/migrations/20250908190433_add_users_table.php diff --git a/phinx.php b/phinx.php new file mode 100644 index 0000000..3c1b2e3 --- /dev/null +++ b/phinx.php @@ -0,0 +1,35 @@ +load(); + +$_ENV = array_merge($envs, $_ENV); + +return [ + 'paths' => [ + 'migrations' => './resources/db/migrations', + 'seeds' => './resources/db/seeds', + ], + 'environments' => [ + 'default_migration_table' => "ut_migrations", + 'default_environment' => 'development', + 'development' => [ + 'adapter' => $_ENV['DB_ADAPTER'] ?? 'mysql', + 'host' => $_ENV['DB_HOST'] ?? '127.0.0.1', + 'name' => $_ENV['DB_NAME'] ?? 'phalcon', + 'user' => $_ENV['DB_USER'] ?? 'root', + 'pass' => $_ENV['DB_PASS'] ?? 'secret', + 'port' => $_ENV['DB_PORT'] ?? 3306, + 'charset' => $_ENV['DB_CHARSET'] ?? 'utf8', + ], + ], + 'version_order' => 'creation', +]; diff --git a/resources/db/migrations/20250908190433_add_users_table.php b/resources/db/migrations/20250908190433_add_users_table.php new file mode 100644 index 0000000..3d9c66d --- /dev/null +++ b/resources/db/migrations/20250908190433_add_users_table.php @@ -0,0 +1,57 @@ +table( + 'co_users', + [ + 'id' => 'usr_id', + 'signed' => false, + ] + ); + + $table + ->addColumn( + 'usr_status_flag', + 'boolean', + [ + 'signed' => false, + 'null' => false, + 'default' => 0, + ] + ) + ->addColumn( + 'usr_username', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'usr_password', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addIndex('usr_status_flag') + ->addIndex('usr_username') + ->save() + ; + } + + public function down(): void + { + $this->table('co_users')->drop()->save(); + } +} From 8c800513f22464f76ef91f2ab2b20158424d7f57 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 9 Sep 2025 08:19:31 -0500 Subject: [PATCH 23/36] [#.x] - added project namespace --- composer.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/composer.json b/composer.json index c064535..e1380a5 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,16 @@ "phpunit/phpunit": "^11.5", "squizlabs/php_codesniffer": "^3.13" }, + "autoload": { + "psr-4": { + "Phalcon\\Api\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Phalcon\\Api\\Tests\\": "tests/" + } + }, "config": { "preferred-install": "dist", "sort-packages": true, From b3d28a1a6b496eb165baf23c06582e8a0e3e1b5c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 9 Sep 2025 08:19:52 -0500 Subject: [PATCH 24/36] [#.x] - added container for the application --- src/Domain/Services/Container.php | 143 ++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/Domain/Services/Container.php diff --git a/src/Domain/Services/Container.php b/src/Domain/Services/Container.php new file mode 100644 index 0000000..554cf1f --- /dev/null +++ b/src/Domain/Services/Container.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services; + +use Phalcon\Api\Action\Hello\GetAction; +use Phalcon\Api\Domain\Hello\HelloService; +use Phalcon\Api\Responder\Hello\HelloTextResponder; +use Phalcon\Di\Di; +use Phalcon\Di\Service; +use Phalcon\Filter\FilterFactory; +use Phalcon\Http\Request; +use Phalcon\Http\Response; +use Phalcon\Mvc\Router; + +class Container extends Di +{ + /** @var string */ + public const CACHE = 'cache'; + /** @var string */ + public const CONNECTION = 'connection'; + /** @var string */ + public const FILTER = 'filter'; + /** @var string */ + public const LOGGER = 'logger'; + /** @var string */ + public const REQUEST = 'request'; + /** @var string */ + public const RESPONSE = 'response'; + /** @var string */ + public const ROUTER = 'router'; + + /** + * Hello + */ + public const HELLO_ACTION = 'hello.action'; + public const HELLO_SERVICE = 'hello.service'; + public const HELLO_RESPONDER_TEXT = 'hello.responder.text'; + + public function __construct() + { + $this->services = [ + self::FILTER => $this->getServiceFilter(), + self::REQUEST => $this->getServiceSimple(Request::class, true), + self::RESPONSE => $this->getServiceSimple(Response::class, true), + self::ROUTER => $this->getServiceRouter(), + + self::HELLO_ACTION => $this->getServiceHelloAction(), + self::HELLO_SERVICE => $this->getServiceSimple(HelloService::class), + self::HELLO_RESPONDER_TEXT => $this->getServiceHelloResponderText(), + ]; + + parent::__construct(); + } + + /** + * @return Service + */ + private function getServiceFilter(): Service + { + return new Service( + function () { + return (new FilterFactory())->newInstance(); + }, + true + ); + } + + /** + * @return Service + */ + private function getServiceRouter(): Service + { + return new Service( + [ + 'className' => Router::class, + 'arguments' => [ + [ + 'type' => 'parameter', + 'value' => false, + ] + ] + ] + ); + } + + private function getServiceHelloAction(): Service + { + return new Service( + [ + 'className' => GetAction::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::HELLO_SERVICE, + ], + [ + 'type' => 'service', + 'name' => self::HELLO_RESPONDER_TEXT, + ] + ] + ] + ); + } + + private function getServiceHelloResponderText(): Service + { + return new Service( + [ + 'className' => HelloTextResponder::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::RESPONSE, + ] + ] + ] + ); + } + + /** + * @param string $className + * @param bool $isShared + * + * @return Service + */ + private function getServiceSimple( + string $className, + bool $isShared = false + ): Service { + return new Service($className, $isShared); + } +} From 26bfc05ef481c5acc268f1eb20139023c4010aec Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 9 Sep 2025 08:20:06 -0500 Subject: [PATCH 25/36] [#.x] - bootstrapping the app in index --- public/index.php | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/public/index.php b/public/index.php index 86e84a5..c5507cb 100644 --- a/public/index.php +++ b/public/index.php @@ -2,4 +2,48 @@ declare(strict_types=1); -echo "Hello World!"; +use Phalcon\Api\Domain\Services\Container; +use Phalcon\Mvc\Micro; + +require_once dirname(__DIR__) . '/vendor/autoload.php'; + +$container = new Container(); +$application = new Micro($container); + +/** + * Routes + */ +$routes = [ + [ + 'method' => 'get', + 'pattern' => '/', + 'handler' => Container::HELLO_ACTION, + ], +]; + +foreach ($routes as $route) { + $method = $route['method']; + $pattern = $route['pattern']; + $handler = $route['handler']; + + $application->$method( + $pattern, + function () use ($container, $handler) { + $action = $container->get($handler); + + $action(); + } + ); +} + +$application->notFound( + function () { + echo "404 - Not Found - " . date("Y-m-d H:i:s"); + } +); + + +/** @var string $uri */ +$uri = $_SERVER['REQUEST_URI'] ?? ''; + +$application->handle($uri); From 74bec607a44099fb960fa7d44a3d9f565613990c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 9 Sep 2025 08:20:19 -0500 Subject: [PATCH 26/36] [#.x] - added action/domain/responder classes --- src/Action/Hello/GetAction.php | 38 ++++++++++++++++++++++ src/Domain/Hello/HelloService.php | 24 ++++++++++++++ src/Responder/Hello/HelloTextResponder.php | 31 ++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 src/Action/Hello/GetAction.php create mode 100644 src/Domain/Hello/HelloService.php create mode 100644 src/Responder/Hello/HelloTextResponder.php diff --git a/src/Action/Hello/GetAction.php b/src/Action/Hello/GetAction.php new file mode 100644 index 0000000..473645a --- /dev/null +++ b/src/Action/Hello/GetAction.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Action\Hello; + +use Phalcon\Api\Domain\Hello\HelloService; +use Phalcon\Api\Responder\Hello\HelloTextResponder; +use Phalcon\Http\Response; + +final class GetAction +{ + public function __construct( + private readonly HelloService $service, + private readonly HelloTextResponder $responder + ) { + } + + public function __invoke(): void + { + $service = $this->service; + $responder = $this->responder; + + $serviceOutput = $service(); + $outputResponse = $responder($serviceOutput); + + $outputResponse->send(); + } +} diff --git a/src/Domain/Hello/HelloService.php b/src/Domain/Hello/HelloService.php new file mode 100644 index 0000000..7b66a1f --- /dev/null +++ b/src/Domain/Hello/HelloService.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Hello; + +use function date; + +final class HelloService +{ + public function __invoke(): string + { + return "Hello World!!! - " . date("Y-m-d H:i:s"); + } +} diff --git a/src/Responder/Hello/HelloTextResponder.php b/src/Responder/Hello/HelloTextResponder.php new file mode 100644 index 0000000..2ee5a54 --- /dev/null +++ b/src/Responder/Hello/HelloTextResponder.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Responder\Hello; + +use Phalcon\Http\Response; + +final class HelloTextResponder +{ + public function __construct( + private Response $response + ) { + } + + public function __invoke(string $payload): Response + { + $this->response->setContent($payload); + + return $this->response; + } +} From cf4ebc59e5a225546bed8303048f63dc03c9c601 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 9 Sep 2025 08:20:51 -0500 Subject: [PATCH 27/36] [#.x] - removing gitkeep --- src/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/.gitkeep diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 From 1c16c209a7e97b33c2ce7a767fea3788f3943859 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:40:21 -0500 Subject: [PATCH 28/36] [#.x] - added exception classes --- src/Action/Hello/GetAction.php | 38 ------------------- .../InvalidConfigurationArgumentException.php | 18 +++++++++ src/Responder/Hello/HelloTextResponder.php | 31 --------------- 3 files changed, 18 insertions(+), 69 deletions(-) delete mode 100644 src/Action/Hello/GetAction.php create mode 100644 src/Domain/Exceptions/InvalidConfigurationArgumentException.php delete mode 100644 src/Responder/Hello/HelloTextResponder.php diff --git a/src/Action/Hello/GetAction.php b/src/Action/Hello/GetAction.php deleted file mode 100644 index 473645a..0000000 --- a/src/Action/Hello/GetAction.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Phalcon\Api\Action\Hello; - -use Phalcon\Api\Domain\Hello\HelloService; -use Phalcon\Api\Responder\Hello\HelloTextResponder; -use Phalcon\Http\Response; - -final class GetAction -{ - public function __construct( - private readonly HelloService $service, - private readonly HelloTextResponder $responder - ) { - } - - public function __invoke(): void - { - $service = $this->service; - $responder = $this->responder; - - $serviceOutput = $service(); - $outputResponse = $responder($serviceOutput); - - $outputResponse->send(); - } -} diff --git a/src/Domain/Exceptions/InvalidConfigurationArgumentException.php b/src/Domain/Exceptions/InvalidConfigurationArgumentException.php new file mode 100644 index 0000000..8e9b0cd --- /dev/null +++ b/src/Domain/Exceptions/InvalidConfigurationArgumentException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Exceptions; + +class InvalidConfigurationArgumentException extends \InvalidArgumentException +{ +} diff --git a/src/Responder/Hello/HelloTextResponder.php b/src/Responder/Hello/HelloTextResponder.php deleted file mode 100644 index 2ee5a54..0000000 --- a/src/Responder/Hello/HelloTextResponder.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Phalcon\Api\Responder\Hello; - -use Phalcon\Http\Response; - -final class HelloTextResponder -{ - public function __construct( - private Response $response - ) { - } - - public function __invoke(string $payload): Response - { - $this->response->setContent($payload); - - return $this->response; - } -} From a01332327a59965c03152e6fb17f1e08c7439ffc Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:40:40 -0500 Subject: [PATCH 29/36] [#.x] - added interfaces for ADR --- src/Domain/Interfaces/ActionInterface.php | 19 +++++++++++++++++ src/Domain/Interfaces/DomainInterface.php | 21 +++++++++++++++++++ src/Domain/Interfaces/ResponderInterface.php | 22 ++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/Domain/Interfaces/ActionInterface.php create mode 100644 src/Domain/Interfaces/DomainInterface.php create mode 100644 src/Domain/Interfaces/ResponderInterface.php diff --git a/src/Domain/Interfaces/ActionInterface.php b/src/Domain/Interfaces/ActionInterface.php new file mode 100644 index 0000000..bb74052 --- /dev/null +++ b/src/Domain/Interfaces/ActionInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Interfaces; + +interface ActionInterface +{ + public function __invoke(): void; +} diff --git a/src/Domain/Interfaces/DomainInterface.php b/src/Domain/Interfaces/DomainInterface.php new file mode 100644 index 0000000..ef9d164 --- /dev/null +++ b/src/Domain/Interfaces/DomainInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Interfaces; + +use Phalcon\Domain\Payload; + +interface DomainInterface +{ + public function __invoke(): Payload; +} diff --git a/src/Domain/Interfaces/ResponderInterface.php b/src/Domain/Interfaces/ResponderInterface.php new file mode 100644 index 0000000..d15b29f --- /dev/null +++ b/src/Domain/Interfaces/ResponderInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Interfaces; + +use Phalcon\Domain\Payload; +use Phalcon\Http\ResponseInterface; + +interface ResponderInterface +{ + public function __invoke(Payload $payload): ResponseInterface; +} From 09877daa75a16a90c6ce4b732d8c5f436779768e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:41:03 -0500 Subject: [PATCH 30/36] [#.x] - moved actions to actionHandler --- src/Domain/Services/ActionHandler.php | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/Domain/Services/ActionHandler.php diff --git a/src/Domain/Services/ActionHandler.php b/src/Domain/Services/ActionHandler.php new file mode 100644 index 0000000..6577ec4 --- /dev/null +++ b/src/Domain/Services/ActionHandler.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services; + +use Phalcon\Api\Domain\Interfaces\ActionInterface; +use Phalcon\Api\Domain\Interfaces\DomainInterface; +use Phalcon\Api\Domain\Interfaces\ResponderInterface; + +final readonly class ActionHandler implements ActionInterface +{ + public function __construct( + private DomainInterface $service, + private ResponderInterface $responder + ) { + } + + public function __invoke(): void + { + $this->responder->__invoke( + $this->service->__invoke() + ); + } +} From f8a1179d8c8f304fab430676b1cbac1ced22318b Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:41:18 -0500 Subject: [PATCH 31/36] [#.x] - new json responder --- src/Responder/JsonResponder.php | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/Responder/JsonResponder.php diff --git a/src/Responder/JsonResponder.php b/src/Responder/JsonResponder.php new file mode 100644 index 0000000..9783832 --- /dev/null +++ b/src/Responder/JsonResponder.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Responder; + +use DateTimeImmutable; +use DateTimeZone; +use Phalcon\Api\Domain\Interfaces\ResponderInterface; +use Phalcon\Domain\Payload; +use Phalcon\Http\ResponseInterface; + +use function json_encode; + +final class JsonResponder implements ResponderInterface +{ + public function __construct( + private ResponseInterface $response + ) { + } + + public function __invoke(Payload $payload): ResponseInterface + { + $result = $payload->getResult(); + /** @var string $content */ + $content = $result['results']; + + $timestamp = new DateTimeImmutable('now', new DateTimeZone('UTC')); + $dateTime = $timestamp->format('Y-m-d H:i:s'); + $output = [ + 'data' => [ + $content + ], + 'errors' => [], + 'meta' => [ + 'code' => 200, + 'hash' => '', + 'message' => 'success', + 'timestamp' => $dateTime, + ] + ]; + + $dataErrors = [ + 'data' => $output['data'], + 'errors' => $output['errors'], + ]; + $encoded = json_encode($dataErrors); + $encoded = (false === $encoded) ? '' : $encoded; + $hash = sha1($dateTime . $encoded); + $eTag = sha1($encoded); + + $output['meta']['hash'] = $hash; + + $this + ->response + ->setContentType('application/json') + ->setHeader('E-Tag', $eTag) + ->setJsonContent($output) + ; + + return $this->response; + } +} From 09412b121e234a0d8f1b0ff22aa574de177ea85b Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:41:37 -0500 Subject: [PATCH 32/36] [#.x] - response sender for sendin responses back --- src/Domain/Middleware/ResponseSender.php | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/Domain/Middleware/ResponseSender.php diff --git a/src/Domain/Middleware/ResponseSender.php b/src/Domain/Middleware/ResponseSender.php new file mode 100644 index 0000000..616d1c0 --- /dev/null +++ b/src/Domain/Middleware/ResponseSender.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Middleware; + +use Phalcon\Api\Domain\Interfaces\ActionInterface; +use Phalcon\Api\Domain\Interfaces\DomainInterface; +use Phalcon\Api\Domain\Interfaces\ResponderInterface; +use Phalcon\Http\ResponseInterface; + +final readonly class ResponseSender +{ + public function __invoke(ResponseInterface $response): ResponseInterface + { + return $response->send(); + } +} From 681b9128dd5a5663b5f13fb03bebfefbcda61176 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:42:03 -0500 Subject: [PATCH 33/36] [#.x] - reworked container and domain using payload --- src/Domain/Hello/HelloService.php | 15 +++- src/Domain/Services/Container.php | 31 ++------ .../Env/Adapters/AdapterInterface.php | 19 +++++ src/Domain/Services/Env/Adapters/DotEnv.php | 44 +++++++++++ src/Domain/Services/Env/EnvFactory.php | 49 ++++++++++++ src/Domain/Services/Env/EnvManager.php | 77 +++++++++++++++++++ 6 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 src/Domain/Services/Env/Adapters/AdapterInterface.php create mode 100644 src/Domain/Services/Env/Adapters/DotEnv.php create mode 100644 src/Domain/Services/Env/EnvFactory.php create mode 100644 src/Domain/Services/Env/EnvManager.php diff --git a/src/Domain/Hello/HelloService.php b/src/Domain/Hello/HelloService.php index 7b66a1f..289ddc4 100644 --- a/src/Domain/Hello/HelloService.php +++ b/src/Domain/Hello/HelloService.php @@ -13,12 +13,21 @@ namespace Phalcon\Api\Domain\Hello; +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Interfaces\DomainInterface; +use Phalcon\Domain\Payload; + use function date; -final class HelloService +final class HelloService implements DomainInterface { - public function __invoke(): string + public function __invoke(): Payload { - return "Hello World!!! - " . date("Y-m-d H:i:s"); + return new Payload( + DomainStatus::SUCCESS, + [ + 'results' => "Hello World!!! - " . date("Y-m-d H:i:s") + ] + ); } } diff --git a/src/Domain/Services/Container.php b/src/Domain/Services/Container.php index 554cf1f..82d6870 100644 --- a/src/Domain/Services/Container.php +++ b/src/Domain/Services/Container.php @@ -16,6 +16,8 @@ use Phalcon\Api\Action\Hello\GetAction; use Phalcon\Api\Domain\Hello\HelloService; use Phalcon\Api\Responder\Hello\HelloTextResponder; +use Phalcon\Api\Responder\HelloJsonResponder; +use Phalcon\Api\Responder\JsonResponder; use Phalcon\Di\Di; use Phalcon\Di\Service; use Phalcon\Filter\FilterFactory; @@ -43,9 +45,8 @@ class Container extends Di /** * Hello */ - public const HELLO_ACTION = 'hello.action'; public const HELLO_SERVICE = 'hello.service'; - public const HELLO_RESPONDER_TEXT = 'hello.responder.text'; + public const HELLO_RESPONDER_JSON = 'hello.responder.json'; public function __construct() { @@ -55,9 +56,8 @@ public function __construct() self::RESPONSE => $this->getServiceSimple(Response::class, true), self::ROUTER => $this->getServiceRouter(), - self::HELLO_ACTION => $this->getServiceHelloAction(), self::HELLO_SERVICE => $this->getServiceSimple(HelloService::class), - self::HELLO_RESPONDER_TEXT => $this->getServiceHelloResponderText(), + self::HELLO_RESPONDER_JSON => $this->getServiceResponderJson(), ]; parent::__construct(); @@ -94,30 +94,11 @@ private function getServiceRouter(): Service ); } - private function getServiceHelloAction(): Service + private function getServiceResponderJson(): Service { return new Service( [ - 'className' => GetAction::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::HELLO_SERVICE, - ], - [ - 'type' => 'service', - 'name' => self::HELLO_RESPONDER_TEXT, - ] - ] - ] - ); - } - - private function getServiceHelloResponderText(): Service - { - return new Service( - [ - 'className' => HelloTextResponder::class, + 'className' => JsonResponder::class, 'arguments' => [ [ 'type' => 'service', diff --git a/src/Domain/Services/Env/Adapters/AdapterInterface.php b/src/Domain/Services/Env/Adapters/AdapterInterface.php new file mode 100644 index 0000000..ae377ad --- /dev/null +++ b/src/Domain/Services/Env/Adapters/AdapterInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services\Env\Adapters; + +interface AdapterInterface +{ + public function load(array $options): array; +} diff --git a/src/Domain/Services/Env/Adapters/DotEnv.php b/src/Domain/Services/Env/Adapters/DotEnv.php new file mode 100644 index 0000000..1d0b64b --- /dev/null +++ b/src/Domain/Services/Env/Adapters/DotEnv.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services\Env\Adapters; + +use Dotenv\Dotenv as ParentDotEnv; +use Exception; +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; + +class DotEnv implements AdapterInterface +{ + /** + * @param array $options + * + * @return array + * @throws Exception + */ + public function load(array $options): array + { + /** @var string|null $filePath */ + $filePath = $options['filePath'] ?? null; + if (true === empty($filePath) || true !== file_exists($filePath)) { + throw new InvalidConfigurationArgumentException( + 'The .env file does not exist at the specified path: ' + . (string)$filePath + ); + } + + $dotenv = ParentDotEnv::createImmutable($filePath); + $dotenv->load(); + + return $_ENV; + } +} diff --git a/src/Domain/Services/Env/EnvFactory.php b/src/Domain/Services/Env/EnvFactory.php new file mode 100644 index 0000000..5125ce2 --- /dev/null +++ b/src/Domain/Services/Env/EnvFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services\Env; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Services\Env\Adapters\AdapterInterface; +use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; + +class EnvFactory +{ + protected array $instances = []; + + public function newInstance(string $name, mixed ...$parameters): AdapterInterface + { + $adapters = $this->getAdapters(); + if (true !== isset($this->instances[$name])) { + if (true !== isset($adapters[$name])) { + throw new InvalidConfigurationArgumentException( + 'Service ' . $name . ' is not registered' + ); + } + + $definition = $adapters[$name]; + /** @var AdapterInterface $instance */ + $instance = new $definition(...$parameters); + $this->instances[$name] = $instance; + } + + return $this->instances[$name]; + } + + protected function getAdapters(): array + { + return [ + 'dotenv' => DotEnv::class, + ]; + } +} diff --git a/src/Domain/Services/Env/EnvManager.php b/src/Domain/Services/Env/EnvManager.php new file mode 100644 index 0000000..1e76fac --- /dev/null +++ b/src/Domain/Services/Env/EnvManager.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\Services\Env; + +use function array_merge; +use function getenv; + +class EnvManager +{ + private static bool $isLoaded = false; + private static array $settings = []; + + public static function appPath(string $path = ''): string + { + return dirname(__DIR__, 4) + . ($path ? DIRECTORY_SEPARATOR . $path : $path) + ; + } + + public static function get( + string $key, + bool | int | string | null $defaultValue = null + ): bool | int | string | null { + self::load(); + + return self::$settings[$key] ?? $defaultValue; + } + + private static function load(): void + { + if (true !== self::$isLoaded) { + self::$isLoaded = true; + + $envFactory = new EnvFactory(); + $options = self::getOptions(); + $adapter = $options['adapter']; + + $envs = array_merge(getenv(), $_ENV); + $options = $envFactory->newInstance($adapter)->load($options); + $envs = array_merge($envs, $options); + + self::$settings = array_map( + function ($value) { + return match ($value) { + 'true' => true, + 'false' => false, + default => $value, + }; + }, + $envs + ); + } + } + + private static function getOptions(): array + { + $envs = array_merge(getenv(), $_ENV); + $adapter = $envs['APP_ENV_ADAPTER'] ?? 'dotenv'; + $filePath = $envs['APP_ENV_FILE_PATH'] ?? ''; + + return [ + 'adapter' => $adapter, + 'filePath' => $filePath, + ]; + } +} From 243e08114e7b5a91c9ea9b64331f4a7475b99e49 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:42:19 -0500 Subject: [PATCH 34/36] [#.x] - adjustments to use the new services --- public/index.php | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/public/index.php b/public/index.php index c5507cb..d20791f 100644 --- a/public/index.php +++ b/public/index.php @@ -2,6 +2,10 @@ declare(strict_types=1); +use Phalcon\Api\Domain\Interfaces\DomainInterface; +use Phalcon\Api\Domain\Interfaces\ResponderInterface; +use Phalcon\Api\Domain\Middleware\ResponseSender; +use Phalcon\Api\Domain\Services\ActionHandler; use Phalcon\Api\Domain\Services\Container; use Phalcon\Mvc\Micro; @@ -15,27 +19,42 @@ */ $routes = [ [ - 'method' => 'get', - 'pattern' => '/', - 'handler' => Container::HELLO_ACTION, + 'method' => 'get', + 'pattern' => '/', + 'service' => Container::HELLO_SERVICE, + 'responder' => Container::HELLO_RESPONDER_JSON, ], ]; foreach ($routes as $route) { - $method = $route['method']; - $pattern = $route['pattern']; - $handler = $route['handler']; + $method = $route['method']; + $pattern = $route['pattern']; + $serviceName = $route['service']; + $responderName = $route['responder']; $application->$method( $pattern, - function () use ($container, $handler) { - $action = $container->get($handler); + function () use ($container, $serviceName, $responderName) { + /** @var DomainInterface $service */ + $service = $container->get($serviceName); + /** @var ResponderInterface $responder */ + $responder = $container->get($responderName); - $action(); + $action = new ActionHandler($service, $responder); + $action->__invoke(); } ); } +$application->finish( + function () use ($container) { + $response = $container->getShared(Container::RESPONSE); + $sender = new ResponseSender(); + + $sender->__invoke($response); + } +); + $application->notFound( function () { echo "404 - Not Found - " . date("Y-m-d H:i:s"); From 37564350dfa59c8ff31113dc0474681fcfc300e8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 13 Sep 2025 14:42:29 -0500 Subject: [PATCH 35/36] [#.x] - added tests for env manager --- tests/Unit/AbstractUnitTestCase.php | 29 ++++++ .../Services/Env/Adapters/DotEnvTest.php | 92 +++++++++++++++++++ .../Domain/Services/Env/EnvFactoryTest.php | 42 +++++++++ .../Domain/Services/Env/EnvManagerTest.php | 77 ++++++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 tests/Unit/AbstractUnitTestCase.php create mode 100644 tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php create mode 100644 tests/Unit/Domain/Services/Env/EnvFactoryTest.php create mode 100644 tests/Unit/Domain/Services/Env/EnvManagerTest.php diff --git a/tests/Unit/AbstractUnitTestCase.php b/tests/Unit/AbstractUnitTestCase.php new file mode 100644 index 0000000..d248af8 --- /dev/null +++ b/tests/Unit/AbstractUnitTestCase.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit; + +use PHPUnit\Framework\TestCase; + +abstract class AbstractUnitTestCase extends TestCase +{ + /** + * Return a long series of strings to be used as a password + * + * @return string + */ + public function getStrongPassword(): string + { + return substr(base64_encode(random_bytes(512)), 0, 128); + } +} diff --git a/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php new file mode 100644 index 0000000..4171d27 --- /dev/null +++ b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\Services\Env\Adapters; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Services\Env\EnvFactory; +use Phalcon\Api\Domain\Services\Env\EnvManager; +use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; + +final class DotEnvTest extends AbstractUnitTestCase +{ + private string $envFile; + + protected function setUp(): void + { + $this->envFile = EnvManager::appPath() + . '/tests/Fixtures/Domain/Services/Env/' + ; + } + + public function testLoadSuccess(): void + { + $dotEnv = new DotEnv(); + $options = [ + 'filePath' => $this->envFile, + ]; + + $expected = [ + 'SAMPLE_STRING' => 'sample_value', + 'SAMPLE_INT' => '1', + 'SAMPLE_TRUE' => 'true', + 'SAMPLE_FALSE' => 'false', + ]; + $actual = $dotEnv->load($options); + + $this->assertArrayHasKey('SAMPLE_STRING', $actual); + $this->assertArrayHasKey('SAMPLE_INT', $actual); + $this->assertArrayHasKey('SAMPLE_TRUE', $actual); + $this->assertArrayHasKey('SAMPLE_FALSE', $actual); + + $actualArray = [ + 'SAMPLE_STRING' => $actual['SAMPLE_STRING'], + 'SAMPLE_INT' => $actual['SAMPLE_INT'], + 'SAMPLE_TRUE' => $actual['SAMPLE_TRUE'], + 'SAMPLE_FALSE' => $actual['SAMPLE_FALSE'], + ]; + + $this->assertSame($expected, $actualArray); + } + + public function testLoadExceptionForEmptyFilePath(): void + { + $this->expectException(InvalidConfigurationArgumentException::class); + $this->expectExceptionMessage( + 'The .env file does not exist at the specified path' + ); + + $dotEnv = new DotEnv(); + $options = [ + 'filePath' => '', + ]; + + $dotEnv->load($options); + } + + public function testLoadExceptionForMissingFile(): void + { + $this->expectException(InvalidConfigurationArgumentException::class); + $this->expectExceptionMessage( + 'The .env file does not exist at the specified path' + ); + + $dotEnv = new DotEnv(); + $options = [ + 'filePath' => '/does/not/exist/', + ]; + + $dotEnv->load($options); + } +} diff --git a/tests/Unit/Domain/Services/Env/EnvFactoryTest.php b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php new file mode 100644 index 0000000..94cc4b8 --- /dev/null +++ b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\Services\Env; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Services\Env\EnvFactory; +use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; + +final class EnvFactoryTest extends AbstractUnitTestCase +{ + public function testLoad(): void + { + $factory = new EnvFactory(); + $dotEnv = $factory->newInstance('dotenv'); + + $class = DotEnv::class; + $this->assertInstanceOf($class, $dotEnv); + } + + public function testUnknownService(): void + { + $this->expectException(InvalidConfigurationArgumentException::class); + $this->expectExceptionMessage( + 'Service unknown is not registered' + ); + + $factory = new EnvFactory(); + $factory->newInstance('unknown'); + } +} diff --git a/tests/Unit/Domain/Services/Env/EnvManagerTest.php b/tests/Unit/Domain/Services/Env/EnvManagerTest.php new file mode 100644 index 0000000..f39c493 --- /dev/null +++ b/tests/Unit/Domain/Services/Env/EnvManagerTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\Services\Env; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Services\Env\EnvFactory; +use Phalcon\Api\Domain\Services\Env\EnvManager; +use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Container\Lazy\Env; +use PHPUnit\Framework\Attributes\BackupGlobals; +use ReflectionClass; + +#[BackupGlobals(true)] +final class EnvManagerTest extends AbstractUnitTestCase +{ + protected function setUp(): void + { + $ref = new ReflectionClass(EnvManager::class); + $ref->setStaticPropertyValue('isLoaded', false); + $ref->setStaticPropertyValue('settings', []); + } + + public function testAppPathReturnsRoot(): void + { + $expected = dirname(__DIR__, 5); + $actual = EnvManager::appPath(); + $this->assertSame($expected, $actual); + } + + public function testGetFromDotEnvLoad(): void + { + $_ENV = [ + 'APP_ENV_ADAPTER' => 'dotenv', + 'APP_ENV_FILE_PATH' => EnvManager::appPath() + . '/tests/Fixtures/Domain/Services/Env/' + ]; + + $values = [ + 'SAMPLE_STRING' => 'sample_value', + 'SAMPLE_INT' => '1', + 'SAMPLE_TRUE' => true, + 'SAMPLE_FALSE' => false, + ]; + + $expected = 'default_value'; + $actual = EnvManager::get('NON_EXISTENT', 'default_value'); + $this->assertSame($expected, $actual); + + $expected = $values['SAMPLE_STRING']; + $actual = EnvManager::get('SAMPLE_STRING'); + $this->assertSame($expected, $actual); + + $expected = $values['SAMPLE_INT']; + $actual = EnvManager::get('SAMPLE_INT'); + $this->assertSame($expected, $actual); + + $expected = $values['SAMPLE_TRUE']; + $actual = EnvManager::get('SAMPLE_TRUE'); + $this->assertSame($expected, $actual); + + $expected = $values['SAMPLE_FALSE']; + $actual = EnvManager::get('SAMPLE_FALSE'); + $this->assertSame($expected, $actual); + } +} From 181be6bdd24a5d3ce996fc292df0133951e38214 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Mon, 15 Sep 2025 10:04:08 -0500 Subject: [PATCH 36/36] [#.x] - added .env for tests --- tests/Fixtures/Domain/Services/Env/.env | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tests/Fixtures/Domain/Services/Env/.env diff --git a/tests/Fixtures/Domain/Services/Env/.env b/tests/Fixtures/Domain/Services/Env/.env new file mode 100644 index 0000000..b707f0d --- /dev/null +++ b/tests/Fixtures/Domain/Services/Env/.env @@ -0,0 +1,4 @@ +SAMPLE_STRING=sample_value +SAMPLE_INT=1 +SAMPLE_TRUE=true +SAMPLE_FALSE=false