From 701736c5cabf885faf071243930ab95c791777e7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:42:02 -0500 Subject: [PATCH 01/47] add parameterized docker environment under resources/ --- resources/.env.example | 33 ++++++++++++ resources/docker/.bashrc | 74 +++++++++++++++++++++++++++ resources/docker/Dockerfile | 79 +++++++++++++++++++++++++++++ resources/docker/extra.ini | 6 +++ resources/docker/nginx/default.conf | 22 ++++++++ 5 files changed, 214 insertions(+) create mode 100644 resources/.env.example create mode 100644 resources/docker/.bashrc create mode 100644 resources/docker/Dockerfile create mode 100644 resources/docker/extra.ini create mode 100644 resources/docker/nginx/default.conf diff --git a/resources/.env.example b/resources/.env.example new file mode 100644 index 00000000..f5318f8a --- /dev/null +++ b/resources/.env.example @@ -0,0 +1,33 @@ +# Docker / project +PROJECT_PREFIX=rest-api +APP_PORT=8080 +PHP_VERSION=8.4 +PHALCON_VARIANT=v5 +# Override these to match your host user if it is not 1000:1000 +UID=1000 +GID=1000 + +# Application +APP_DEBUG=true +APP_ENV=development +APP_URL=http://localhost:8080 +APP_NAME="Phalcon API" +APP_BASE_URI=/ +APP_SUPPORT_EMAIL=team@phalcon.io +APP_TIMEZONE=UTC +APP_IP=127.0.0.1 + +# Database (host is the compose service name) +DATA_API_MYSQL_HOST=mysql +DATA_API_MYSQL_USER=root +DATA_API_MYSQL_PASS=secret +DATA_API_MYSQL_NAME=phalcon_api + +# Cache (host is the compose service name) +DATA_API_REDIS_HOST=redis +CACHE_PREFIX=api_cache_ +CACHE_LIFETIME=86400 + +TOKEN_AUDIENCE=https://phalcon.io +LOGGER_DEFAULT_FILENAME=api +VERSION=20180401 diff --git a/resources/docker/.bashrc b/resources/docker/.bashrc new file mode 100644 index 00000000..9fafb48e --- /dev/null +++ b/resources/docker/.bashrc @@ -0,0 +1,74 @@ +#!/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' + +# Make project binaries available +PATH=$PATH:./vendor/bin +export PATH diff --git a/resources/docker/Dockerfile b/resources/docker/Dockerfile new file mode 100644 index 00000000..de0f668e --- /dev/null +++ b/resources/docker/Dockerfile @@ -0,0 +1,79 @@ +# syntax=docker/dockerfile:1 +ARG PHP_VERSION=8.4 + +FROM php:${PHP_VERSION}-fpm AS base + +ARG PHP_VERSION=8.4 +ARG PHALCON_VARIANT=v5 +ARG PHALCON_V5_CONSTRAINT="^5.0" +ARG UID=1000 +ARG GID=1000 +ARG USER=phalcon +ARG GROUP=phalcon + +# System packages, PHP extensions and an unprivileged user matching the host UID +RUN < Date: Tue, 14 Jul 2026 22:43:20 -0500 Subject: [PATCH 02/47] move entry point to public/ per pds/skeleton --- .htrouter.php | 2 +- {api/public => public}/.htaccess | 0 {api/public => public}/favicon.ico | Bin {api/public => public}/index.php | 2 +- resources/docker/nginx/default.conf | 6 ++++++ tests/unit/BootstrapCest.php | 2 +- 6 files changed, 9 insertions(+), 3 deletions(-) rename {api/public => public}/.htaccess (100%) rename {api/public => public}/favicon.ico (100%) rename {api/public => public}/index.php (83%) diff --git a/.htrouter.php b/.htrouter.php index 302df6a5..ed41e815 100644 --- a/.htrouter.php +++ b/.htrouter.php @@ -10,4 +10,4 @@ $_GET['_url'] = $_SERVER['REQUEST_URI']; -require_once __DIR__ . '/api/public/index.php'; +require_once __DIR__ . '/public/index.php'; diff --git a/api/public/.htaccess b/public/.htaccess similarity index 100% rename from api/public/.htaccess rename to public/.htaccess diff --git a/api/public/favicon.ico b/public/favicon.ico similarity index 100% rename from api/public/favicon.ico rename to public/favicon.ico diff --git a/api/public/index.php b/public/index.php similarity index 83% rename from api/public/index.php rename to public/index.php index dbca2f0e..dbedab8b 100644 --- a/api/public/index.php +++ b/public/index.php @@ -12,6 +12,6 @@ use Phalcon\Api\Bootstrap\Api; -require_once __DIR__ . '/../../library/Core/autoload.php'; +require_once dirname(__DIR__) . '/library/Core/autoload.php'; (new Api())->run(); diff --git a/resources/docker/nginx/default.conf b/resources/docker/nginx/default.conf index 998c9caf..4d3fa614 100644 --- a/resources/docker/nginx/default.conf +++ b/resources/docker/nginx/default.conf @@ -19,4 +19,10 @@ server { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } + + # public/.htaccess is an Apache artifact and inert here, but the docroot is + # served as static files; without this it would be readable over HTTP. + location ~ /\.ht { + deny all; + } } diff --git a/tests/unit/BootstrapCest.php b/tests/unit/BootstrapCest.php index 565383e9..d0e0f39a 100644 --- a/tests/unit/BootstrapCest.php +++ b/tests/unit/BootstrapCest.php @@ -12,7 +12,7 @@ class BootstrapCest public function checkBootstrap(CliTester $I) { ob_start(); - require appPath('api/public/index.php'); + require appPath('public/index.php'); $actual = ob_get_contents(); ob_end_clean(); From 3e2a010c1342786a47070b7e9ee8e09f7a1d020f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:45:40 -0500 Subject: [PATCH 03/47] consolidate compose into single parameterized stack --- README.md | 20 +++++-- docker-compose.yml | 132 +++++++++++++++++---------------------------- 2 files changed, 64 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index d6063a9e..73ceb646 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,23 @@ Sample API using Phalcon Implementation of an API application using the Phalcon Framework [https://phalcon.io](https://phalcon.io) ### Installation -- Clone the project -- In the project folder run `nanobox run php-server` -- Hit the IP address with postman -**NOTE** This requires [nanobox](https://nanobox.io) to be present in your system. Visit their site for installation instructions. +Requires [Docker](https://docs.docker.com/get-docker/) with the Compose plugin. + +- Clone the project +- Copy the environment template: `cp resources/.env.example .env` +- If your host user is not `1000:1000`, set `UID` and `GID` in `.env` to match the output of `id -u` and `id -g` +- Start the stack: `docker compose up -d --build` +- Install the dependencies: `docker compose exec app composer install` +- Hit `http://localhost:8080` with postman + +The project directory is mounted at `/srv` inside the container, which masks the +`vendor/` directory baked into the image — hence the `composer install` step +above. It only needs repeating when `composer.lock` changes. + +The host port is configurable via `APP_PORT` in `.env`, so this app can run +alongside the other Phalcon sample applications. `PHP_VERSION` (8.1 minimum) and +`PHALCON_VARIANT` select the PHP and Phalcon versions the image is built with. ### Features ##### JWT Tokens diff --git a/docker-compose.yml b/docker-compose.yml index 1f75fe56..eebdee0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,95 +1,59 @@ -version: '3' services: - - # Application - REST API 8.0 - application-8.0: - build: docker/8.0 - container_name: rest-api-app-8.0 - restart: unless-stopped - tty: true - working_dir: /var/www - depends_on: - - nginx-8.0 - - redis - - mysql + app: + build: + context: . + dockerfile: resources/docker/Dockerfile + args: + PHP_VERSION: ${PHP_VERSION:-8.4} + PHALCON_VARIANT: ${PHALCON_VARIANT:-v5} + UID: ${UID:-1000} + GID: ${GID:-1000} + container_name: ${PROJECT_PREFIX:-rest-api}-app + hostname: ${PROJECT_PREFIX:-rest-api}-app volumes: - - ./:/var/www - - ./storage/config/extra.ini:/usr/local/etc/php/conf.d/extra.ini - networks: - - rest-api-network - - # Application - REST API 8.1 - application-8.1: - build: docker/8.1 - container_name: rest-api-app-8.1 - restart: unless-stopped - tty: true - working_dir: /var/www + - .:/srv + env_file: .env depends_on: - - nginx-8.1 - - redis - - mysql - volumes: - - ./:/var/www - - ./storage/config/extra.ini:/usr/local/etc/php/conf.d/extra.ini - networks: - - rest-api-network - - # Webserver - nginX 8.0 - nginx-8.0: - image: nginx:alpine - container_name: rest-api-nginx-8.0 - restart: unless-stopped - tty: true - volumes: - - ./:/var/www - - ./storage/config/nginx/8.0/:/etc/nginx/conf.d/ - networks: - - rest-api-network + mysql: + condition: service_healthy + redis: + condition: service_healthy - # Webserver - nginX 8.0 - nginx-8.1: + nginx: image: nginx:alpine - container_name: rest-api-nginx-8.1 - restart: unless-stopped - tty: true + container_name: ${PROJECT_PREFIX:-rest-api}-nginx + hostname: ${PROJECT_PREFIX:-rest-api}-nginx + ports: + # Host port is configurable (APP_PORT) so multiple apps can coexist; + # the container always listens on 80. + - "${APP_PORT:-8080}:80" volumes: - - ./:/var/www - - ./storage/config/nginx/8.1/:/etc/nginx/conf.d/ - networks: - - rest-api-network + - .:/srv + - ./resources/docker/nginx/default.conf:/etc/nginx/conf.d/default.conf + depends_on: + - app - # Database - Mysql mysql: - image: mysql:5.7.22 - container_name: rest-api-mysql - restart: unless-stopped - tty: true + image: mysql:8.0 + container_name: ${PROJECT_PREFIX:-rest-api}-mysql environment: - - MYSQL_ROOT_PASSWORD=secret - - MYSQL_USER=phalcon - - MYSQL_DATABASE=phalcon_api - - MYSQL_PASSWORD=secret - volumes: - - rest-api-volume:/var/lib/mysql/ - - ./storage/config/my.cnf:/etc/mysql/my.cnf - networks: - - rest-api-network + MYSQL_ROOT_PASSWORD: ${DATA_API_MYSQL_PASS:-secret} + MYSQL_DATABASE: ${DATA_API_MYSQL_NAME:-phalcon_api} + # No host port published: the app reaches the DB over the internal compose + # network as mysql:3306. Not exposed to the host by design. + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${DATA_API_MYSQL_PASS:-secret}"] + interval: 5s + timeout: 5s + retries: 12 - # Cache - Redis redis: - container_name: rest-api-cache - image: redis:5-alpine - restart: "always" - networks: - - rest-api-network - -# Network -networks: - rest-api-network: - driver: bridge - -# Volumes -volumes: - rest-api-volume: - driver: local + image: redis:alpine + container_name: ${PROJECT_PREFIX:-rest-api}-redis + # No host port published: the app reaches the cache over the internal + # compose network as redis:6379. Not exposed to the host by design. + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 12 From aebf7ebb2706d643b77f8d6a368f5994b7690ac6 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:46:16 -0500 Subject: [PATCH 04/47] remove superseded 8.0/8.1 docker stack --- docker/8.0/Dockerfile | 70 ------------------------- docker/8.0/config/.bashrc | 75 --------------------------- docker/8.0/config/extra.ini | 8 --- docker/8.1/Dockerfile | 70 ------------------------- docker/8.1/config/.bashrc | 75 --------------------------- docker/8.1/config/extra.ini | 8 --- storage/config/extra.ini | 8 --- storage/config/my.cnf | 3 -- storage/config/nginx/8.0/app-8.0.conf | 20 ------- storage/config/nginx/8.1/app-8.1.conf | 20 ------- 10 files changed, 357 deletions(-) delete mode 100644 docker/8.0/Dockerfile delete mode 100644 docker/8.0/config/.bashrc delete mode 100644 docker/8.0/config/extra.ini delete mode 100644 docker/8.1/Dockerfile delete mode 100644 docker/8.1/config/.bashrc delete mode 100644 docker/8.1/config/extra.ini delete mode 100644 storage/config/extra.ini delete mode 100644 storage/config/my.cnf delete mode 100644 storage/config/nginx/8.0/app-8.0.conf delete mode 100644 storage/config/nginx/8.1/app-8.1.conf diff --git a/docker/8.0/Dockerfile b/docker/8.0/Dockerfile deleted file mode 100644 index 82cde1c1..00000000 --- a/docker/8.0/Dockerfile +++ /dev/null @@ -1,70 +0,0 @@ -FROM composer:latest as composer -FROM php:8.0-fpm - -# Set working directory -WORKDIR /var/www - -LABEL vendor="Phalcon" \ - maintainer="Phalcon Team " \ - description="The PHP image to test the REST API example concepts" - -ENV PHALCON_VERSION="5.0.1" \ - PHP_VERSION="8.0" - -# Update -RUN apt update -y && \ - apt install -y \ - apt-utils \ - gettext \ - git \ - libzip-dev \ - nano \ - sudo \ - wget \ - zip - -# PECL Packages -RUN pecl install -o -f redis && \ - pecl install phalcon-${PHALCON_VERSION} \ - xdebug - -# Install PHP extensions -RUN docker-php-ext-install \ - gettext \ - pdo_mysql \ - zip - -# Install PHP extensions -RUN docker-php-ext-enable \ - opcache \ - phalcon \ - redis \ - xdebug - -# Clear cache -RUN apt-get clean && rm -rf /var/lib/apt/lists/* - -# Add user -RUN groupadd -g 1000 phalcon -RUN useradd -u 1000 -ms /bin/bash -g phalcon phalcon - -# Composer -COPY --from=composer /usr/bin/composer /usr/local/bin/composer - -# Copy existing application directory contents -COPY . /var/www - -# Bash script with helper aliases -COPY ./config/.bashrc /root/.bashrc -COPY ./config/.bashrc /home/phalcon/.bashrc - -# Copy existing application directory permissions -COPY --chown=phalcon:phalcon . /var/www - -# Change current user to phalcon -USER phalcon - -# Expose port 9000 and start php-fpm server -EXPOSE 9000 - -CMD ["php-fpm"] diff --git a/docker/8.0/config/.bashrc b/docker/8.0/config/.bashrc deleted file mode 100644 index e106a759..00000000 --- a/docker/8.0/config/.bashrc +++ /dev/null @@ -1,75 +0,0 @@ -#!/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 -alias untar='tar xvf' - -PATH=$PATH:./vendor/bin diff --git a/docker/8.0/config/extra.ini b/docker/8.0/config/extra.ini deleted file mode 100644 index ec632f51..00000000 --- a/docker/8.0/config/extra.ini +++ /dev/null @@ -1,8 +0,0 @@ -error_reporting = E_ALL -display_errors = "On" -display_startup_errors = "On" -log_errors = "On" -error_log = /srv/storage/logs/php_errors.log -memory_limit = 512M -apc.enable_cli = "On" -session.save_path = "/tmp" diff --git a/docker/8.1/Dockerfile b/docker/8.1/Dockerfile deleted file mode 100644 index 61353a6f..00000000 --- a/docker/8.1/Dockerfile +++ /dev/null @@ -1,70 +0,0 @@ -FROM composer:latest as composer -FROM php:8.1-fpm - -# Set working directory -WORKDIR /var/www - -LABEL vendor="Phalcon" \ - maintainer="Phalcon Team " \ - description="The PHP image to test the REST API example concepts" - -ENV PHALCON_VERSION="5.0.1" \ - PHP_VERSION="8.1" - -# Update -RUN apt update -y && \ - apt install -y \ - apt-utils \ - gettext \ - git \ - libzip-dev \ - nano \ - sudo \ - wget \ - zip - -# PECL Packages -RUN pecl install -o -f redis && \ - pecl install phalcon-${PHALCON_VERSION} \ - xdebug - -# Install PHP extensions -RUN docker-php-ext-install \ - gettext \ - pdo_mysql \ - zip - -# Install PHP extensions -RUN docker-php-ext-enable \ - opcache \ - phalcon \ - redis \ - xdebug - -# Clear cache -RUN apt-get clean && rm -rf /var/lib/apt/lists/* - -# Add user -RUN groupadd -g 1000 phalcon -RUN useradd -u 1000 -ms /bin/bash -g phalcon phalcon - -# Composer -COPY --from=composer /usr/bin/composer /usr/local/bin/composer - -# Copy existing application directory contents -COPY . /var/www - -# Bash script with helper aliases -COPY ./config/.bashrc /root/.bashrc -COPY ./config/.bashrc /home/phalcon/.bashrc - -# Copy existing application directory permissions -COPY --chown=phalcon:phalcon . /var/www - -# Change current user to phalcon -USER phalcon - -# Expose port 9000 and start php-fpm server -EXPOSE 9000 - -CMD ["php-fpm"] diff --git a/docker/8.1/config/.bashrc b/docker/8.1/config/.bashrc deleted file mode 100644 index e106a759..00000000 --- a/docker/8.1/config/.bashrc +++ /dev/null @@ -1,75 +0,0 @@ -#!/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 -alias untar='tar xvf' - -PATH=$PATH:./vendor/bin diff --git a/docker/8.1/config/extra.ini b/docker/8.1/config/extra.ini deleted file mode 100644 index ec632f51..00000000 --- a/docker/8.1/config/extra.ini +++ /dev/null @@ -1,8 +0,0 @@ -error_reporting = E_ALL -display_errors = "On" -display_startup_errors = "On" -log_errors = "On" -error_log = /srv/storage/logs/php_errors.log -memory_limit = 512M -apc.enable_cli = "On" -session.save_path = "/tmp" diff --git a/storage/config/extra.ini b/storage/config/extra.ini deleted file mode 100644 index 124fbb22..00000000 --- a/storage/config/extra.ini +++ /dev/null @@ -1,8 +0,0 @@ -error_reporting=E_ALL -display_errors="On" -display_startup_errors="On" -log_errors="On" -error_log=/code/storage/logs/php_errors.log -memory_limit=512M -apc.enable_cli="On" -session.save_path="/tmp" diff --git a/storage/config/my.cnf b/storage/config/my.cnf deleted file mode 100644 index 1c50b026..00000000 --- a/storage/config/my.cnf +++ /dev/null @@ -1,3 +0,0 @@ -[mysqld] -general_log = 1 -general_log_file = /var/lib/mysql/general.log diff --git a/storage/config/nginx/8.0/app-8.0.conf b/storage/config/nginx/8.0/app-8.0.conf deleted file mode 100644 index e55a28b3..00000000 --- a/storage/config/nginx/8.0/app-8.0.conf +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 80; - index index.php index.html; - error_log /var/www/storage/logs/nginx-error.log; - access_log /var/www/storage/logs/nginx-access.log; - root /var/www/api/public; - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass rest-api-app-8.0:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } - location / { - try_files $uri $uri/ /index.php?$query_string; - gzip_static on; - } -} diff --git a/storage/config/nginx/8.1/app-8.1.conf b/storage/config/nginx/8.1/app-8.1.conf deleted file mode 100644 index 9a359a28..00000000 --- a/storage/config/nginx/8.1/app-8.1.conf +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 80; - index index.php index.html; - error_log /var/www/storage/logs/nginx-error.log; - access_log /var/www/storage/logs/nginx-access.log; - root /var/www/api/public; - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass rest-api-app-8.1:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } - location / { - try_files $uri $uri/ /index.php?$query_string; - gzip_static on; - } -} From b808d43909c6ab049ed1e0147b1c5cb406bb49b4 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:50:54 -0500 Subject: [PATCH 05/47] strip composer.json to bare, raise php floor to 8.1 --- composer.json | 32 +- composer.lock | 6953 +------------------------------------------------ 2 files changed, 16 insertions(+), 6969 deletions(-) diff --git a/composer.json b/composer.json index 34edf5bb..4b1fab1b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "phalcon/rest-api", - "type": "library", - "description": "This is a sample API REST application for the Phalcon PHP Framework", + "type": "project", + "description": "This is a sample REST API application for the Phalcon PHP Framework", "keywords": [ "phalcon", "framework", @@ -19,33 +19,17 @@ } ], "require": { - "php": ">=8.0", + "php": ">=8.1", "ext-json": "*", - "ext-openssl": "*", - "ext-phalcon": "^5.0.0RC4", - "league/fractal": "^0.20.1", - "robmorgan/phinx": "^0.12.12", - "vlucas/phpdotenv": "^5.4" + "ext-openssl": "*" }, - "require-dev": { - "codeception/codeception": "^5.0", - "codeception/module-asserts": "^3.0", - "codeception/module-cli": "^2.0", - "codeception/module-filesystem": "^3.0", - "codeception/module-phalcon5": "^2.0", - "codeception/module-phpbrowser": "^3.0", - "codeception/module-rest": "^3.3", - "phalcon/ide-stubs": "^v5.0.0-RC1", - "phpstan/phpstan": "^1.8", - "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "^4.27" + "suggest": { + "ext-phalcon": "Install the Phalcon v5 C extension to run on v5 (the default); alternatively run on v6 via the phalcon/phalcon package - see the Dockerfile PHALCON_VARIANT build arg and the README." }, "config": { "preferred-install": "dist", "sort-packages": true, - "optimize-autoloader": true - }, - "scripts": { - "phpcs": "phpcs --standard=PSR12" + "optimize-autoloader": true, + "allow-plugins": {} } } diff --git a/composer.lock b/composer.lock index 7e80a6ff..6a6281c1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,6956 +4,19 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bba5b028fab1210394cb8f18090e2b49", - "packages": [ - { - "name": "cakephp/core", - "version": "4.4.5", - "source": { - "type": "git", - "url": "https://github.com/cakephp/core.git", - "reference": "77e53e3784863c1b8006464b82ab842ee7c58e6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/core/zipball/77e53e3784863c1b8006464b82ab842ee7c58e6b", - "reference": "77e53e3784863c1b8006464b82ab842ee7c58e6b", - "shasum": "" - }, - "require": { - "cakephp/utility": "^4.0", - "php": ">=7.4.0" - }, - "suggest": { - "cakephp/cache": "To use Configure::store() and restore().", - "cakephp/event": "To use PluginApplicationInterface or plugin applications.", - "league/container": "To use Container and ServiceProvider classes" - }, - "type": "library", - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Cake\\Core\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/core/graphs/contributors" - } - ], - "description": "CakePHP Framework Core classes", - "homepage": "https://cakephp.org", - "keywords": [ - "cakephp", - "core", - "framework" - ], - "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "issues": "https://github.com/cakephp/cakephp/issues", - "source": "https://github.com/cakephp/core" - }, - "time": "2022-08-26T02:01:18+00:00" - }, - { - "name": "cakephp/database", - "version": "4.4.5", - "source": { - "type": "git", - "url": "https://github.com/cakephp/database.git", - "reference": "eec6239b0734e2a4dfe212dc93bcd505c7d296ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/database/zipball/eec6239b0734e2a4dfe212dc93bcd505c7d296ef", - "reference": "eec6239b0734e2a4dfe212dc93bcd505c7d296ef", - "shasum": "" - }, - "require": { - "cakephp/core": "^4.0", - "cakephp/datasource": "^4.0", - "php": ">=7.4.0" - }, - "suggest": { - "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types." - }, - "type": "library", - "autoload": { - "psr-4": { - "Cake\\Database\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/database/graphs/contributors" - } - ], - "description": "Flexible and powerful Database abstraction library with a familiar PDO-like API", - "homepage": "https://cakephp.org", - "keywords": [ - "abstraction", - "cakephp", - "database", - "database abstraction", - "pdo" - ], - "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "issues": "https://github.com/cakephp/cakephp/issues", - "source": "https://github.com/cakephp/database" - }, - "time": "2022-08-18T21:01:25+00:00" - }, - { - "name": "cakephp/datasource", - "version": "4.4.5", - "source": { - "type": "git", - "url": "https://github.com/cakephp/datasource.git", - "reference": "6fbd1f49833dedf3bd351e1a98b18133a6b9e86c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/datasource/zipball/6fbd1f49833dedf3bd351e1a98b18133a6b9e86c", - "reference": "6fbd1f49833dedf3bd351e1a98b18133a6b9e86c", - "shasum": "" - }, - "require": { - "cakephp/core": "^4.0", - "php": ">=7.4.0", - "psr/log": "^1.0 || ^2.0", - "psr/simple-cache": "^1.0 || ^2.0" - }, - "suggest": { - "cakephp/cache": "If you decide to use Query caching.", - "cakephp/collection": "If you decide to use ResultSetInterface.", - "cakephp/utility": "If you decide to use EntityTrait." - }, - "type": "library", - "autoload": { - "psr-4": { - "Cake\\Datasource\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/datasource/graphs/contributors" - } - ], - "description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores", - "homepage": "https://cakephp.org", - "keywords": [ - "cakephp", - "connection management", - "datasource", - "entity", - "query" - ], - "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "issues": "https://github.com/cakephp/cakephp/issues", - "source": "https://github.com/cakephp/datasource" - }, - "time": "2022-08-18T20:55:21+00:00" - }, - { - "name": "cakephp/utility", - "version": "4.4.5", - "source": { - "type": "git", - "url": "https://github.com/cakephp/utility.git", - "reference": "7d28a3935f746fcbbfb38e8dddaa9c9d48b251c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/utility/zipball/7d28a3935f746fcbbfb38e8dddaa9c9d48b251c5", - "reference": "7d28a3935f746fcbbfb38e8dddaa9c9d48b251c5", - "shasum": "" - }, - "require": { - "cakephp/core": "^4.0", - "php": ">=7.4.0" - }, - "suggest": { - "ext-intl": "To use Text::transliterate() or Text::slug()", - "lib-ICU": "To use Text::transliterate() or Text::slug()" - }, - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Cake\\Utility\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/utility/graphs/contributors" - } - ], - "description": "CakePHP Utility classes such as Inflector, String, Hash, and Security", - "homepage": "https://cakephp.org", - "keywords": [ - "cakephp", - "hash", - "inflector", - "security", - "string", - "utility" - ], - "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "issues": "https://github.com/cakephp/cakephp/issues", - "source": "https://github.com/cakephp/utility" - }, - "time": "2022-08-19T06:02:03+00:00" - }, - { - "name": "graham-campbell/result-type", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" - }, - "type": "library", - "autoload": { - "psr-4": { - "GrahamCampbell\\ResultType\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - } - ], - "description": "An Implementation Of The Result Type", - "keywords": [ - "Graham Campbell", - "GrahamCampbell", - "Result Type", - "Result-Type", - "result" - ], - "support": { - "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", - "type": "tidelift" - } - ], - "time": "2022-07-30T15:56:11+00:00" - }, - { - "name": "league/fractal", - "version": "0.20.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/fractal.git", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/8b9d39b67624db9195c06f9c1ffd0355151eaf62", - "reference": "8b9d39b67624db9195c06f9c1ffd0355151eaf62", - "shasum": "" - }, - "require": { - "php": ">=7.4" - }, - "require-dev": { - "doctrine/orm": "^2.5", - "illuminate/contracts": "~5.0", - "mockery/mockery": "^1.3", - "pagerfanta/pagerfanta": "~1.0.0", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "~3.4", - "vimeo/psalm": "^4.22", - "zendframework/zend-paginator": "~2.3" - }, - "suggest": { - "illuminate/pagination": "The Illuminate Pagination component.", - "pagerfanta/pagerfanta": "Pagerfanta Paginator", - "zendframework/zend-paginator": "Zend Framework Paginator" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.20.x-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Fractal\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Phil Sturgeon", - "email": "me@philsturgeon.uk", - "homepage": "http://philsturgeon.uk/", - "role": "Developer" - } - ], - "description": "Handle the output of complex data structures ready for API output.", - "homepage": "http://fractal.thephpleague.com/", - "keywords": [ - "api", - "json", - "league", - "rest" - ], - "support": { - "issues": "https://github.com/thephpleague/fractal/issues", - "source": "https://github.com/thephpleague/fractal/tree/0.20.1" - }, - "time": "2022-04-11T12:47:17+00:00" - }, - { - "name": "phpoption/phpoption", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": true - }, - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpOption\\": "src/PhpOption/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" - }, - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "support": { - "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", - "type": "tidelift" - } - ], - "time": "2022-07-30T15:51:26+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/log", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" - }, - "time": "2021-07-14T16:41:46+00:00" - }, - { - "name": "psr/simple-cache", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "support": { - "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" - }, - "time": "2021-10-29T13:22:09+00:00" - }, - { - "name": "robmorgan/phinx", - "version": "0.12.12", - "source": { - "type": "git", - "url": "https://github.com/cakephp/phinx.git", - "reference": "9a6ce1e7fdf0fa4e602ba5875b5bc9442ccaa115" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cakephp/phinx/zipball/9a6ce1e7fdf0fa4e602ba5875b5bc9442ccaa115", - "reference": "9a6ce1e7fdf0fa4e602ba5875b5bc9442ccaa115", - "shasum": "" - }, - "require": { - "cakephp/database": "^4.0", - "php": ">=7.2", - "psr/container": "^1.0 || ^2.0", - "symfony/config": "^3.4|^4.0|^5.0|^6.0", - "symfony/console": "^3.4|^4.0|^5.0|^6.0" - }, - "require-dev": { - "cakephp/cakephp-codesniffer": "^4.0", - "ext-json": "*", - "ext-pdo": "*", - "phpunit/phpunit": "^8.5|^9.3", - "sebastian/comparator": ">=1.2.3", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "ext-json": "Install if using JSON configuration format", - "ext-pdo": "PDO extension is needed", - "symfony/yaml": "Install if using YAML configuration format" - }, - "bin": [ - "bin/phinx" - ], - "type": "library", - "autoload": { - "psr-4": { - "Phinx\\": "src/Phinx/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rob Morgan", - "email": "robbym@gmail.com", - "homepage": "https://robmorgan.id.au", - "role": "Lead Developer" - }, - { - "name": "Woody Gilk", - "email": "woody.gilk@gmail.com", - "homepage": "https://shadowhand.me", - "role": "Developer" - }, - { - "name": "Richard Quadling", - "email": "rquadling@gmail.com", - "role": "Developer" - }, - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/phinx/graphs/contributors", - "role": "Developer" - } - ], - "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", - "homepage": "https://phinx.org", - "keywords": [ - "database", - "database migrations", - "db", - "migrations", - "phinx" - ], - "support": { - "issues": "https://github.com/cakephp/phinx/issues", - "source": "https://github.com/cakephp/phinx/tree/0.12.12" - }, - "time": "2022-07-09T18:53:51+00:00" - }, - { - "name": "symfony/config", - "version": "v6.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "956d4ec5df274dda91a4cedfccc2bfd063f6f649" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/956d4ec5df274dda91a4cedfccc2bfd063f6f649", - "reference": "956d4ec5df274dda91a4cedfccc2bfd063f6f649", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^5.4|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php81": "^1.22" - }, - "conflict": { - "symfony/finder": "<4.4" - }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v6.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T17:10:44+00:00" - }, - { - "name": "symfony/console", - "version": "v6.0.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "c5c2e313aa682530167c25077d6bdff36346251e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c5c2e313aa682530167c25077d6bdff36346251e", - "reference": "c5c2e313aa682530167c25077d6bdff36346251e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v6.0.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-23T20:52:30+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v6.0.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "a36b782dc19dce3ab7e47d4b92b13cefb3511da3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/a36b782dc19dce3ab7e47d4b92b13cefb3511da3", - "reference": "a36b782dc19dce3ab7e47d4b92b13cefb3511da3", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.0.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-02T16:01:06+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-10T07:21:04+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/container": "^2.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-30T19:17:58+00:00" - }, - { - "name": "symfony/string", - "version": "v6.0.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "3a975ba1a1508ad97df45f4590f55b7cc4c1a0a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/3a975ba1a1508ad97df45f4590f55b7cc4c1a0a0", - "reference": "3a975ba1a1508ad97df45f4590f55b7cc4c1a0a0", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" - }, - "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v6.0.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-12T18:05:20+00:00" - }, - { - "name": "vlucas/phpdotenv", - "version": "v5.4.1", - "source": { - "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", - "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.2", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.8", - "symfony/polyfill-ctype": "^1.23", - "symfony/polyfill-mbstring": "^1.23.1", - "symfony/polyfill-php80": "^1.23.1" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" - }, - "suggest": { - "ext-filter": "Required to use the boolean validator." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "psr-4": { - "Dotenv\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Vance Lucas", - "email": "vance@vancelucas.com", - "homepage": "https://github.com/vlucas" - } - ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", - "keywords": [ - "dotenv", - "env", - "environment" - ], - "support": { - "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", - "type": "tidelift" - } - ], - "time": "2021-12-12T23:22:04+00:00" - } - ], - "packages-dev": [ - { - "name": "amphp/amp", - "version": "v2.6.2", - "source": { - "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], - "psr-4": { - "Amp\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", - "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2022-02-20T17:52:18+00:00" - }, - { - "name": "amphp/byte-stream", - "version": "v1.8.1", - "source": { - "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", - "shasum": "" - }, - "require": { - "amphp/amp": "^2", - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php" - ], - "psr-4": { - "Amp\\ByteStream\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2021-03-30T17:13:30+00:00" - }, - { - "name": "behat/gherkin", - "version": "v4.9.0", - "source": { - "type": "git", - "url": "https://github.com/Behat/Gherkin.git", - "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", - "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", - "shasum": "" - }, - "require": { - "php": "~7.2|~8.0" - }, - "require-dev": { - "cucumber/cucumber": "dev-gherkin-22.0.0", - "phpunit/phpunit": "~8|~9", - "symfony/yaml": "~3|~4|~5" - }, - "suggest": { - "symfony/yaml": "If you want to parse features, represented in YAML files" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-0": { - "Behat\\Gherkin": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Gherkin DSL parser for PHP", - "homepage": "http://behat.org/", - "keywords": [ - "BDD", - "Behat", - "Cucumber", - "DSL", - "gherkin", - "parser" - ], - "support": { - "issues": "https://github.com/Behat/Gherkin/issues", - "source": "https://github.com/Behat/Gherkin/tree/v4.9.0" - }, - "time": "2021-10-12T13:05:09+00:00" - }, - { - "name": "codeception/codeception", - "version": "5.0.2", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Codeception.git", - "reference": "69f5c85d0c733826395271566a7677d2bc19bd21" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/69f5c85d0c733826395271566a7677d2bc19bd21", - "reference": "69f5c85d0c733826395271566a7677d2bc19bd21", - "shasum": "" - }, - "require": { - "behat/gherkin": "^4.6.2", - "codeception/lib-asserts": "2.0.*@dev", - "codeception/stub": "^3.7 | ^4.0", - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "php": "^8.0", - "phpunit/php-code-coverage": "^9.2", - "phpunit/php-text-template": "^2.0", - "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.5", - "psy/psysh": "^0.11.2", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "symfony/console": ">=4.4.24 <7.0", - "symfony/css-selector": ">=4.4.24 <7.0", - "symfony/event-dispatcher": ">=4.4.24 <7.0", - "symfony/finder": ">=4.4.24 <7.0", - "symfony/var-dumper": ">=4.4.24 < 7.0", - "symfony/yaml": ">=4.4.24 <7.0" - }, - "conflict": { - "codeception/lib-innerbrowser": "<3.1", - "codeception/module-filesystem": "<3.0", - "codeception/module-phpbrowser": "<2.5" - }, - "replace": { - "codeception/phpunit-wrapper": "*" - }, - "require-dev": { - "codeception/lib-innerbrowser": "*@dev", - "codeception/lib-web": "^1.0", - "codeception/module-asserts": "*@dev", - "codeception/module-cli": "*@dev", - "codeception/module-db": "*@dev", - "codeception/module-filesystem": "*@dev", - "codeception/module-phpbrowser": "*@dev", - "codeception/util-universalframework": "*@dev", - "ext-simplexml": "*", - "symfony/dotenv": ">=4.4.24 <7.0", - "symfony/process": ">=4.4.24 <7.0", - "vlucas/phpdotenv": "^5.1" - }, - "suggest": { - "codeception/specify": "BDD-style code blocks", - "codeception/verify": "BDD-style assertions", - "ext-simplexml": "For loading params from XML files", - "stecman/symfony-console-completion": "For BASH autocompletion", - "symfony/dotenv": "For loading params from .env files", - "symfony/phpunit-bridge": "For phpunit-bridge support", - "vlucas/phpdotenv": "For loading params from .env files" - }, - "bin": [ - "codecept" - ], - "type": "library", - "autoload": { - "files": [ - "functions.php" - ], - "psr-4": { - "Codeception\\": "src/Codeception", - "Codeception\\Extension\\": "ext" - }, - "classmap": [ - "src/PHPUnit/TestCase.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert.ua@gmail.com", - "homepage": "https://codeception.com" - } - ], - "description": "BDD-style testing framework", - "homepage": "https://codeception.com/", - "keywords": [ - "BDD", - "TDD", - "acceptance testing", - "functional testing", - "unit testing" - ], - "support": { - "issues": "https://github.com/Codeception/Codeception/issues", - "source": "https://github.com/Codeception/Codeception/tree/5.0.2" - }, - "funding": [ - { - "url": "https://opencollective.com/codeception", - "type": "open_collective" - } - ], - "time": "2022-08-20T18:19:54+00:00" - }, - { - "name": "codeception/lib-asserts", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/lib-asserts.git", - "reference": "df9c8346722ddde4a20e6372073c09c8df87c296" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/df9c8346722ddde4a20e6372073c09c8df87c296", - "reference": "df9c8346722ddde4a20e6372073c09c8df87c296", - "shasum": "" - }, - "require": { - "codeception/phpunit-wrapper": "^7.7.1 | ^8.0.3 | ^9.0", - "ext-dom": "*", - "php": "^7.4 | ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert@mail.ua", - "homepage": "http://codegyre.com" - }, - { - "name": "Gintautas Miselis" - }, - { - "name": "Gustavo Nieves", - "homepage": "https://medium.com/@ganieves" - } - ], - "description": "Assertion methods used by Codeception core and Asserts module", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/lib-asserts/issues", - "source": "https://github.com/Codeception/lib-asserts/tree/2.0.0" - }, - "time": "2021-12-03T12:40:37+00:00" - }, - { - "name": "codeception/lib-innerbrowser", - "version": "3.1.2", - "source": { - "type": "git", - "url": "https://github.com/Codeception/lib-innerbrowser.git", - "reference": "bc91300ad6794c3ac5c83d80ea2460ad7a57250c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/bc91300ad6794c3ac5c83d80ea2460ad7a57250c", - "reference": "bc91300ad6794c3ac5c83d80ea2460ad7a57250c", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "codeception/lib-web": "^1.0.1", - "ext-dom": "*", - "ext-json": "*", - "ext-mbstring": "*", - "php": "^8.0", - "symfony/browser-kit": "^4.4.24 || ^5.4 || ^6.0", - "symfony/dom-crawler": "^4.4.30 || ^5.4 || ^6.0" - }, - "conflict": { - "codeception/codeception": "<5.0.0-alpha3" - }, - "require-dev": { - "codeception/util-universalframework": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert@mail.ua", - "homepage": "https://codegyre.com" - }, - { - "name": "Gintautas Miselis" - } - ], - "description": "Parent library for all Codeception framework modules and PhpBrowser", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/lib-innerbrowser/issues", - "source": "https://github.com/Codeception/lib-innerbrowser/tree/3.1.2" - }, - "time": "2022-04-09T08:43:01+00:00" - }, - { - "name": "codeception/lib-web", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/Codeception/lib-web.git", - "reference": "91e35c5a849479a626f79daf4754ca4ba4e3227f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-web/zipball/91e35c5a849479a626f79daf4754ca4ba4e3227f", - "reference": "91e35c5a849479a626f79daf4754ca4ba4e3227f", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "guzzlehttp/psr7": "^2.0", - "php": "^8.0", - "symfony/css-selector": ">=4.4.24 <7.0" - }, - "conflict": { - "codeception/codeception": "<5.0.0-alpha3" - }, - "require-dev": { - "php-webdriver/webdriver": "^1.12", - "phpunit/phpunit": "^9.5 | ^10.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gintautas Miselis" - } - ], - "description": "Library containing files used by module-webdriver and lib-innerbrowser or module-phpbrowser", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/lib-web/issues", - "source": "https://github.com/Codeception/lib-web/tree/1.0.1" - }, - "time": "2022-04-09T08:17:46+00:00" - }, - { - "name": "codeception/lib-xml", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/Codeception/lib-xml.git", - "reference": "d6e4c094fb83958bcf254a20815cea5ac31e98d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/lib-xml/zipball/d6e4c094fb83958bcf254a20815cea5ac31e98d0", - "reference": "d6e4c094fb83958bcf254a20815cea5ac31e98d0", - "shasum": "" - }, - "require": { - "codeception/lib-web": "^1.0", - "ext-dom": "*", - "php": "^8.0", - "phpunit/phpunit": "^9.5 | ^10.0", - "symfony/css-selector": ">=4.4.24 <7.0" - }, - "conflict": { - "codeception/codeception": "<5.0.0-alpha3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gintautas Miselis" - } - ], - "description": "Files used by module-rest and module-soap", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/lib-xml/issues", - "source": "https://github.com/Codeception/lib-xml/tree/1.0.1" - }, - "time": "2022-09-11T14:09:09+00:00" - }, - { - "name": "codeception/module-asserts", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-asserts.git", - "reference": "1b6b150b30586c3614e7e5761b31834ed7968603" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/1b6b150b30586c3614e7e5761b31834ed7968603", - "reference": "1b6b150b30586c3614e7e5761b31834ed7968603", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "codeception/lib-asserts": "^2.0", - "php": "^8.0" - }, - "conflict": { - "codeception/codeception": "<5.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk" - }, - { - "name": "Gintautas Miselis" - }, - { - "name": "Gustavo Nieves", - "homepage": "https://medium.com/@ganieves" - } - ], - "description": "Codeception module containing various assertions", - "homepage": "https://codeception.com/", - "keywords": [ - "assertions", - "asserts", - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/module-asserts/issues", - "source": "https://github.com/Codeception/module-asserts/tree/3.0.0" - }, - "time": "2022-02-16T19:48:08+00:00" - }, - { - "name": "codeception/module-cli", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-cli.git", - "reference": "aa9bdd8346983eebc3aa3f21e902399ceefec372" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-cli/zipball/aa9bdd8346983eebc3aa3f21e902399ceefec372", - "reference": "aa9bdd8346983eebc3aa3f21e902399ceefec372", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "php": "^7.4 || ^8.0" - }, - "conflict": { - "codeception/codeception": "<4.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk" - } - ], - "description": "Codeception module for testing basic shell commands and shell output", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception" - ], - "support": { - "issues": "https://github.com/Codeception/module-cli/issues", - "source": "https://github.com/Codeception/module-cli/tree/2.0.0" - }, - "time": "2021-12-01T01:21:55+00:00" - }, - { - "name": "codeception/module-db", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-db.git", - "reference": "4ec1208d6d5f546b72decd733a94bf4488683a74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-db/zipball/4ec1208d6d5f546b72decd733a94bf4488683a74", - "reference": "4ec1208d6d5f546b72decd733a94bf4488683a74", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "ext-json": "*", - "ext-pdo": "*", - "php": "^8.0" - }, - "conflict": { - "codeception/codeception": "<5.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk" - }, - { - "name": "Gintautas Miselis" - } - ], - "description": "DB module for Codeception", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception", - "database-testing", - "db-testing" - ], - "support": { - "issues": "https://github.com/Codeception/module-db/issues", - "source": "https://github.com/Codeception/module-db/tree/3.0.1" - }, - "time": "2022-03-05T19:26:44+00:00" - }, - { - "name": "codeception/module-filesystem", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-filesystem.git", - "reference": "326ef1c1edf90f52ceec2965ff240a8d93c1ba63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-filesystem/zipball/326ef1c1edf90f52ceec2965ff240a8d93c1ba63", - "reference": "326ef1c1edf90f52ceec2965ff240a8d93c1ba63", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "php": "^8.0", - "symfony/finder": "^4.4 || ^5.4 || ^6.0" - }, - "conflict": { - "codeception/codeception": "<5.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk" - }, - { - "name": "Gintautas Miselis" - } - ], - "description": "Codeception module for testing local filesystem", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception", - "filesystem" - ], - "support": { - "issues": "https://github.com/Codeception/module-filesystem/issues", - "source": "https://github.com/Codeception/module-filesystem/tree/3.0.0" - }, - "time": "2022-03-14T18:48:55+00:00" - }, - { - "name": "codeception/module-phalcon5", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-phalcon5.git", - "reference": "c331afda2f4c3cd94f78f3efa0370b555ab2489b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-phalcon5/zipball/c331afda2f4c3cd94f78f3efa0370b555ab2489b", - "reference": "c331afda2f4c3cd94f78f3efa0370b555ab2489b", - "shasum": "" - }, - "require": { - "codeception/codeception": "^5.0.0-RC1", - "codeception/module-asserts": "^3.0", - "codeception/module-db": "^3.0", - "codeception/module-phpbrowser": "^3.0", - "ext-json": "*", - "php": ">=8.0" - }, - "require-dev": { - "codeception/util-robohelpers": "dev-master", - "phalcon/ide-stubs": "^5.0.0RC1", - "squizlabs/php_codesniffer": "^3.6", - "vimeo/psalm": "^4.23", - "vlucas/phpdotenv": "^5.4" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Phalcon Team", - "email": "team@phalcon.io", - "homepage": "https://phalcon.io/en/team" - }, - { - "name": "Codeception Team", - "email": "team@codeception.com", - "homepage": "https://codeception.com/" - } - ], - "description": "Codeception module for Phalcon 5 framework", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception", - "phalcon", - "phalcon5" - ], - "support": { - "issues": "https://github.com/Codeception/module-phalcon5/issues", - "source": "https://github.com/Codeception/module-phalcon5/tree/v2.0.0" - }, - "time": "2022-06-03T15:18:31+00:00" - }, - { - "name": "codeception/module-phpbrowser", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-phpbrowser.git", - "reference": "8e1fdcc85e182e6b61399b35a35a562862c3be62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-phpbrowser/zipball/8e1fdcc85e182e6b61399b35a35a562862c3be62", - "reference": "8e1fdcc85e182e6b61399b35a35a562862c3be62", - "shasum": "" - }, - "require": { - "codeception/codeception": "*@dev", - "codeception/lib-innerbrowser": "*@dev", - "ext-json": "*", - "guzzlehttp/guzzle": "^7.4", - "php": "^8.0", - "symfony/browser-kit": "^5.4 || ^6.0" - }, - "conflict": { - "codeception/codeception": "<5.0", - "codeception/lib-innerbrowser": "<3.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^3.199", - "codeception/module-rest": "^2.0 || *@dev", - "ext-curl": "*" - }, - "suggest": { - "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk" - }, - { - "name": "Gintautas Miselis" - } - ], - "description": "Codeception module for testing web application over HTTP", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception", - "functional-testing", - "http" - ], - "support": { - "issues": "https://github.com/Codeception/module-phpbrowser/issues", - "source": "https://github.com/Codeception/module-phpbrowser/tree/3.0.0" - }, - "time": "2022-02-19T18:22:27+00:00" - }, - { - "name": "codeception/module-rest", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/module-rest.git", - "reference": "57b35f9732bcebd744119c054fdb1b82345147a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/module-rest/zipball/57b35f9732bcebd744119c054fdb1b82345147a7", - "reference": "57b35f9732bcebd744119c054fdb1b82345147a7", - "shasum": "" - }, - "require": { - "codeception/codeception": "^5.0.0-alpha2", - "codeception/lib-xml": "^1.0", - "ext-dom": "*", - "ext-json": "*", - "justinrainbow/json-schema": "~5.2.9", - "php": "^8.0", - "softcreatr/jsonpath": "^0.8" - }, - "conflict": { - "codeception/codeception": "<5.0.0-alpha3" - }, - "require-dev": { - "codeception/lib-innerbrowser": "^3.0", - "codeception/stub": "^4.0", - "codeception/util-universalframework": "^1.0", - "ext-libxml": "*", - "ext-simplexml": "*" - }, - "suggest": { - "aws/aws-sdk-php": "For using AWS Auth" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gintautas Miselis" - } - ], - "description": "REST module for Codeception", - "homepage": "https://codeception.com/", - "keywords": [ - "codeception", - "rest" - ], - "support": { - "issues": "https://github.com/Codeception/module-rest/issues", - "source": "https://github.com/Codeception/module-rest/tree/3.3.0" - }, - "time": "2022-08-22T07:10:02+00:00" - }, - { - "name": "codeception/stub", - "version": "4.0.2", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "18a148dacd293fc7b044042f5aa63a82b08bff5d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/18a148dacd293fc7b044042f5aa63a82b08bff5d", - "reference": "18a148dacd293fc7b044042f5aa63a82b08bff5d", - "shasum": "" - }, - "require": { - "php": "^7.4 | ^8.0", - "phpunit/phpunit": "^8.4 | ^9.0 | ^10.0 | 10.0.x-dev" - }, - "require-dev": { - "consolidation/robo": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Codeception\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "support": { - "issues": "https://github.com/Codeception/Stub/issues", - "source": "https://github.com/Codeception/Stub/tree/4.0.2" - }, - "time": "2022-01-31T19:25:15+00:00" - }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, - { - "name": "composer/pcre", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", - "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.0.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-02-25T20:21:48+00:00" - }, - { - "name": "composer/semver", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-04-01T19:23:25+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-02-25T21:32:43+00:00" - }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" - }, - "time": "2022-03-02T22:36:06+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "7.5.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2022-08-28T15:39:27+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "1.5.2", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2022-08-28T14:55:35+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2022-08-28T14:45:39+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.12", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" - }, - "time": "2022-04-13T08:02:27+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2022-03-03T13:19:32+00:00" - }, - { - "name": "netresearch/jsonmapper", - "version": "v4.0.0", - "source": { - "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" - }, - "time": "2020-12-01T19:48:11+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.15.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" - }, - "time": "2022-09-04T07:30:47+00:00" - }, - { - "name": "openlss/lib-array2xml", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/nullivex/lib-array2xml.git", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "LSS": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Bryan Tong", - "email": "bryan@nullivex.com", - "homepage": "https://www.nullivex.com" - }, - { - "name": "Tony Butler", - "email": "spudz76@gmail.com", - "homepage": "https://www.nullivex.com" - } - ], - "description": "Array2XML conversion library credit to lalit.org", - "homepage": "https://www.nullivex.com", - "keywords": [ - "array", - "array conversion", - "xml", - "xml conversion" - ], - "support": { - "issues": "https://github.com/nullivex/lib-array2xml/issues", - "source": "https://github.com/nullivex/lib-array2xml/tree/master" - }, - "time": "2019-03-29T20:06:56+00:00" - }, - { - "name": "phalcon/ide-stubs", - "version": "v5.0.1", - "source": { - "type": "git", - "url": "https://github.com/phalcon/ide-stubs.git", - "reference": "6009efb3af9300d2c24dfc06c5d0850b9b88f641" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/6009efb3af9300d2c24dfc06c5d0850b9b88f641", - "reference": "6009efb3af9300d2c24dfc06c5d0850b9b88f641", - "shasum": "" - }, - "require": { - "php": ">=7.4" - }, - "require-dev": { - "squizlabs/php_codesniffer": "3.*", - "vimeo/psalm": "^3.4" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Phalcon Team", - "email": "team@phalcon.io", - "homepage": "https://phalcon.io/en-us/team" - }, - { - "name": "Contributors", - "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors" - } - ], - "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.", - "homepage": "https://phalcon.io", - "keywords": [ - "Devtools", - "Eclipse", - "autocomplete", - "ide", - "netbeans", - "phalcon", - "phpstorm", - "stub", - "stubs" - ], - "support": { - "forum": "https://forum.phalcon.io/", - "issues": "https://github.com/phalcon/ide-stubs/issues", - "source": "https://github.com/phalcon/ide-stubs" - }, - "funding": [ - { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", - "type": "open_collective" - } - ], - "time": "2022-09-23T18:22:45+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" - }, - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.8.6", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c386ab2741e64cc9e21729f891b28b2b10fe6618", - "reference": "c386ab2741e64cc9e21729f891b28b2b10fe6618", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.6" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-09-23T09:54:39+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.17", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-08-30T12:24:04+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.5.24", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.1", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-08-30T07:42:16+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client/tree/master" - }, - "time": "2020-06-29T06:28:15+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psy/psysh", - "version": "v0.11.8", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "f455acf3645262ae389b10e9beba0c358aa6994e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/f455acf3645262ae389b10e9beba0c358aa6994e", - "reference": "f455acf3645262ae389b10e9beba0c358aa6994e", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^4.0 || ^3.1", - "php": "^8.0 || ^7.0.8", - "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" - }, - "conflict": { - "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.2" - }, - "suggest": { - "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." - }, - "bin": [ - "bin/psysh" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "0.11.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Psy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", - "keywords": [ - "REPL", - "console", - "interactive", - "shell" - ], - "support": { - "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.8" - }, - "time": "2022-07-28T14:25:11+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:41:17+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:52:27+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:10:38+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-04-03T09:37:03+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T06:03:37+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-02-14T08:28:10+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-28T06:42:11+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:17:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:45:17+00:00" - }, - { - "name": "sebastian/type", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-12T14:47:03+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "softcreatr/jsonpath", - "version": "0.8.0", - "source": { - "type": "git", - "url": "https://github.com/SoftCreatR/JSONPath.git", - "reference": "c2f4a164c2d9be754e2d47811157a31fe93067d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/c2f4a164c2d9be754e2d47811157a31fe93067d0", - "reference": "c2f4a164c2d9be754e2d47811157a31fe93067d0", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=8.0" - }, - "replace": { - "flow/jsonpath": "*" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Flow\\JSONPath\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stephen Frank", - "email": "stephen@flowsa.com", - "homepage": "https://prismaticbytes.com", - "role": "Developer" - }, - { - "name": "Sascha Greuel", - "email": "hello@1-2.dev", - "homepage": "https://1-2.dev", - "role": "Developer" - } - ], - "description": "JSONPath implementation for parsing, searching and flattening arrays", - "support": { - "email": "hello@1-2.dev", - "forum": "https://github.com/SoftCreatR/JSONPath/discussions", - "issues": "https://github.com/SoftCreatR/JSONPath/issues", - "source": "https://github.com/SoftCreatR/JSONPath" - }, - "funding": [ - { - "url": "https://ecologi.com/softcreatr?r=61212ab3fc69b8eb8a2014f4", - "type": "custom" - }, - { - "url": "https://github.com/softcreatr", - "type": "github" - } - ], - "time": "2022-01-31T14:22:15+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.7.1", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2022-06-18T07:21:10+00:00" - }, - { - "name": "symfony/browser-kit", - "version": "v6.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "92e4b59bf2ef0f7a01d2620c4c87108e5c143d36" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/92e4b59bf2ef0f7a01d2620c4c87108e5c143d36", - "reference": "92e4b59bf2ef0f7a01d2620c4c87108e5c143d36", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/dom-crawler": "^5.4|^6.0" - }, - "require-dev": { - "symfony/css-selector": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0" - }, - "suggest": { - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-27T15:50:26+00:00" - }, - { - "name": "symfony/css-selector", - "version": "v6.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab2746acddc4f03a7234c8441822ac5d5c63efe9", - "reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Converts CSS selectors to XPath expressions", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T17:10:44+00:00" - }, - { - "name": "symfony/dom-crawler", - "version": "v6.0.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "c69331ec49913a91f32737e29ed451ecee8cbea2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c69331ec49913a91f32737e29ed451ecee8cbea2", - "reference": "c69331ec49913a91f32737e29ed451ecee8cbea2", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "masterminds/html5": "<2.6" - }, - "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^5.4|^6.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.0.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-04T19:18:27+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v6.0.9", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5c85b58422865d42c6eb46f7693339056db098a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5c85b58422865d42c6eb46f7693339056db098a8", - "reference": "5c85b58422865d42c6eb46f7693339056db098a8", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/event-dispatcher-contracts": "^2|^3" - }, - "conflict": { - "symfony/dependency-injection": "<5.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^5.4|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.9" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-05T16:45:52+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/event-dispatcher": "^1" - }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/finder", - "version": "v6.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/09cb683ba5720385ea6966e5e06be2a34f2568b1", - "reference": "09cb683ba5720385ea6966e5e06be2a34f2568b1", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v6.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-29T07:39:48+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v6.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "2672bdc01c1971e3d8879ce153ec4c3621be5f07" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2672bdc01c1971e3d8879ce153ec4c3621be5f07", - "reference": "2672bdc01c1971e3d8879ce153ec4c3621be5f07", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T13:45:53+00:00" - }, - { - "name": "symfony/yaml", - "version": "v6.0.12", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "8c68efb08b038ec02753da6f16e1601a6ed4ef17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/8c68efb08b038ec02753da6f16e1601a6ed4ef17", - "reference": "8c68efb08b038ec02753da6f16e1601a6ed4ef17", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "symfony/console": "^5.4|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v6.0.12" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-02T16:01:06+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "vimeo/psalm", - "version": "4.27.0", - "source": { - "type": "git", - "url": "https://github.com/vimeo/psalm.git", - "reference": "faf106e717c37b8c81721845dba9de3d8deed8ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/faf106e717c37b8c81721845dba9de3d8deed8ff", - "reference": "faf106e717c37b8c81721845dba9de3d8deed8ff", - "shasum": "" - }, - "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", - "composer/package-versions-deprecated": "^1.8.0", - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", - "dnoegel/php-xdg-base-dir": "^0.1.1", - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.0.3", - "felixfbecker/language-server-protocol": "^1.5", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.13", - "openlss/lib-array2xml": "^1.0", - "php": "^7.1|^8", - "sebastian/diff": "^3.0 || ^4.0", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", - "symfony/polyfill-php80": "^1.25", - "webmozart/path-util": "^2.3" - }, - "provide": { - "psalm/psalm": "self.version" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "brianium/paratest": "^4.0||^6.0", - "ext-curl": "*", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpdocumentor/reflection-docblock": "^5", - "phpmyadmin/sql-parser": "5.1.0||dev-master", - "phpspec/prophecy": ">=1.9.0", - "phpunit/phpunit": "^9.0", - "psalm/plugin-phpunit": "^0.16", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", - "symfony/process": "^4.3 || ^5.0 || ^6.0", - "weirdan/prophecy-shim": "^1.0 || ^2.0" - }, - "suggest": { - "ext-curl": "In order to send data to shepherd", - "ext-igbinary": "^2.0.5 is required, used to serialize caching data" - }, - "bin": [ - "psalm", - "psalm-language-server", - "psalm-plugin", - "psalm-refactor", - "psalter" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev", - "dev-3.x": "3.x-dev", - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php", - "src/spl_object_id.php" - ], - "psr-4": { - "Psalm\\": "src/Psalm/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matthew Brown" - } - ], - "description": "A static analysis tool for finding errors in PHP applications", - "keywords": [ - "code", - "inspection", - "php" - ], - "support": { - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.27.0" - }, - "time": "2022-08-31T13:47:09+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - }, - { - "name": "webmozart/path-util", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/path-util.git", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "webmozart/assert": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\PathUtil\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", - "support": { - "issues": "https://github.com/webmozart/path-util/issues", - "source": "https://github.com/webmozart/path-util/tree/2.3.0" - }, - "abandoned": "symfony/filesystem", - "time": "2015-12-17T08:42:14+00:00" - } - ], + "content-hash": "77d75e7fb6539291ca4aeddab68244a6", + "packages": [], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "ext-phalcon": 5, - "phalcon/ide-stubs": 5 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0", + "php": ">=8.1", "ext-json": "*", - "ext-openssl": "*", - "ext-phalcon": "^5.0.0RC4" + "ext-openssl": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } From 5f3ac12a263346c06251810f7c3ea095a83b3896 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:51:41 -0500 Subject: [PATCH 06/47] add runtime dependencies: phpdotenv, fractal, phinx --- composer.json | 5 +- composer.lock | 1904 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1906 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4b1fab1b..b974e602 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,10 @@ "require": { "php": ">=8.1", "ext-json": "*", - "ext-openssl": "*" + "ext-openssl": "*", + "league/fractal": "^0.21.0", + "robmorgan/phinx": "^0.16.12", + "vlucas/phpdotenv": "^5.6" }, "suggest": { "ext-phalcon": "Install the Phalcon v5 C extension to run on v5 (the default); alternatively run on v6 via the phalcon/phalcon package - see the Dockerfile PHALCON_VARIANT build arg and the README." diff --git a/composer.lock b/composer.lock index 6a6281c1..94e04128 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,1908 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "77d75e7fb6539291ca4aeddab68244a6", - "packages": [], + "content-hash": "3a9cb89c9b4376c659ec084653bffa26", + "packages": [ + { + "name": "cakephp/chronos", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/cakephp/chronos.git", + "reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/chronos/zipball/e6e777b534244911566face8a5dbdbd7f7bda5a6", + "reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "cakephp/cakephp-codesniffer": "^5.0", + "phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cake\\Chronos\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + }, + { + "name": "The CakePHP Team", + "homepage": "https://cakephp.org" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "https://cakephp.org", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/cakephp/chronos/issues", + "source": "https://github.com/cakephp/chronos" + }, + "time": "2026-04-10T02:50:39+00:00" + }, + { + "name": "cakephp/core", + "version": "5.2.14", + "source": { + "type": "git", + "url": "https://github.com/cakephp/core.git", + "reference": "f18f37c04832831ca37f5300212b1adddcc54b86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/core/zipball/f18f37c04832831ca37f5300212b1adddcc54b86", + "reference": "f18f37c04832831ca37f5300212b1adddcc54b86", + "shasum": "" + }, + "require": { + "cakephp/utility": "5.2.*@dev", + "league/container": "^4.2", + "php": ">=8.1", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^2.0" + }, + "suggest": { + "cakephp/cache": "To use Configure::store() and restore().", + "cakephp/event": "To use PluginApplicationInterface or plugin applications.", + "league/container": "To use Container and ServiceProvider classes" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Cake\\Core\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/core/graphs/contributors" + } + ], + "description": "CakePHP Framework Core classes", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "core", + "framework" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/core" + }, + "time": "2025-11-14T14:52:55+00:00" + }, + { + "name": "cakephp/database", + "version": "5.2.14", + "source": { + "type": "git", + "url": "https://github.com/cakephp/database.git", + "reference": "3dbc1354d6e4ce06db3442c8ec724b3fe99a1659" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/database/zipball/3dbc1354d6e4ce06db3442c8ec724b3fe99a1659", + "reference": "3dbc1354d6e4ce06db3442c8ec724b3fe99a1659", + "shasum": "" + }, + "require": { + "cakephp/chronos": "^3.1", + "cakephp/core": "5.2.*@dev", + "cakephp/datasource": "5.2.*@dev", + "php": ">=8.1", + "psr/log": "^3.0" + }, + "require-dev": { + "cakephp/i18n": "5.2.*@dev", + "cakephp/log": "5.2.*@dev" + }, + "suggest": { + "cakephp/i18n": "If you are using locale-aware datetime formats.", + "cakephp/log": "If you want to use query logging without providing a logger yourself." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cake\\Database\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/database/graphs/contributors" + } + ], + "description": "Flexible and powerful Database abstraction library with a familiar PDO-like API", + "homepage": "https://cakephp.org", + "keywords": [ + "abstraction", + "cakephp", + "database", + "database abstraction", + "pdo" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/database" + }, + "time": "2026-07-14T03:43:51+00:00" + }, + { + "name": "cakephp/datasource", + "version": "5.2.14", + "source": { + "type": "git", + "url": "https://github.com/cakephp/datasource.git", + "reference": "906a8b719b6dc241fa81a55be20c9adc51c31f74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/datasource/zipball/906a8b719b6dc241fa81a55be20c9adc51c31f74", + "reference": "906a8b719b6dc241fa81a55be20c9adc51c31f74", + "shasum": "" + }, + "require": { + "cakephp/core": "5.2.*@dev", + "php": ">=8.1", + "psr/simple-cache": "^2.0 || ^3.0" + }, + "require-dev": { + "cakephp/cache": "5.2.*@dev", + "cakephp/collection": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev" + }, + "suggest": { + "cakephp/cache": "If you decide to use Query caching.", + "cakephp/collection": "If you decide to use ResultSetInterface.", + "cakephp/utility": "If you decide to use EntityTrait." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cake\\Datasource\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/datasource/graphs/contributors" + } + ], + "description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "connection management", + "datasource", + "entity", + "query" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/datasource" + }, + "time": "2025-11-14T14:52:55+00:00" + }, + { + "name": "cakephp/utility", + "version": "5.2.14", + "source": { + "type": "git", + "url": "https://github.com/cakephp/utility.git", + "reference": "df9bc4e420db3b4a02cafad896398bad48813e50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/utility/zipball/df9bc4e420db3b4a02cafad896398bad48813e50", + "reference": "df9bc4e420db3b4a02cafad896398bad48813e50", + "shasum": "" + }, + "require": { + "cakephp/core": "5.2.*@dev", + "php": ">=8.1" + }, + "suggest": { + "ext-intl": "To use Text::transliterate() or Text::slug()", + "lib-ICU": "To use Text::transliterate() or Text::slug()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Cake\\Utility\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/utility/graphs/contributors" + } + ], + "description": "CakePHP Utility classes such as Inflector, String, Hash, and Security", + "homepage": "https://cakephp.org", + "keywords": [ + "cakephp", + "hash", + "inflector", + "security", + "string", + "utility" + ], + "support": { + "forum": "https://stackoverflow.com/tags/cakephp", + "irc": "irc://irc.freenode.org/cakephp", + "issues": "https://github.com/cakephp/cakephp/issues", + "source": "https://github.com/cakephp/utility" + }, + "time": "2025-11-14T14:52:55+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "league/container", + "version": "4.2.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "nette/php-generator": "^3.4", + "nikic/php-parser": "^4.10", + "phpstan/phpstan": "^0.12.47", + "phpunit/phpunit": "^8.5.17", + "roave/security-advisories": "dev-latest", + "scrutinizer/ocular": "^1.8", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "mail@philbennett.co.uk", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "support": { + "issues": "https://github.com/thephpleague/container/issues", + "source": "https://github.com/thephpleague/container/tree/4.2.5" + }, + "funding": [ + { + "url": "https://github.com/philipobenito", + "type": "github" + } + ], + "time": "2025-05-20T12:55:37+00:00" + }, + { + "name": "league/fractal", + "version": "0.21", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/fractal.git", + "reference": "9e817358dc451dfdcf656d6757f0c04e92dea0e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/9e817358dc451dfdcf656d6757f0c04e92dea0e8", + "reference": "9e817358dc451dfdcf656d6757f0c04e92dea0e8", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "doctrine/orm": "^2.5", + "friendsofphp/php-cs-fixer": "^3.91", + "illuminate/contracts": "~5.0", + "laminas/laminas-paginator": "~2.12", + "mockery/mockery": "^1.3", + "pagerfanta/pagerfanta": "~1.0.0|~4.0.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.30" + }, + "suggest": { + "illuminate/pagination": "The Illuminate Pagination component.", + "laminas/laminas-paginator": "Laminas Framework Paginator", + "pagerfanta/pagerfanta": "Pagerfanta Paginator" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.20.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Fractal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Sturgeon", + "email": "me@philsturgeon.uk", + "homepage": "http://philsturgeon.uk/", + "role": "Developer" + } + ], + "description": "Handle the output of complex data structures ready for API output.", + "homepage": "http://fractal.thephpleague.com/", + "keywords": [ + "api", + "json", + "league", + "rest" + ], + "support": { + "issues": "https://github.com/thephpleague/fractal/issues", + "source": "https://github.com/thephpleague/fractal/tree/0.21" + }, + "time": "2025-12-08T21:14:52+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "robmorgan/phinx", + "version": "0.16.12", + "source": { + "type": "git", + "url": "https://github.com/cakephp/phinx.git", + "reference": "1cce44387a9146dc04c312e6a231b17fb30385cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/phinx/zipball/1cce44387a9146dc04c312e6a231b17fb30385cd", + "reference": "1cce44387a9146dc04c312e6a231b17fb30385cd", + "shasum": "" + }, + "require": { + "cakephp/database": "^5.0.2", + "composer-runtime-api": "^2.0", + "php-64bit": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/config": "^4.0|^5.0|^6.0|^7.0|^8.0", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/i18n": "^5.0", + "ext-json": "*", + "ext-pdo": "*", + "phpunit/phpunit": "^10.5", + "symfony/yaml": "^4.0|^5.0|^6.0|^7.0|^8.0" + }, + "suggest": { + "ext-json": "Install if using JSON configuration format", + "ext-pdo": "PDO extension is needed", + "symfony/yaml": "Install if using YAML configuration format" + }, + "bin": [ + "bin/phinx" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "https://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "https://shadowhand.me", + "role": "Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Developer" + }, + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/phinx/graphs/contributors", + "role": "Developer" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ], + "support": { + "issues": "https://github.com/cakephp/phinx/issues", + "source": "https://github.com/cakephp/phinx/tree/0.16.12" + }, + "time": "2026-07-03T07:27:17+00:00" + }, + { + "name": "symfony/config", + "version": "v6.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "922d980c90a69ffdd63e6ee551efb338b58be677" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/922d980c90a69ffdd63e6ee551efb338b58be677", + "reference": "922d980c90a69ffdd63e6ee551efb338b58be677", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.4.42" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-09T07:36:15+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9ef84af84a7b66396da483634227650506428639" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9ef84af84a7b66396da483634227650506428639", + "reference": "9ef84af84a7b66396da483634227650506428639", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.42" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-15T05:35:29+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c507b077756b4e3e09adbbe7975fac81cd3722ca", + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.39" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-07T13:11:42+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T05:58:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-25T13:48:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T09:55:08+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.39", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "62e3c927de664edadb5bef260987eb047a17a113" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/62e3c927de664edadb5bef260987eb047a17a113", + "reference": "62e3c927de664edadb5bef260987eb047a17a113", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.39" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-12T11:44:19+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.4", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/416df702837983f8d5ff48c9c3fee4f5f57b980b", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-07-06T19:11:50+00:00" + } + ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", From 662612725daaef41a23e0f383f2e33d89e7f5548 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:52:27 -0500 Subject: [PATCH 07/47] add dev tools: phpstan, codesniffer, ide-stubs, pds packages --- composer.json | 7 ++ composer.lock | 279 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 284 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b974e602..7a777a76 100644 --- a/composer.json +++ b/composer.json @@ -34,5 +34,12 @@ "sort-packages": true, "optimize-autoloader": true, "allow-plugins": {} + }, + "require-dev": { + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/ide-stubs": "^5.16", + "phpstan/phpstan": "^2.2", + "squizlabs/php_codesniffer": "^4.0" } } diff --git a/composer.lock b/composer.lock index 94e04128..b0cf1678 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3a9cb89c9b4376c659ec084653bffa26", + "content-hash": "b348c4490899913428d65225bebd208a", "packages": [ { "name": "cakephp/chronos", @@ -1906,7 +1906,282 @@ "time": "2026-07-06T19:11:50+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "pds/composer-script-names", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/composer-script-names.git", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/composer-script-names/zipball/e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "shasum": "" + }, + "type": "standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for Composer script names.", + "homepage": "https://github.com/php-pds/composer-script-names", + "support": { + "issues": "https://github.com/php-pds/composer-script-names/issues", + "source": "https://github.com/php-pds/composer-script-names/tree/1.0.0" + }, + "time": "2023-04-06T13:42:16+00:00" + }, + { + "name": "pds/skeleton", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/skeleton.git", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/skeleton/zipball/95e476e5d629eadacbd721c5a9553e537514a231", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231", + "shasum": "" + }, + "bin": [ + "bin/pds-skeleton" + ], + "type": "standard", + "autoload": { + "psr-4": { + "Pds\\Skeleton\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for PHP package skeletons.", + "homepage": "https://github.com/php-pds/skeleton", + "support": { + "issues": "https://github.com/php-pds/skeleton/issues", + "source": "https://github.com/php-pds/skeleton/tree/1.x" + }, + "time": "2017-01-25T23:30:41+00:00" + }, + { + "name": "phalcon/ide-stubs", + "version": "v5.16.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/ide-stubs.git", + "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/3eea65770216ee93d349802f6e8d8711478d9a5c", + "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "squizlabs/php_codesniffer": "^4.0", + "vimeo/psalm": "^6.16" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io/en-us/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors" + } + ], + "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.", + "homepage": "https://phalcon.io", + "keywords": [ + "Devtools", + "Eclipse", + "autocomplete", + "ide", + "netbeans", + "phalcon", + "phpstorm", + "stub", + "stubs" + ], + "support": { + "discussions": "https://phalcon.io/discussions/", + "issues": "https://github.com/phalcon/ide-stubs/issues", + "source": "https://github.com/phalcon/ide-stubs" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-06-22T18:56:53+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.5", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "reference": "909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-07-05T06:31:06+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-10T16:43:36+00:00" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, From 8824c959b5fc0012c26121f91b500ab3320ed3e1 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:59:10 -0500 Subject: [PATCH 08/47] remove psalm and codeception configs --- codeception.yml | 60 -------------------------------- psalm.xml.dist | 16 --------- phpcs.xml => resources/phpcs.xml | 0 3 files changed, 76 deletions(-) delete mode 100644 codeception.yml delete mode 100644 psalm.xml.dist rename phpcs.xml => resources/phpcs.xml (100%) diff --git a/codeception.yml b/codeception.yml deleted file mode 100644 index 681098cb..00000000 --- a/codeception.yml +++ /dev/null @@ -1,60 +0,0 @@ -# Can be changed while bootstrapping project -actor_suffix: Tester - -paths: - # Where the modules stored - tests: tests - output: tests/_output - # Directory for fixture data - data: tests/_data - # Directory for custom modules (helpers) - support: tests/_support - envs: tests/_envs - -# The name of bootstrap that will be used. -# Each bootstrap file should be inside a suite directory. -bootstrap: _bootstrap.php - -settings: - colors: true - # Tests (especially functional) can take a lot of memory - # We set a high limit for them by default. - memory_limit: 128M - log: true - -coverage: - enabled: true - remote: false - include: - - ./*.php - exclude: - - phinx.php - - storage/* - - tests/* - - vendor/* - -extensions: - enabled: - - Codeception\Extension\RunFailed # default extension - -# Global modules configuration. -modules: - config: - Phalcon: - bootstrap: "tests/_ci/bootstrap.php" - cleanup: false - savepoints: false - DB: - dsn: 'mysql:host=%DATA_API_MYSQL_HOST%;dbname=%DATA_API_MYSQL_NAME%' - user: '%DATA_API_MYSQL_USER%' - password: '%DATA_API_MYSQL_PASS%' - dump: 'tests/_data/dump.sql' - populate: false - cleanup: false - reconnect: true - -# Get params from .env file -params: - - .env - -error_level: "E_ALL" diff --git a/psalm.xml.dist b/psalm.xml.dist deleted file mode 100644 index ba8ab1b1..00000000 --- a/psalm.xml.dist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/phpcs.xml b/resources/phpcs.xml similarity index 100% rename from phpcs.xml rename to resources/phpcs.xml From 1f623fddd99d5ae2a5a4899f3d3e0186198a3bc5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:59:10 -0500 Subject: [PATCH 09/47] add phalcon/phalcon v6, phpstan config and composer scripts --- composer.json | 15 ++ composer.lock | 344 ++++++++++++++++++++++++++++++++++++++++- resources/phpcs.xml | 7 +- resources/phpstan.neon | 14 ++ 4 files changed, 375 insertions(+), 5 deletions(-) create mode 100644 resources/phpstan.neon diff --git a/composer.json b/composer.json index 7a777a76..782ff59c 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,22 @@ "pds/composer-script-names": "^1.0", "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", + "phalcon/phalcon": "^6.0@alpha", "phpstan/phpstan": "^2.2", "squizlabs/php_codesniffer": "^4.0" + }, + "scripts": { + "analyze": "phpstan analyse -c resources/phpstan.neon --memory-limit 1024M", + "cs": "phpcs --standard=resources/phpcs.xml", + "cs-fix": "phpcbf --standard=resources/phpcs.xml", + "test": "talon run", + "test-coverage": "talon run unit -- --coverage-clover tests/_output/coverage.xml", + "test-coverage-html": "talon run unit -- --coverage-html tests/_output/coverage", + "migrate": "phinx migrate -c phinx.php", + "seed": "phinx seed:run -c phinx.php", + "post-create-project-cmd": [ + "@php -r \"file_exists('.env') || copy('resources/.env.example', '.env');\"", + "@php -r \"echo PHP_EOL . 'The Phalcon REST API is ready (.env created from resources/.env.example). Next steps:' . PHP_EOL . ' docker: docker compose up -d --build, then composer install && composer migrate' . PHP_EOL . ' tests: vendor/bin/talon run' . PHP_EOL;\"" + ] } } diff --git a/composer.lock b/composer.lock index b0cf1678..e79fea28 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b348c4490899913428d65225bebd208a", + "content-hash": "a39f61aea26a10ed2f9fdf66d84d9b3e", "packages": [ { "name": "cakephp/chronos", @@ -1907,6 +1907,129 @@ } ], "packages-dev": [ + { + "name": "matthiasmullie/minify", + "version": "1.3.75", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": ">=2.0", + "matthiasmullie/scrapbook": ">=1.3", + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "https://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "https://github.com/matthiasmullie/minify", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "support": { + "issues": "https://github.com/matthiasmullie/minify/issues", + "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" + }, + "funding": [ + { + "url": "https://github.com/matthiasmullie", + "type": "github" + } + ], + "time": "2025-06-25T09:56:19+00:00" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "support": { + "issues": "https://github.com/matthiasmullie/path-converter/issues", + "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" + }, + "time": "2019-02-05T23:41:09+00:00" + }, { "name": "pds/composer-script-names", "version": "1.0.0", @@ -2038,6 +2161,171 @@ ], "time": "2026-06-22T18:56:53+00:00" }, + { + "name": "phalcon/phalcon", + "version": "v6.0.0alpha4", + "source": { + "type": "git", + "url": "https://github.com/phalcon/phalcon.git", + "reference": "4568057c441baf0a729c3eca7585dcb0c5c97d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/phalcon/zipball/4568057c441baf0a729c3eca7585dcb0c5c97d2f", + "reference": "4568057c441baf0a729c3eca7585dcb0c5c97d2f", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-pdo": "*", + "ext-xml": "*", + "matthiasmullie/minify": "^1.3", + "phalcon/traits": "^4.0", + "php": ">=8.1 <9.0", + "psr/event-dispatcher": "^1.0" + }, + "require-dev": { + "ext-gettext": "*", + "ext-yaml": "*", + "friendsofphp/php-cs-fixer": "^3.95", + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/phql": "1.0.x-dev", + "phalcon/talon": "^0.6.0", + "phalcon/volt": "1.0.x-dev", + "phpbench/phpbench": "^1.4", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^10.5", + "predis/predis": "^3.4", + "squizlabs/php_codesniffer": "^4.0", + "vlucas/phpdotenv": "^5.6" + }, + "suggest": { + "ext-apcu": "to use Cache\\Adapter\\Apcu, Storage\\Adapter\\Apcu", + "ext-gd": "to use Image\\Adapter\\Gd", + "ext-igbinary": "to use Storage\\Serializer\\Igbinary", + "ext-imagick": "to use Image\\Adapter\\Imagick", + "ext-memcached": "to use Cache\\Adapter\\Libmemcached, Session\\Adapter\\Libmemcached, Storage\\Adapter\\Libmemcached", + "ext-openssl": "to use Encryption\\Crypt", + "ext-pcntl": "to use signal-based graceful shutdown in Queue\\Consumer\\Worker", + "ext-redis": "to use Cache\\Adapter\\Redis, Session\\Adapter\\Redis, Storage\\Adapter\\Redis", + "ext-yaml": "to use Config\\Adapter\\Yaml" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Phalcon Framework", + "keywords": [ + "framework", + "php" + ], + "support": { + "issues": "https://github.com/phalcon/phalcon/issues", + "source": "https://github.com/phalcon/phalcon/tree/v6.0.0alpha4" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-13T18:05:01+00:00" + }, + { + "name": "phalcon/traits", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/traits.git", + "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/traits/zipball/4e4412a78475af2d08f7e82e48e577e2a40b6896", + "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=8.1 <9.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.75", + "infection/infection": "^0.29.9", + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/talon": "^0.7", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^4.0" + }, + "suggest": { + "ext-apcu": "For Php\\ApcuTrait", + "ext-gettext": "For the gettext-based translation helpers", + "ext-igbinary": "For Php\\IgbinaryTrait", + "ext-msgpack": "For Php\\MsgpackTrait", + "ext-yaml": "For Php\\YamlTrait" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\Traits\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io/en/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/traits/graphs/contributors" + } + ], + "description": "Phalcon Framework reusable traits", + "homepage": "https://phalcon.io", + "keywords": [ + "framework", + "phalcon", + "php", + "traits" + ], + "support": { + "discord": "https://phalcon.io/discord/", + "issues": "https://github.com/phalcon/traits/issues", + "source": "https://github.com/phalcon/traits" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-10T19:24:02+00:00" + }, { "name": "phpstan/phpstan", "version": "2.2.5", @@ -2102,6 +2390,56 @@ ], "time": "2026-07-05T06:31:06+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "squizlabs/php_codesniffer", "version": "4.0.1", @@ -2184,7 +2522,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "phalcon/phalcon": 15 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/resources/phpcs.xml b/resources/phpcs.xml index 564a16b1..e5a5d214 100644 --- a/resources/phpcs.xml +++ b/resources/phpcs.xml @@ -12,7 +12,8 @@ */public/* */_output/* */_support/* - ./api - ./library - ./tests + ../api + ../cli + ../library + ../tests diff --git a/resources/phpstan.neon b/resources/phpstan.neon new file mode 100644 index 00000000..dd784e97 --- /dev/null +++ b/resources/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + paths: + - ../api + - ../cli + - ../library + - ../public/index.php + level: max + reportUnmatchedIgnoredErrors: false + tmpDir: ../tests/_output + # The application registers its own namespaces via library/Core/autoload.php, + # but that file also loads Dotenv and therefore needs a .env present. Bootstrap + # from Composer only; the analysed paths above cover the application's classes. + bootstrapFiles: + - ../vendor/autoload.php From e217556fedf67c988c7439841c6ecc0f43122abd Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 22:59:27 -0500 Subject: [PATCH 10/47] add talon test runner --- composer.json | 1 + composer.lock | 368 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 368 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 782ff59c..3bbfcb9c 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", "phalcon/phalcon": "^6.0@alpha", + "phalcon/talon": "^0.7.0", "phpstan/phpstan": "^2.2", "squizlabs/php_codesniffer": "^4.0" }, diff --git a/composer.lock b/composer.lock index e79fea28..fadc3c78 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a39f61aea26a10ed2f9fdf66d84d9b3e", + "content-hash": "0a80cbef6089aa12c9e12a45b33936bb", "packages": [ { "name": "cakephp/chronos", @@ -1907,6 +1907,73 @@ } ], "packages-dev": [ + { + "name": "masterminds/html5", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + }, + "time": "2026-06-23T18:43:15+00:00" + }, { "name": "matthiasmullie/minify", "version": "1.3.75", @@ -2092,6 +2159,86 @@ }, "time": "2017-01-25T23:30:41+00:00" }, + { + "name": "phalcon/cli-options-parser", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/cli-options-parser.git", + "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/cli-options-parser/zipball/ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", + "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "pds/skeleton": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Phalcon\\Cop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalconphp.com", + "homepage": "https://phalconphp.com/en/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/cli-options-parser/graphs/contributors" + } + ], + "description": "Command line arguments/options parser.", + "homepage": "https://phalconphp.com", + "keywords": [ + "argparse", + "cli", + "command", + "command-line", + "getopt", + "line", + "option", + "optparse", + "parser", + "terminal" + ], + "support": { + "discord": "https://phalcon.io/discord/", + "issues": "https://github.com/phalcon/cli-options-parser/issues", + "source": "https://github.com/phalcon/cli-options-parser" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2023-11-24T16:04:00+00:00" + }, { "name": "phalcon/ide-stubs", "version": "v5.16.0", @@ -2244,6 +2391,82 @@ ], "time": "2026-07-13T18:05:01+00:00" }, + { + "name": "phalcon/talon", + "version": "v0.7.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/talon.git", + "reference": "7e5da75bf6fe1e2ff4d861282f562a8f49c6525d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/talon/zipball/7e5da75bf6fe1e2ff4d861282f562a8f49c6525d", + "reference": "7e5da75bf6fe1e2ff4d861282f562a8f49c6525d", + "shasum": "" + }, + "require": { + "phalcon/cli-options-parser": "^2.0", + "php": "^8.1", + "symfony/browser-kit": "^6.4 || ^7.0", + "symfony/dom-crawler": "^6.4 || ^7.0" + }, + "require-dev": { + "ext-pdo": "*", + "ext-sqlite3": "*", + "friendsofphp/php-cs-fixer": "^3", + "infection/infection": "^0.29.9", + "pds/composer-script-names": "^1", + "pds/skeleton": "^1", + "phalcon/phalcon": "^6.0@alpha", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.5", + "predis/predis": "^2 || ^3", + "squizlabs/php_codesniffer": "^3 || ^4" + }, + "suggest": { + "ext-memcached": "For the Services (Memcached) helpers", + "ext-phalcon": "Phalcon C extension (^5) - one of ext-phalcon or phalcon/phalcon is required", + "phalcon/phalcon": "Phalcon PHP implementation (^6) - alternative to the C extension", + "predis/predis": "For the Services (Redis) helpers" + }, + "bin": [ + "bin/talon" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\Talon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Test harness and Phalcon bootstrapping for PHPUnit and beyond", + "keywords": [ + "harness", + "phalcon", + "phpunit", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/phalcon/talon/issues", + "source": "https://github.com/phalcon/talon/tree/v0.7.0" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-10T12:07:59+00:00" + }, { "name": "phalcon/traits", "version": "4.0.0", @@ -2518,6 +2741,149 @@ } ], "time": "2025-11-10T16:43:36+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v6.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/94d0d5413126d81bffcd0de509fb9daf0363babd", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v6.4.42" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-08T07:06:12+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v6.4.40", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", + "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.40" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-19T20:33:22+00:00" } ], "aliases": [], From 64b66b065225c88730f8ec138c715431f86460f2 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 14 Jul 2026 23:13:52 -0500 Subject: [PATCH 11/47] use dirname(__FILE__) for path resolution --- .htrouter.php | 4 ++-- phinx.php | 4 ++-- public/index.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.htrouter.php b/.htrouter.php index ed41e815..6f6a64c8 100644 --- a/.htrouter.php +++ b/.htrouter.php @@ -4,10 +4,10 @@ parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ); -if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) { +if ($uri !== '/' && file_exists(dirname(__FILE__) . '/public' . $uri)) { return false; } $_GET['_url'] = $_SERVER['REQUEST_URI']; -require_once __DIR__ . '/public/index.php'; +require_once dirname(__FILE__) . '/public/index.php'; diff --git a/phinx.php b/phinx.php index 757d1ff2..5d021a69 100644 --- a/phinx.php +++ b/phinx.php @@ -3,8 +3,8 @@ use function Phalcon\Api\Core\appPath; use function Phalcon\Api\Core\envValue; -// This file will end up in the root of the project -require_once './library/Core/autoload.php'; +// This file lives in the root of the project +require_once dirname(__FILE__) . '/library/Core/autoload.php'; return [ 'paths' => [ diff --git a/public/index.php b/public/index.php index dbedab8b..b569822b 100644 --- a/public/index.php +++ b/public/index.php @@ -12,6 +12,6 @@ use Phalcon\Api\Bootstrap\Api; -require_once dirname(__DIR__) . '/library/Core/autoload.php'; +require_once dirname(__FILE__, 2) . '/library/Core/autoload.php'; (new Api())->run(); From b394d2bd55770ca26051c6afdc544e4df2c2be21 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 08:33:25 -0500 Subject: [PATCH 12/47] add talon scaffolding: bootstrap, env and per-suite phpunit configs --- composer.json | 1 + composer.lock | 1808 +++++++++++++++++++++++++++-- resources/phpunit.api.xml | 17 + resources/phpunit.cli.xml | 17 + resources/phpunit.integration.xml | 17 + resources/phpunit.xml.dist | 17 + tests/.env.test | 14 + tests/bootstrap.php | 38 + 8 files changed, 1851 insertions(+), 78 deletions(-) create mode 100644 resources/phpunit.api.xml create mode 100644 resources/phpunit.cli.xml create mode 100644 resources/phpunit.integration.xml create mode 100644 resources/phpunit.xml.dist create mode 100644 tests/.env.test create mode 100644 tests/bootstrap.php diff --git a/composer.json b/composer.json index 3bbfcb9c..b78387d6 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "phalcon/phalcon": "^6.0@alpha", "phalcon/talon": "^0.7.0", "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^10.5", "squizlabs/php_codesniffer": "^4.0" }, "scripts": { diff --git a/composer.lock b/composer.lock index fadc3c78..1f661c10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0a80cbef6089aa12c9e12a45b33936bb", + "content-hash": "a303fd850830e8d6dca752d6e5dcf143", "packages": [ { "name": "cakephp/chronos", @@ -2097,6 +2097,123 @@ }, "time": "2019-02-05T23:41:09+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, { "name": "pds/composer-script-names", "version": "1.0.0", @@ -2549,6 +2666,124 @@ ], "time": "2026-07-10T19:24:02+00:00" }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, { "name": "phpstan/phpstan", "version": "2.2.5", @@ -2614,151 +2849,1518 @@ "time": "2026-07-05T06:31:06+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { - "php": ">=7.2.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Standard interfaces for event handling.", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "events", - "psr", - "psr-14" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, - "time": "2019-01-08T18:20:26+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "4.0.1", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0525c73950de35ded110cffafb9892946d7771b5" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", - "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=7.2.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + "phpunit/phpunit": "^10.0" }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "phpcs", - "standards", - "static analysis" + "filesystem", + "iterator" ], "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" } ], - "time": "2025-11-10T16:43:36+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "symfony/browser-kit", - "version": "v6.4.42", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/94d0d5413126d81bffcd0de509fb9daf0363babd", - "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/dom-crawler": "^5.4|^6.0|^7.0" + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.64", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:50:35+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-10T16:43:36+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v6.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/94d0d5413126d81bffcd0de509fb9daf0363babd", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0|^7.0" }, "require-dev": { "symfony/css-selector": "^5.4|^6.0|^7.0", @@ -2884,6 +4486,56 @@ } ], "time": "2026-05-19T20:33:22+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], diff --git a/resources/phpunit.api.xml b/resources/phpunit.api.xml new file mode 100644 index 00000000..eac3c030 --- /dev/null +++ b/resources/phpunit.api.xml @@ -0,0 +1,17 @@ + + + + + ../tests/Api + + + + + ../api + ../cli + ../library + + + diff --git a/resources/phpunit.cli.xml b/resources/phpunit.cli.xml new file mode 100644 index 00000000..8782b194 --- /dev/null +++ b/resources/phpunit.cli.xml @@ -0,0 +1,17 @@ + + + + + ../tests/Cli + + + + + ../api + ../cli + ../library + + + diff --git a/resources/phpunit.integration.xml b/resources/phpunit.integration.xml new file mode 100644 index 00000000..e47ec9ea --- /dev/null +++ b/resources/phpunit.integration.xml @@ -0,0 +1,17 @@ + + + + + ../tests/Integration + + + + + ../api + ../cli + ../library + + + diff --git a/resources/phpunit.xml.dist b/resources/phpunit.xml.dist new file mode 100644 index 00000000..09f555e5 --- /dev/null +++ b/resources/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + ../tests/Unit + + + + + ../api + ../cli + ../library + + + diff --git a/tests/.env.test b/tests/.env.test new file mode 100644 index 00000000..7f364909 --- /dev/null +++ b/tests/.env.test @@ -0,0 +1,14 @@ +# Talon's own settings (Settings::fromEnv) read DATA_MYSQL_* / DATA_REDIS_*, +# which the application's DATA_API_* variables do not cover. Hosts are the +# compose service names; getenv() wins over these, so a native run can override +# them with real environment variables. + +DATA_MYSQL_HOST=mysql +DATA_MYSQL_PORT=3306 +DATA_MYSQL_USER=root +DATA_MYSQL_PASS=secret +DATA_MYSQL_NAME=phalcon_api +DATA_MYSQL_CHARSET=utf8mb4 + +DATA_REDIS_HOST=redis +DATA_REDIS_PORT=6379 diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..8b200f78 --- /dev/null +++ b/tests/bootstrap.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); + +use Dotenv\Dotenv; +use Phalcon\Talon\Settings; +use Phalcon\Talon\Talon; + +define('API_TESTS', true); + +/** + * The suite runs under the CLI SAPI, where there is no request URI. + */ +$_SERVER['REQUEST_URI'] = '/'; + +/** + * Registers the application namespaces, pulls in the Composer autoloader and + * loads the root .env. + */ +require_once dirname(__FILE__, 2) . '/library/Core/autoload.php'; + +/** + * Talon reads DATA_MYSQL_* / DATA_REDIS_*, while the application reads + * DATA_API_*. Settings::fromEnv() checks getenv() before $_ENV, so real + * container and CI variables win and this file only fills the gaps. + */ +Dotenv::createImmutable(dirname(__FILE__), '.env.test')->safeLoad(); + +Talon::boot(Settings::fromEnv()); From 5237807fe4a177c25ed300ba1bb5d064604ce6f0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 08:40:24 -0500 Subject: [PATCH 13/47] convert unit suite to talon format --- .../AbstractIntegrationTestCase.php | 404 ++++++++++++++++++ .../Library/Models/CompaniesTest.php | 110 +++++ .../Library/Models/CompaniesXProductsTest.php | 67 +++ .../Integration/Library/Traits/QueryTest.php | 187 ++++++++ .../Transformers/BaseTransformerTest.php | 50 +++ .../IndividualsTransformerTest.php | 159 +++++++ .../Transformers/ProductsTransformerTest.php | 167 ++++++++ .../Validation/CompaniesValidatorTest.php | 36 ++ tests/Support/Data.php | 342 +++++++++++++++ tests/Unit/BootstrapTest.php | 35 ++ tests/Unit/Cli/BaseTest.php | 53 +++ tests/Unit/Cli/BootstrapTest.php | 45 ++ tests/Unit/Cli/ClearCacheTest.php | 80 ++++ tests/Unit/Config/AutoloaderTest.php | 61 +++ tests/Unit/Config/ConfigTest.php | 31 ++ tests/Unit/Config/FunctionsTest.php | 83 ++++ tests/Unit/Config/ProvidersTest.php | 56 +++ tests/Unit/Library/BootstrapTest.php | 32 ++ tests/Unit/Library/Constants/FlagsTest.php | 26 ++ .../Library/Constants/RelationshipsTest.php | 30 ++ tests/Unit/Library/ErrorHandlerTest.php | 68 +++ tests/Unit/Library/Http/ResponseTest.php | 146 +++++++ tests/Unit/Library/Providers/CacheTest.php | 37 ++ tests/Unit/Library/Providers/ConfigTest.php | 44 ++ tests/Unit/Library/Providers/DatabaseTest.php | 38 ++ .../Library/Providers/ErrorHandlerTest.php | 42 ++ tests/Unit/Library/Providers/LoggerTest.php | 38 ++ .../Library/Providers/ModelsMetadataTest.php | 37 ++ tests/Unit/Library/Providers/ResponseTest.php | 34 ++ tests/Unit/Library/Providers/RouterTest.php | 71 +++ 30 files changed, 2609 insertions(+) create mode 100644 tests/Integration/AbstractIntegrationTestCase.php create mode 100644 tests/Integration/Library/Models/CompaniesTest.php create mode 100644 tests/Integration/Library/Models/CompaniesXProductsTest.php create mode 100644 tests/Integration/Library/Traits/QueryTest.php create mode 100644 tests/Integration/Library/Transformers/BaseTransformerTest.php create mode 100644 tests/Integration/Library/Transformers/IndividualsTransformerTest.php create mode 100644 tests/Integration/Library/Transformers/ProductsTransformerTest.php create mode 100644 tests/Integration/Library/Validation/CompaniesValidatorTest.php create mode 100644 tests/Support/Data.php create mode 100644 tests/Unit/BootstrapTest.php create mode 100644 tests/Unit/Cli/BaseTest.php create mode 100644 tests/Unit/Cli/BootstrapTest.php create mode 100644 tests/Unit/Cli/ClearCacheTest.php create mode 100644 tests/Unit/Config/AutoloaderTest.php create mode 100644 tests/Unit/Config/ConfigTest.php create mode 100644 tests/Unit/Config/FunctionsTest.php create mode 100644 tests/Unit/Config/ProvidersTest.php create mode 100644 tests/Unit/Library/BootstrapTest.php create mode 100644 tests/Unit/Library/Constants/FlagsTest.php create mode 100644 tests/Unit/Library/Constants/RelationshipsTest.php create mode 100644 tests/Unit/Library/ErrorHandlerTest.php create mode 100644 tests/Unit/Library/Http/ResponseTest.php create mode 100644 tests/Unit/Library/Providers/CacheTest.php create mode 100644 tests/Unit/Library/Providers/ConfigTest.php create mode 100644 tests/Unit/Library/Providers/DatabaseTest.php create mode 100644 tests/Unit/Library/Providers/ErrorHandlerTest.php create mode 100644 tests/Unit/Library/Providers/LoggerTest.php create mode 100644 tests/Unit/Library/Providers/ModelsMetadataTest.php create mode 100644 tests/Unit/Library/Providers/ResponseTest.php create mode 100644 tests/Unit/Library/Providers/RouterTest.php diff --git a/tests/Integration/AbstractIntegrationTestCase.php b/tests/Integration/AbstractIntegrationTestCase.php new file mode 100644 index 00000000..7a9bf163 --- /dev/null +++ b/tests/Integration/AbstractIntegrationTestCase.php @@ -0,0 +1,404 @@ + + * + * 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\Integration; + +use Phalcon\Api\Bootstrap\Api; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\CompaniesXProducts; +use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Mvc\Model\AbstractModel; +use Phalcon\Config\Config as PhConfig; +use Phalcon\Di\DiInterface; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use RuntimeException; + +use function array_keys; +use function array_map; +use function array_merge; +use function call_user_func; +use function count; +use function implode; +use function is_array; +use function rtrim; +use function sprintf; +use function uniqid; + +/** + * Ported from the Codeception `Helper\Integration` module. Boots the application + * container for each test and tracks the records a test creates so they can be + * removed afterwards. + */ +abstract class AbstractIntegrationTestCase extends AbstractUnitTestCase +{ + protected ?DiInterface $diContainer = null; + + /** @var array> */ + protected array $savedModels = []; + + /** @var array */ + protected array $savedRecords = []; + + /** @var array */ + protected array $options = ['rollback' => false]; + + protected function setUp(): void + { + parent::setUp(); + + $app = new Api(); + $this->diContainer = $app->getContainer(); + + if ($this->options['rollback']) { + $this->diContainer->get('db')->begin(); + } + + $this->savedModels = []; + $this->savedRecords = []; + } + + protected function tearDown(): void + { + if (!$this->options['rollback']) { + foreach ($this->savedRecords as $record) { + $record->delete(); + } + } else { + $this->diContainer->get('db')->rollback(); + } + + $this->diContainer->get('db')->close(); + + parent::tearDown(); + } + + protected function addCompanyRecord( + string $namePrefix = '', + string $addressPrefix = '', + string $cityPrefix = '', + string $phonePrefix = '' + ): Companies { + return $this->haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid($namePrefix), + 'address' => uniqid($addressPrefix), + 'city' => uniqid($cityPrefix), + 'phone' => uniqid($phonePrefix), + ] + ); + } + + protected function addCompanyXProduct(int $companyId, int $productId): CompaniesXProducts + { + return $this->haveRecordWithFields( + CompaniesXProducts::class, + [ + 'companyId' => $companyId, + 'productId' => $productId, + ] + ); + } + + protected function addIndividualTypeRecord(string $namePrefix = ''): IndividualTypes + { + return $this->haveRecordWithFields( + IndividualTypes::class, + [ + 'name' => uniqid($namePrefix), + 'description' => uniqid(), + ] + ); + } + + protected function addIndividualRecord(string $namePrefix = '', int $comId = 0, int $typeId = 0): Individuals + { + return $this->haveRecordWithFields( + Individuals::class, + [ + 'companyId' => $comId, + 'typeId' => $typeId, + 'prefix' => uniqid(), + 'first' => uniqid($namePrefix), + 'middle' => uniqid(), + 'last' => uniqid(), + 'suffix' => uniqid(), + ] + ); + } + + protected function addProductRecord(string $namePrefix = '', int $typeId = 0): Products + { + return $this->haveRecordWithFields( + Products::class, + [ + 'name' => uniqid($namePrefix), + 'typeId' => $typeId, + 'description' => uniqid(), + 'quantity' => 25, + 'price' => 19.99, + ] + ); + } + + protected function addProductTypeRecord(string $namePrefix = ''): ProductTypes + { + return $this->haveRecordWithFields( + ProductTypes::class, + [ + 'name' => uniqid($namePrefix), + 'description' => uniqid(), + ] + ); + } + + protected function grabDi(): ?DiInterface + { + return $this->diContainer; + } + + /** + * @return mixed + */ + protected function grabFromDi(string $name) + { + return $this->diContainer->get($name); + } + + /** + * Returns the relationships that a model has. + * + * @return array> + */ + protected function getModelRelationships(string $class): array + { + /** @var AbstractModel $model */ + $model = new $class(); + $manager = $model->getModelsManager(); + $relationships = $manager->getRelations($class); + + $data = []; + foreach ($relationships as $relationship) { + $data[] = [ + $relationship->getType(), + $relationship->getFields(), + $relationship->getReferencedModel(), + $relationship->getReferencedFields(), + $relationship->getOptions(), + ]; + } + + return $data; + } + + /** + * Get a record from $modelName with the fields provided. + * + * @param array $fields + * + * @return bool|AbstractModel + */ + protected function getRecordWithFields(string $modelName, array $fields = []) + { + $record = false; + if (count($fields) > 0) { + $conditions = ''; + $bind = []; + foreach ($fields as $field => $value) { + $conditions .= sprintf( + '%s = :%s: AND ', + $field, + $field + ); + $bind[$field] = $value; + } + + $conditions = rtrim($conditions, ' AND '); + + /** @var AbstractModel $record */ + $record = $modelName::findFirst( + [ + 'conditions' => $conditions, + 'bind' => $bind, + ] + ); + } + + return $record; + } + + /** + * @param array $configData + */ + protected function haveConfig(array $configData): void + { + $config = new PhConfig($configData); + $this->diContainer->set('config', $config); + } + + /** + * Checks model fields. + * + * @param array $fields + */ + protected function haveModelDefinition(string $modelName, array $fields): void + { + /** @var AbstractModel $model */ + $model = new $modelName(); + $metadata = $model->getModelsMetaData(); + $attributes = $metadata->getAttributes($model); + + $this->assertEquals( + count($fields), + count($attributes), + "Field count not correct for $modelName" + ); + + foreach ($fields as $value) { + $this->assertContains( + $value, + $attributes, + "Field not exists in $modelName" + ); + } + } + + /** + * Create a record for $modelName with the fields provided. + * + * @param array $fields + * + * @return mixed + */ + protected function haveRecordWithFields(string $modelName, array $fields = []) + { + $record = new $modelName(); + foreach ($fields as $key => $val) { + $record->set($key, $val); + } + + $result = $record->save(); + $this->savedModels[$modelName] = $fields; + $this->assertNotSame(false, $result); + + $this->savedRecords[] = $record; + + return $record; + } + + /** + * @param mixed $service + */ + protected function haveService(string $name, $service): void + { + $this->diContainer->set($name, $service); + } + + protected function removeService(string $name): void + { + if ($this->diContainer->has($name)) { + $this->diContainer->remove($name); + } + } + + /** + * Check that a record created with haveRecordWithFields can be fetched and + * that all its fields contain valid values. + * + * @param string|array $by + * @param array $except + * + * @return mixed + */ + protected function seeRecordFieldsValid(string $modelName, $by, array $except = []) + { + if (!isset($this->savedModels[$modelName])) { + throw new RuntimeException( + 'Should be used after haveModelWithFields with ' . $modelName + ); + } + + $fields = $this->savedModels[$modelName]; + if (!is_array($by)) { + $by = [$by]; + } + + $bySelector = implode( + ' AND ', + array_map( + static function ($key) { + return "$key = :$key:"; + }, + $by + ) + ); + + $byBind = []; + foreach ($by as $byVal) { + if (!isset($fields[$byVal])) { + throw new RuntimeException("Field $byVal is not set"); + } + $byBind[$byVal] = $fields[$byVal]; + } + + $record = call_user_func( + [ + $modelName, 'findFirst', + ], + [ + 'conditions' => $bySelector, + 'bind' => $byBind, + ] + ); + + if (!$record) { + $this->fail("Record $modelName not found"); + } + + foreach ($fields as $key => $val) { + if (isset($except[$key])) { + continue; + } + + $this->assertEquals( + $val, + $record->get($key), + "Field in $modelName for $key not valid" + ); + } + + return $record; + } + + /** + * Checks that a record exists and has the provided fields. + * + * @param array $by + * @param array $fields + */ + protected function seeRecordSaved(string $model, array $by, array $fields): void + { + $this->savedModels[$model] = array_merge($by, $fields); + + $record = $this->seeRecordFieldsValid( + $model, + array_keys($by), + array_keys($by) + ); + + $this->savedRecords[] = $record; + } +} diff --git a/tests/Integration/Library/Models/CompaniesTest.php b/tests/Integration/Library/Models/CompaniesTest.php new file mode 100644 index 00000000..82fd77ca --- /dev/null +++ b/tests/Integration/Library/Models/CompaniesTest.php @@ -0,0 +1,110 @@ + + * + * 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\Integration\Library\Models; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Filter\Filter; + +use function count; + +final class CompaniesTest extends AbstractIntegrationTestCase +{ + public function testValidateModel(): void + { + $this->haveModelDefinition( + Companies::class, + [ + 'id', + 'name', + 'address', + 'city', + 'phone', + ] + ); + } + + public function testValidateFilters(): void + { + $model = new Companies(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'address' => Filter::FILTER_STRING, + 'city' => Filter::FILTER_STRING, + 'phone' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } + + public function testValidateRelationships(): void + { + $actual = $this->getModelRelationships(Companies::class); + $expected = [ + [ + 2, + 'id', + Individuals::class, + 'companyId', + ['alias' => Relationships::INDIVIDUALS, 'reusable' => true], + ], + [ + 4, + 'id', + Products::class, + 'id', + ['alias' => Relationships::PRODUCTS, 'reusable' => true], + ], + ]; + + $this->assertSame($expected, $actual); + } + + public function testValidateUniqueName(): void + { + $companyOne = new Companies(); + /** @var Companies $companyOne */ + $result = $companyOne + ->set('name', 'acme') + ->set('address', '123 Phalcon way') + ->set('city', 'World') + ->set('phone', '555-999-4444') + ->save() + ; + $this->assertNotEquals(false, $result); + + $companyTwo = new Companies(); + /** @var Companies $companyTwo */ + $result = $companyTwo + ->set('name', 'acme') + ->set('address', '123 Phalcon way') + ->set('city', 'World') + ->set('phone', '555-999-4444') + ->save() + ; + $this->assertSame(false, $result); + $this->assertSame(1, count($companyTwo->getMessages())); + + $messages = $companyTwo->getMessages(); + $expected = 'The company name already exists in the database'; + $actual = $messages[0]->getMessage(); + $this->assertSame($expected, $actual); + + $result = $companyOne->delete(); + $this->assertNotEquals(false, $result); + } +} diff --git a/tests/Integration/Library/Models/CompaniesXProductsTest.php b/tests/Integration/Library/Models/CompaniesXProductsTest.php new file mode 100644 index 00000000..03627675 --- /dev/null +++ b/tests/Integration/Library/Models/CompaniesXProductsTest.php @@ -0,0 +1,67 @@ + + * + * 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\Integration\Library\Models; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\CompaniesXProducts; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Filter\Filter; + +final class CompaniesXProductsTest extends AbstractIntegrationTestCase +{ + public function testValidateModel(): void + { + $this->haveModelDefinition( + CompaniesXProducts::class, + [ + 'companyId', + 'productId', + ] + ); + } + + public function testValidateFilters(): void + { + $model = new CompaniesXProducts(); + $expected = [ + 'companyId' => Filter::FILTER_ABSINT, + 'productId' => Filter::FILTER_ABSINT, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } + + public function testValidateRelationships(): void + { + $actual = $this->getModelRelationships(CompaniesXProducts::class); + $expected = [ + [ + 0, + 'companyId', + Companies::class, + 'id', + ['alias' => Relationships::COMPANIES, 'reusable' => true], + ], + [ + 0, + 'productId', + Products::class, + 'id', + ['alias' => Relationships::PRODUCTS, 'reusable' => true], + ], + ]; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Integration/Library/Traits/QueryTest.php b/tests/Integration/Library/Traits/QueryTest.php new file mode 100644 index 00000000..18c91237 --- /dev/null +++ b/tests/Integration/Library/Traits/QueryTest.php @@ -0,0 +1,187 @@ + + * + * 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\Integration\Library\Traits; + +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\QueryTrait; +use Phalcon\Api\Traits\TokenTrait; +use Phalcon\Cache\Cache; +use Phalcon\Config\Config; +use Phalcon\Encryption\Security\JWT\Builder; +use Phalcon\Encryption\Security\JWT\Signer\Hmac; + +use function count; +use function Phalcon\Api\Core\appPath; +use function uniqid; + +final class QueryTest extends AbstractIntegrationTestCase +{ + use TokenTrait; + use QueryTrait; + + public function testGetUserByUsernameAndPassword(): void + { + /** @var Users $result */ + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + $dbUser = $this->getUserByUsernameAndPassword( + $config, + $cache, + Data::$testUsername, + Data::$testPassword + ); + + $this->assertNotNull($dbUser); + } + + public function testGetUserByWrongUsernameAndPasswordReturnsNull(): void + { + /** @var Users $result */ + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + $dbUser = $this->getUserByUsernameAndPassword( + $config, + $cache, + Data::$testUsername, + 'nothing' + ); + + $this->assertNull($dbUser); + } + + /** + * @throws ModelException + */ + public function testGetUserByWrongTokenReturnsNull(): void + { + /** @var Users $result */ + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + $signer = new Hmac(); + $builder = new Builder($signer); + $token = $builder + ->setIssuer('https://somedomain.com') + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + $actual = $this->getUserByToken($config, $cache, $token); + + $this->assertNull($actual); + } + + public function testGetCompaniesCachedData(): void + { + $configData = require appPath('./library/Core/config.php'); + $this->assertTrue($configData['app']['devMode']); + + $configData['app']['devMode'] = false; + /** @var Config $config */ + $config = new Config($configData); + $container = $this->grabDi(); + $container->set('config', $config); + $this->assertFalse($config->path('app.devMode')); + + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + $cache->clear(); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + $this->assertFalse($config->path('app.devMode')); + + /** + * Company 1 + */ + $comName = uniqid('com-cached-'); + $comOne = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => $comName, + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + $results = $this->getRecords($config, $cache, Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + + /** + * Get the record again but ensure the name has been changed + */ + $result = $comOne->set('name', 'com-cached-change') + ->save() + ; + $this->assertNotEquals(false, $result); + + /** + * This should return the cached result + */ + $results = $this->getRecords($config, $cache, Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + } +} diff --git a/tests/Integration/Library/Transformers/BaseTransformerTest.php b/tests/Integration/Library/Transformers/BaseTransformerTest.php new file mode 100644 index 00000000..a6c28536 --- /dev/null +++ b/tests/Integration/Library/Transformers/BaseTransformerTest.php @@ -0,0 +1,50 @@ + + * + * 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\Integration\Library\Transformers; + +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Transformers\BaseTransformer; + +final class BaseTransformerTest extends AbstractIntegrationTestCase +{ + /** + * @throws ModelException + */ + public function testTransformer(): void + { + /** @var Companies $company */ + $company = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => 'acme', + 'address' => '123 Phalcon way', + 'city' => 'World', + 'phone' => '555-999-4444', + ] + ); + + $transformer = new BaseTransformer(); + $expected = [ + 'id' => $company->get('id'), + 'name' => $company->get('name'), + 'address' => $company->get('address'), + 'city' => $company->get('city'), + 'phone' => $company->get('phone'), + ]; + + $this->assertSame($expected, $transformer->transform($company)); + } +} diff --git a/tests/Integration/Library/Transformers/IndividualsTransformerTest.php b/tests/Integration/Library/Transformers/IndividualsTransformerTest.php new file mode 100644 index 00000000..df5a6fd1 --- /dev/null +++ b/tests/Integration/Library/Transformers/IndividualsTransformerTest.php @@ -0,0 +1,159 @@ + + * + * 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\Integration\Library\Transformers; + +use League\Fractal\Manager; +use League\Fractal\Resource\Collection; +use League\Fractal\Serializer\JsonApiSerializer; +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Transformers\IndividualsTransformer; + +use function Phalcon\Api\Core\envValue; +use function sprintf; +use function uniqid; + +final class IndividualsTransformerTest extends AbstractIntegrationTestCase +{ + /** + * @throws ModelException + */ + public function testTransformer(): void + { + /** @var Companies $company */ + $company = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid('com-a-'), + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + /** @var IndividualTypes $individualType */ + $individualType = $this->haveRecordWithFields( + IndividualTypes::class, + [ + 'name' => 'my type', + 'description' => 'description of my type', + ] + ); + + /** @var Individuals $individual */ + $individual = $this->haveRecordWithFields( + Individuals::class, + [ + 'companyId' => $company->get('id'), + 'typeId' => $individualType->get('id'), + 'prefix' => uniqid(), + 'first' => uniqid('first-'), + 'middle' => uniqid(), + 'last' => uniqid('last-'), + 'suffix' => uniqid(), + ] + ); + + $url = envValue('APP_URL', 'http://localhost'); + $manager = new Manager(); + $manager->setSerializer(new JsonApiSerializer($url)); + $manager->parseIncludes([Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES]); + $resource = new Collection([$individual], new IndividualsTransformer(), Relationships::INDIVIDUALS); + $results = $manager->createData($resource) + ->toArray() + ; + $expected = [ + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => (string) $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + 'type' => Relationships::COMPANIES, + 'id' => (string) $company->get('id'), + ], + ], + Relationships::INDIVIDUAL_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => (string) $individualType->get('id'), + ], + ], + ], + ], + ], + 'included' => [ + Data::companiesResponse($company), + Data::individualTypeResponse($individualType), + ], + ]; + + $this->assertSame($expected, $results); + } +} diff --git a/tests/Integration/Library/Transformers/ProductsTransformerTest.php b/tests/Integration/Library/Transformers/ProductsTransformerTest.php new file mode 100644 index 00000000..4960b9b1 --- /dev/null +++ b/tests/Integration/Library/Transformers/ProductsTransformerTest.php @@ -0,0 +1,167 @@ + + * + * 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\Integration\Library\Transformers; + +use League\Fractal\Manager; +use League\Fractal\Resource\Collection; +use League\Fractal\Serializer\JsonApiSerializer; +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\CompaniesXProducts; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Transformers\ProductsTransformer; + +use function Phalcon\Api\Core\envValue; +use function sprintf; +use function uniqid; + +final class ProductsTransformerTest extends AbstractIntegrationTestCase +{ + /** + * @throws ModelException + */ + public function testTransformer(): void + { + /** @var Companies $company */ + $company = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid('com-a-'), + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + /** @var ProductTypes $productType */ + $productType = $this->haveRecordWithFields( + ProductTypes::class, + [ + 'name' => 'my type', + 'description' => 'description of my type', + ] + ); + + /** @var Products $product */ + $product = $this->haveRecordWithFields( + Products::class, + [ + 'name' => 'my product', + 'typeId' => $productType->get('id'), + 'description' => 'my product description', + 'quantity' => 99, + 'price' => 19.99, + ] + ); + + /** @var CompaniesXProducts $glue */ + $glue = $this->haveRecordWithFields( + CompaniesXProducts::class, + [ + 'companyId' => $company->get('id'), + 'productId' => $product->get('id'), + ] + ); + + $url = envValue('APP_URL', 'http://localhost'); + $manager = new Manager(); + $manager->setSerializer(new JsonApiSerializer($url)); + $manager->parseIncludes([Relationships::COMPANIES, Relationships::PRODUCT_TYPES]); + $resource = new Collection([$product], new ProductsTransformer(), Relationships::PRODUCTS); + $results = $manager->createData($resource) + ->toArray() + ; + $expected = [ + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => (string) $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + [ + 'type' => Relationships::COMPANIES, + 'id' => (string) $company->get('id'), + ], + ], + ], + Relationships::PRODUCT_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => (string) $productType->get('id'), + ], + ], + ], + ], + ], + 'included' => [ + Data::companiesResponse($company), + Data::productTypeResponse($productType), + ], + ]; + + $this->assertEquals($expected, $results); + } +} diff --git a/tests/Integration/Library/Validation/CompaniesValidatorTest.php b/tests/Integration/Library/Validation/CompaniesValidatorTest.php new file mode 100644 index 00000000..c2e52bf5 --- /dev/null +++ b/tests/Integration/Library/Validation/CompaniesValidatorTest.php @@ -0,0 +1,36 @@ + + * + * 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\Integration\Library\Validation; + +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Validation\CompaniesValidator; + +use function count; + +final class CompaniesValidatorTest extends AbstractIntegrationTestCase +{ + public function testValidator(): void + { + $validation = new CompaniesValidator(); + $_POST = [ + 'name' => '', + 'address' => '123 Phalcon way', + 'city' => 'World', + 'phone' => '555-999-4444', + ]; + $messages = $validation->validate($_POST); + $this->assertSame(1, count($messages)); + $this->assertSame('The company name is required', $messages[0]->getMessage()); + } +} diff --git a/tests/Support/Data.php b/tests/Support/Data.php new file mode 100644 index 00000000..497f27f5 --- /dev/null +++ b/tests/Support/Data.php @@ -0,0 +1,342 @@ + + * + * 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\Support; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Mvc\Model\AbstractModel; + +use function Phalcon\Api\Core\envValue; +use function sprintf; + +class Data +{ + public static $companiesSortUrl = '/companies?sort=%s'; + public static $companiesUrl = '/companies'; + public static $companiesRecordUrl = '/companies/%s'; + public static $companiesRecordIncludesUrl = '/companies/%s?includes=%s'; + public static $loginUrl = '/login'; + public static $individualsUrl = '/individuals'; + public static $individualsRecordUrl = '/individuals/%s'; + public static $individualsRecordIncludesUrl = '/individuals/%s?includes=%s'; + public static $individualTypesUrl = '/individual-types'; + public static $individualTypesRecordUrl = '/individual-types/%s'; + public static $individualTypesRecordIncludesUrl = '/individual-types/%s?includes=%s'; + public static $productsUrl = '/products'; + public static $productsRecordUrl = '/products/%s'; + public static $productsRecordIncludesUrl = '/products/%s?includes=%s'; + public static $productTypesUrl = '/product-types'; + public static $productTypesRecordUrl = '/product-types/%s'; + public static $productTypesRecordIncludesUrl = '/product-types/%s?includes=%s'; + public static $usersUrl = '/users'; + public static $wrongUrl = '/sommething'; + + public static $strongPassphrase = 'DR^3*ZwnAHKc9yP$YSpW98dsmHJBax5&'; + public static $testIssuer = 'https://niden.net'; + public static $testPassword = 'testpass'; + public static $testTokenId = '110011'; + public static $testTokenPassword = 'DR^4*ZwnAHKc0yP$YSpW09dsmHJBax6&'; + public static $testUsername = 'testuser'; + + /** + * @return array + */ + public static function loginJson() + { + return [ + 'username' => self::$testUsername, + 'password' => self::$testPassword, + ]; + } + + /** + * @param $name + * @param string $address + * @param string $city + * @param string $phone + * + * @return array + */ + public static function companyAddJson($name, $address = '', $city = '', $phone = '') + { + return [ + 'name' => $name, + 'address' => $address, + 'city' => $city, + 'phone' => $phone, + ]; + } + + /** + * @param Companies $record + * + * @return array + * @throws ModelException + */ + public static function companiesAddResponse(Companies $record): array + { + return [ + 'type' => Relationships::COMPANIES, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'name' => $record->get('name'), + 'address' => $record->get('address'), + 'city' => $record->get('city'), + 'phone' => $record->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param Companies $record + * + * @return array + * @throws ModelException + */ + public static function companiesResponse(Companies $record): array + { + return [ + 'type' => Relationships::COMPANIES, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'name' => $record->get('name'), + 'address' => $record->get('address'), + 'city' => $record->get('city'), + 'phone' => $record->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + ], + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/products', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + 'related' => sprintf( + '%s/%s/%s/products', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + ] + ], + Relationships::INDIVIDUALS => [ + "links" => [ + 'self' => sprintf( + '%s/%s/%s/relationships/individuals', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + 'related' => sprintf( + '%s/%s/%s/individuals', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + ], + ], + ], + ]; + } + + /** + * @param Individuals $record + * + * @return array + * @throws ModelException + */ + public static function individualResponse(Individuals $record): array + { + return [ + 'type' => Relationships::INDIVIDUALS, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'companyId' => $record->get('companyId'), + 'typeId' => $record->get('typeId'), + 'prefix' => $record->get('prefix'), + 'first' => $record->get('first'), + 'middle' => $record->get('middle'), + 'last' => $record->get('last'), + 'suffix' => $record->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUALS, + $record->get('id') + ), + ], + ]; + } + + /** + * @param IndividualTypes $record + * + * @return array + * @throws ModelException + */ + public static function individualTypeResponse(IndividualTypes $record): array + { + return [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'name' => $record->get('name'), + 'description' => $record->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param Products $record + * + * @return array + * @throws ModelException + */ + public static function productResponse(Products $record): array + { + return [ + 'type' => Relationships::PRODUCTS, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'typeId' => $record->get('typeId'), + 'name' => $record->get('name'), + 'description' => $record->get('description'), + 'quantity' => $record->get('quantity'), + 'price' => $record->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCTS, + $record->get('id') + ), + ], + ]; + } + + /** + * @param Products $record + * + * @return array + * @throws ModelException + */ + public static function productFieldsResponse(Products $record): array + { + return [ + 'type' => Relationships::PRODUCTS, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'name' => $record->get('name'), + 'price' => $record->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCTS, + $record->get('id') + ), + ], + ]; + } + + /** + * @param ProductTypes $record + * + * @return array + * @throws ModelException + */ + public static function productTypeResponse(ProductTypes $record): array + { + return [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'name' => $record->get('name'), + 'description' => $record->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws ModelException + */ + public static function userResponse(AbstractModel $record) + { + return [ + 'type' => Relationships::USERS, + 'id' => (string) $record->get('id'), + 'attributes' => [ + 'status' => $record->get('status'), + 'username' => $record->get('username'), + 'issuer' => $record->get('issuer'), + 'tokenPassword' => $record->get('tokenPassword'), + 'tokenId' => $record->get('tokenId'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::USERS, + $record->get('id') + ), + ], + ]; + } +} diff --git a/tests/Unit/BootstrapTest.php b/tests/Unit/BootstrapTest.php new file mode 100644 index 00000000..663b1dd7 --- /dev/null +++ b/tests/Unit/BootstrapTest.php @@ -0,0 +1,35 @@ + + * + * 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 Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function Phalcon\Api\Core\appPath; + +final class BootstrapTest extends AbstractUnitTestCase +{ + public function testBootstrap(): void + { + ob_start(); + require appPath('public/index.php'); + $actual = ob_get_contents(); + ob_end_clean(); + + $results = json_decode($actual, true); + $this->assertSame('1.0', $results['jsonapi']['version']); + $this->assertTrue(empty($results['data'])); + // Was Codeception's HttpCode::getDescription(404), which renders exactly this. + $this->assertSame('404 (Not Found)', $results['errors'][0]); + } +} diff --git a/tests/Unit/Cli/BaseTest.php b/tests/Unit/Cli/BaseTest.php new file mode 100644 index 00000000..3cef5af5 --- /dev/null +++ b/tests/Unit/Cli/BaseTest.php @@ -0,0 +1,53 @@ + + * + * 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\Cli; + +use Phalcon\Api\Cli\Tasks\MainTask; +use Phalcon\Di\FactoryDefault\Cli; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function ob_end_clean; +use function ob_get_contents; +use function ob_start; + +use const PHP_EOL; + +final class BaseTest extends AbstractUnitTestCase +{ + public function testOutput(): void + { + $container = new Cli(); + $task = new MainTask(); + $task->setDI($container); + + ob_start(); + $task->mainAction(); + $actual = ob_get_contents(); + ob_end_clean(); + + $year = date('Y'); + $expected = "" + . "******************************************************" . PHP_EOL + . " Phalcon Team | (C) {$year}" . PHP_EOL + . "******************************************************" . PHP_EOL + . "" . PHP_EOL + . "Usage: runCli " . PHP_EOL + . "" . PHP_EOL + . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL + . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL + . PHP_EOL; + + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Cli/BootstrapTest.php b/tests/Unit/Cli/BootstrapTest.php new file mode 100644 index 00000000..244ff2cf --- /dev/null +++ b/tests/Unit/Cli/BootstrapTest.php @@ -0,0 +1,45 @@ + + * + * 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\Cli; + +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function Phalcon\Api\Core\appPath; + +use const PHP_EOL; + +final class BootstrapTest extends AbstractUnitTestCase +{ + public function testBootstrap(): void + { + ob_start(); + require appPath('cli/cli.php'); + $actual = ob_get_contents(); + ob_end_clean(); + + $year = date('Y'); + $expected = "" // Here just for readability + . "******************************************************" . PHP_EOL + . " Phalcon Team | (C) {$year}" . PHP_EOL + . "******************************************************" . PHP_EOL + . "" . PHP_EOL + . "Usage: runCli " . PHP_EOL + . "" . PHP_EOL + . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL + . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL + . PHP_EOL; + + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Cli/ClearCacheTest.php b/tests/Unit/Cli/ClearCacheTest.php new file mode 100644 index 00000000..40c96f0c --- /dev/null +++ b/tests/Unit/Cli/ClearCacheTest.php @@ -0,0 +1,80 @@ + + * + * 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\Cli; + +use FilesystemIterator; +use Phalcon\Api\Cli\Tasks\ClearcacheTask; +use Phalcon\Api\Providers\CacheDataProvider; +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Di\FactoryDefault\Cli; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function fclose; +use function iterator_count; +use function ob_end_clean; +use function ob_get_contents; +use function ob_start; +use function Phalcon\Api\Core\appPath; +use function uniqid; + +final class ClearCacheTest extends AbstractUnitTestCase +{ + public function testClearCache(): void + { + require appPath('vendor/autoload.php'); + + $path = appPath('/storage/cache/data'); + $container = new Cli(); + $config = new ConfigProvider(); + $config->register($container); + $cache = new CacheDataProvider(); + $cache->register($container); + $task = new ClearcacheTask(); + $task->setDI($container); + + $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); + $count = iterator_count($iterator); + + $this->createFile(); + $this->createFile(); + $this->createFile(); + $this->createFile(); + + $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); + $this->assertSame($count + 4, iterator_count($iterator)); + + ob_start(); + $task->mainAction(); + $actual = ob_get_contents(); + ob_end_clean(); + + // Codeception's assertGreaterOrEquals is spelled assertGreaterThanOrEqual in + // PHPUnit. NOTE: both assertions are vacuous - strpos() returns false when the + // needle is absent, and false >= 0 is true. assertStringContainsString is almost + // certainly the intent; left as-is because that is a change of test logic. + $this->assertGreaterThanOrEqual(0, strpos($actual, 'Clearing Cache folders')); + $this->assertGreaterThanOrEqual(0, strpos($actual, 'Cleared Cache folders')); + + $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); + $this->assertSame(1, iterator_count($iterator)); + } + + private function createFile(): void + { + $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; + $pointer = fopen($name, 'wb'); + fwrite($pointer, 'test'); + fclose($pointer); + } +} diff --git a/tests/Unit/Config/AutoloaderTest.php b/tests/Unit/Config/AutoloaderTest.php new file mode 100644 index 00000000..a991e104 --- /dev/null +++ b/tests/Unit/Config/AutoloaderTest.php @@ -0,0 +1,61 @@ + + * + * 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\Config; + +use Phalcon\Api\Http\Response; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function function_exists; +use function Phalcon\Api\Core\appPath; + +final class AutoloaderTest extends AbstractUnitTestCase +{ + public function testAutoloader(): void + { + require appPath('library/Core/autoload.php'); + + $class = new Response(); + $this->assertTrue($class instanceof Response); + $this->assertTrue(function_exists('Phalcon\Api\Core\envValue')); + } + + public function testDotenvVariables(): void + { + require appPath('library/Core/autoload.php'); + + $this->assertNotEquals(false, $_ENV['APP_DEBUG']); + $this->assertNotEquals(false, $_ENV['APP_ENV']); + $this->assertNotEquals(false, $_ENV['APP_URL']); + $this->assertNotEquals(false, $_ENV['APP_NAME']); + $this->assertNotEquals(false, $_ENV['APP_BASE_URI']); + $this->assertNotEquals(false, $_ENV['APP_SUPPORT_EMAIL']); + $this->assertNotEquals(false, $_ENV['APP_TIMEZONE']); + $this->assertNotEquals(false, $_ENV['CACHE_PREFIX']); + $this->assertNotEquals(false, $_ENV['CACHE_LIFETIME']); + $this->assertNotEquals(false, $_ENV['DATA_API_MYSQL_NAME']); + $this->assertNotEquals(false, $_ENV['LOGGER_DEFAULT_FILENAME']); + $this->assertNotEquals(false, $_ENV['VERSION']); + + $this->assertSame('true', $_ENV['APP_DEBUG']); + $this->assertSame('development', $_ENV['APP_ENV']); + $this->assertSame('http://api.phalcon.ld', $_ENV['APP_URL']); + $this->assertSame('/', $_ENV['APP_BASE_URI']); + $this->assertSame('team@phalcon.io', $_ENV['APP_SUPPORT_EMAIL']); + $this->assertSame('UTC', $_ENV['APP_TIMEZONE']); + $this->assertSame('api_cache_', $_ENV['CACHE_PREFIX']); + $this->assertSame('86400', $_ENV['CACHE_LIFETIME']); + $this->assertSame('api', $_ENV['LOGGER_DEFAULT_FILENAME']); + $this->assertSame('20180401', $_ENV['VERSION']); + } +} diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php new file mode 100644 index 00000000..ba6d62a1 --- /dev/null +++ b/tests/Unit/Config/ConfigTest.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\Tests\Unit\Config; + +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function is_array; +use function Phalcon\Api\Core\appPath; + +final class ConfigTest extends AbstractUnitTestCase +{ + public function testConfig(): void + { + $config = require(appPath('library/Core/config.php')); + + $this->assertTrue(is_array($config)); + $this->assertTrue(isset($config['app'])); + $this->assertTrue(isset($config['cache'])); + } +} diff --git a/tests/Unit/Config/FunctionsTest.php b/tests/Unit/Config/FunctionsTest.php new file mode 100644 index 00000000..55609e10 --- /dev/null +++ b/tests/Unit/Config/FunctionsTest.php @@ -0,0 +1,83 @@ + + * + * 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\Config; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function Phalcon\Api\Core\appPath; +use function Phalcon\Api\Core\appUrl; +use function Phalcon\Api\Core\envValue; + +final class FunctionsTest extends AbstractUnitTestCase +{ + private array $store; + + protected function setUp(): void + { + parent::setUp(); + + $this->store = $_ENV ?? []; + } + + protected function tearDown(): void + { + $_ENV = $this->store; + + parent::tearDown(); + } + + public function testAppPath(): void + { + $path = dirname(__FILE__, 4); + + $this->assertSame($path, appPath()); + } + + public function testAppPathWithParameter(): void + { + $path = dirname(__FILE__, 4) . '/library/Core/config.php'; + + $this->assertSame($path, appPath('library/Core/config.php')); + } + + public function testEnvValueAsFalse(): void + { + $_ENV['SOMEVAL'] = false; + + $this->assertFalse(envValue('SOMEVAL')); + } + + public function testEnvValueAsTrue(): void + { + $_ENV['SOMEVAL'] = true; + + $this->assertTrue(envValue('SOMEVAL')); + } + + public function testEnvValueWithValue(): void + { + $_ENV['SOMEVAL'] = 'someval'; + + $this->assertSame('someval', envValue('SOMEVAL')); + } + + public function testAppUrlWithUrl(): void + { + $this->assertSame( + 'http://api.phalcon.ld/companies/1', + appUrl(Relationships::COMPANIES, 1) + ); + } +} diff --git a/tests/Unit/Config/ProvidersTest.php b/tests/Unit/Config/ProvidersTest.php new file mode 100644 index 00000000..bc7bd3cc --- /dev/null +++ b/tests/Unit/Config/ProvidersTest.php @@ -0,0 +1,56 @@ + + * + * 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\Config; + +use Phalcon\Api\Providers\CliDispatcherProvider; +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\DatabaseProvider; +use Phalcon\Api\Providers\ErrorHandlerProvider; +use Phalcon\Api\Providers\LoggerProvider; +use Phalcon\Api\Providers\ModelsMetadataProvider; +use Phalcon\Api\Providers\RequestProvider; +use Phalcon\Api\Providers\ResponseProvider; +use Phalcon\Api\Providers\RouterProvider; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function Phalcon\Api\Core\appPath; + +final class ProvidersTest extends AbstractUnitTestCase +{ + public function testApiProviders(): void + { + $providers = require(appPath('api/config/providers.php')); + + $this->assertSame(ConfigProvider::class, $providers[0]); + $this->assertSame(LoggerProvider::class, $providers[1]); + $this->assertSame(ErrorHandlerProvider::class, $providers[2]); + $this->assertSame(DatabaseProvider::class, $providers[3]); + $this->assertSame(ModelsMetadataProvider::class, $providers[4]); + $this->assertSame(RequestProvider::class, $providers[5]); + $this->assertSame(ResponseProvider::class, $providers[6]); + $this->assertSame(RouterProvider::class, $providers[7]); + } + + public function testCliProviders(): void + { + $providers = require(appPath('cli/config/providers.php')); + + $this->assertSame(ConfigProvider::class, $providers[0]); + $this->assertSame(LoggerProvider::class, $providers[1]); + $this->assertSame(ErrorHandlerProvider::class, $providers[2]); + $this->assertSame(DatabaseProvider::class, $providers[3]); + $this->assertSame(ModelsMetadataProvider::class, $providers[4]); + $this->assertSame(CliDispatcherProvider::class, $providers[5]); + } +} diff --git a/tests/Unit/Library/BootstrapTest.php b/tests/Unit/Library/BootstrapTest.php new file mode 100644 index 00000000..30509f79 --- /dev/null +++ b/tests/Unit/Library/BootstrapTest.php @@ -0,0 +1,32 @@ + + * + * 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\Library; + +use Phalcon\Api\Bootstrap\Api; +use Phalcon\Api\Http\Response; +use Phalcon\Di\FactoryDefault; +use Phalcon\Mvc\Micro; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class BootstrapTest extends AbstractUnitTestCase +{ + public function testBootstrap(): void + { + $bootstrap = new Api(); + + $this->assertTrue($bootstrap->getContainer() instanceof FactoryDefault); + $this->assertTrue($bootstrap->getResponse() instanceof Response); + $this->assertTrue($bootstrap->getApplication() instanceof Micro); + } +} diff --git a/tests/Unit/Library/Constants/FlagsTest.php b/tests/Unit/Library/Constants/FlagsTest.php new file mode 100644 index 00000000..6392b848 --- /dev/null +++ b/tests/Unit/Library/Constants/FlagsTest.php @@ -0,0 +1,26 @@ + + * + * 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\Library\Constants; + +use Phalcon\Api\Constants\Flags; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class FlagsTest extends AbstractUnitTestCase +{ + public function testConstants(): void + { + $this->assertSame(1, Flags::ACTIVE); + $this->assertSame(2, Flags::INACTIVE); + } +} diff --git a/tests/Unit/Library/Constants/RelationshipsTest.php b/tests/Unit/Library/Constants/RelationshipsTest.php new file mode 100644 index 00000000..9b9e38c2 --- /dev/null +++ b/tests/Unit/Library/Constants/RelationshipsTest.php @@ -0,0 +1,30 @@ + + * + * 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\Library\Constants; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class RelationshipsTest extends AbstractUnitTestCase +{ + public function testConstants(): void + { + $this->assertSame('companies', Relationships::COMPANIES); + $this->assertSame('individual-types', Relationships::INDIVIDUAL_TYPES); + $this->assertSame('individuals', Relationships::INDIVIDUALS); + $this->assertSame('product-types', Relationships::PRODUCT_TYPES); + $this->assertSame('products', Relationships::PRODUCTS); + $this->assertSame('users', Relationships::USERS); + } +} diff --git a/tests/Unit/Library/ErrorHandlerTest.php b/tests/Unit/Library/ErrorHandlerTest.php new file mode 100644 index 00000000..c7946195 --- /dev/null +++ b/tests/Unit/Library/ErrorHandlerTest.php @@ -0,0 +1,68 @@ + + * + * 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\Library; + +use Phalcon\Api\ErrorHandler; +use Phalcon\Api\Logger; +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\LoggerProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function Phalcon\Api\Core\appPath; + +final class ErrorHandlerTest extends AbstractUnitTestCase +{ + public function testLogErrorOnError(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new LoggerProvider(); + $provider->register($diContainer); + + /** @var Config $config */ + $config = $diContainer->getShared('config'); + /** @var Logger $logger */ + $logger = $diContainer->getShared('logger'); + $handler = new ErrorHandler($logger, $config); + + $handler->handle(1, 'test error', 'file.php', 4); + $fileName = appPath('storage/logs/api.log'); + $expected = '[ERROR] [#:1]-[L: 4] : test error (file.php)'; + + $this->assertFileContentsContains($fileName, $expected); + } + + public function testLogErrorOnShutdown(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new LoggerProvider(); + $provider->register($diContainer); + + /** @var Config $config */ + $config = $diContainer->getShared('config'); + /** @var Logger $logger */ + $logger = $diContainer->getShared('logger'); + $handler = new ErrorHandler($logger, $config); + + $handler->shutdown(); + $fileName = appPath('storage/logs/api.log'); + $expected = '[INFO] Shutdown completed'; + + $this->assertFileContentsContains($fileName, $expected); + } +} diff --git a/tests/Unit/Library/Http/ResponseTest.php b/tests/Unit/Library/Http/ResponseTest.php new file mode 100644 index 00000000..c31fcdef --- /dev/null +++ b/tests/Unit/Library/Http/ResponseTest.php @@ -0,0 +1,146 @@ + + * + * 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\Library\Http; + +use Phalcon\Api\Http\Response; +use Phalcon\Messages\Message; +use Phalcon\Messages\Messages; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function is_string; +use function json_decode; + +final class ResponseTest extends AbstractUnitTestCase +{ + public function testHttpCodes(): void + { + $response = new Response(); + $this->assertSame('200 (OK)', $response->getHttpCodeDescription($response::OK)); + $this->assertSame('301 (Moved Permanently)', $response->getHttpCodeDescription($response::MOVED_PERMANENTLY)); + $this->assertSame('302 (Found)', $response->getHttpCodeDescription($response::FOUND)); + $this->assertSame('307 (Temporary Redirect)', $response->getHttpCodeDescription($response::TEMPORARY_REDIRECT)); + $this->assertSame('308 (Permanent Redirect)', $response->getHttpCodeDescription($response::PERMANENTLY_REDIRECT)); + $this->assertSame('400 (Bad Request)', $response->getHttpCodeDescription($response::BAD_REQUEST)); + $this->assertSame('401 (Unauthorized)', $response->getHttpCodeDescription($response::UNAUTHORIZED)); + $this->assertSame('403 (Forbidden)', $response->getHttpCodeDescription($response::FORBIDDEN)); + $this->assertSame('404 (Not Found)', $response->getHttpCodeDescription($response::NOT_FOUND)); + $this->assertSame( + '500 (Internal Server Error)', + $response->getHttpCodeDescription($response::INTERNAL_SERVER_ERROR) + ); + $this->assertSame('501 (Not Implemented)', $response->getHttpCodeDescription($response::NOT_IMPLEMENTED)); + $this->assertSame('502 (Bad Gateway)', $response->getHttpCodeDescription($response::BAD_GATEWAY)); + $this->assertSame('999 (Undefined code)', $response->getHttpCodeDescription(999)); + } + + public function testResponseWithArrayPayload(): void + { + $response = new Response(); + + $response + ->setPayloadSuccess(['a' => 'b']) + ; + + $payload = $this->checkPayload($response); + + $this->assertFalse(isset($payload['errors'])); + $this->assertSame(['a' => 'b'], $payload['data']); + } + + public function testResponseWithErrorCode(): void + { + $response = new Response(); + + $response + ->setPayloadError('error content') + ; + + $payload = $this->checkPayload($response, true); + + $this->assertFalse(isset($payload['data'])); + $this->assertSame('error content', $payload['errors'][0]); + } + + public function testResponseWithModelErrors(): void + { + $messages = [ + new Message('hello'), + new Message('goodbye'), + ]; + $response = new Response(); + $response + ->setPayloadErrors($messages) + ; + + $payload = $this->checkPayload($response, true); + + $this->assertFalse(isset($payload['data'])); + $this->assertSame(2, count($payload['errors'])); + $this->assertSame('hello', $payload['errors'][0]); + $this->assertSame('goodbye', $payload['errors'][1]); + } + + public function testResponseWithStringPayload(): void + { + $response = new Response(); + + $response + ->setPayloadSuccess('test') + ; + + $contents = $response->getContent(); + $this->assertTrue(is_string($contents)); + + $payload = $this->checkPayload($response); + + $this->assertFalse(isset($payload['errors'])); + $this->assertSame('test', $payload['data']); + } + + public function testResponseWithValidationErrors(): void + { + $group = new Messages(); + $message = new Message('hello'); + $group->appendMessage($message); + $message = new Message('goodbye'); + $group->appendMessage($message); + + $response = new Response(); + $response + ->setPayloadErrors($group) + ; + + $payload = $this->checkPayload($response, true); + + $this->assertFalse(isset($payload['data'])); + $this->assertSame(2, count($payload['errors'])); + $this->assertSame('hello', $payload['errors'][0]); + $this->assertSame('goodbye', $payload['errors'][1]); + } + + private function checkPayload(Response $response, bool $error = false): array + { + $contents = $response->getContent(); + $this->assertTrue(is_string($contents)); + + $payload = json_decode($contents, true); + if (true === $error) { + $this->assertTrue(isset($payload['errors'])); + } else { + $this->assertTrue(isset($payload['data'])); + } + + return $payload; + } +} diff --git a/tests/Unit/Library/Providers/CacheTest.php b/tests/Unit/Library/Providers/CacheTest.php new file mode 100644 index 00000000..97665c5d --- /dev/null +++ b/tests/Unit/Library/Providers/CacheTest.php @@ -0,0 +1,37 @@ + + * + * 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\Library\Providers; + +use Phalcon\Api\Providers\CacheDataProvider; +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Cache\Cache; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class CacheTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $config = new ConfigProvider(); + $config->register($diContainer); + $provider = new CacheDataProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('cache')); + /** @var Cache $cache */ + $cache = $diContainer->getShared('cache'); + $this->assertTrue($cache instanceof Cache); + } +} diff --git a/tests/Unit/Library/Providers/ConfigTest.php b/tests/Unit/Library/Providers/ConfigTest.php new file mode 100644 index 00000000..0f7e0044 --- /dev/null +++ b/tests/Unit/Library/Providers/ConfigTest.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\Tests\Unit\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Config\Config; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class ConfigTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('config')); + $config = $diContainer->getShared('config'); + $this->assertTrue($config instanceof Config); + + $configArray = $config->toArray(); + $this->assertTrue(isset($configArray['app']['version'])); + $this->assertTrue(isset($configArray['app']['timezone'])); + $this->assertTrue(isset($configArray['app']['debug'])); + $this->assertTrue(isset($configArray['app']['env'])); + $this->assertTrue(isset($configArray['app']['devMode'])); + $this->assertTrue(isset($configArray['app']['baseUri'])); + $this->assertTrue(isset($configArray['app']['supportEmail'])); + $this->assertTrue(isset($configArray['app']['time'])); + $this->assertTrue(isset($configArray['cache'])); + } +} diff --git a/tests/Unit/Library/Providers/DatabaseTest.php b/tests/Unit/Library/Providers/DatabaseTest.php new file mode 100644 index 00000000..fa9e4dfe --- /dev/null +++ b/tests/Unit/Library/Providers/DatabaseTest.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\Tests\Unit\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\DatabaseProvider; +use Phalcon\Db\Adapter\Pdo\Mysql; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class DatabaseTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new DatabaseProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('db')); + /** @var Mysql $db */ + $db = $diContainer->getShared('db'); + $this->assertTrue($db instanceof Mysql); + $this->assertSame('mysql', $db->getType()); + } +} diff --git a/tests/Unit/Library/Providers/ErrorHandlerTest.php b/tests/Unit/Library/Providers/ErrorHandlerTest.php new file mode 100644 index 00000000..5c613707 --- /dev/null +++ b/tests/Unit/Library/Providers/ErrorHandlerTest.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\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\ErrorHandlerProvider; +use Phalcon\Api\Providers\LoggerProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function date_default_timezone_get; + +final class ErrorHandlerTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new LoggerProvider(); + $provider->register($diContainer); + $provider = new ErrorHandlerProvider(); + $provider->register($diContainer); + + $config = $diContainer->getShared('config'); + + $this->assertSame(date_default_timezone_get(), $config->path('app.timezone')); + $this->assertSame(ini_get('display_errors'), 'Off'); + $this->assertSame(E_ALL, (int) ini_get('error_reporting')); + } +} diff --git a/tests/Unit/Library/Providers/LoggerTest.php b/tests/Unit/Library/Providers/LoggerTest.php new file mode 100644 index 00000000..0400a819 --- /dev/null +++ b/tests/Unit/Library/Providers/LoggerTest.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\Tests\Unit\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\LoggerProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Logger\Logger; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class LoggerTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new LoggerProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('logger')); + /** @var Logger $logger */ + $logger = $diContainer->getShared('logger'); + $this->assertTrue($logger instanceof Logger); + $this->assertSame('api', $logger->getName()); + } +} diff --git a/tests/Unit/Library/Providers/ModelsMetadataTest.php b/tests/Unit/Library/Providers/ModelsMetadataTest.php new file mode 100644 index 00000000..585b1427 --- /dev/null +++ b/tests/Unit/Library/Providers/ModelsMetadataTest.php @@ -0,0 +1,37 @@ + + * + * 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\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\ModelsMetadataProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Mvc\Model\MetaData\Memory; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class ModelsMetadataTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $config = new ConfigProvider(); + $config->register($diContainer); + $provider = new ModelsMetadataProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('modelsMetadata')); + /** @var Memory $metadata */ + $metadata = $diContainer->getShared('modelsMetadata'); + $this->assertTrue($metadata instanceof Memory); + } +} diff --git a/tests/Unit/Library/Providers/ResponseTest.php b/tests/Unit/Library/Providers/ResponseTest.php new file mode 100644 index 00000000..4c04c17c --- /dev/null +++ b/tests/Unit/Library/Providers/ResponseTest.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\Tests\Unit\Library\Providers; + +use Phalcon\Api\Http\Response; +use Phalcon\Api\Providers\ResponseProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class ResponseTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $provider = new ResponseProvider(); + $provider->register($diContainer); + + $this->assertTrue($diContainer->has('response')); + /** @var Response $response */ + $response = $diContainer->getShared('response'); + $this->assertTrue($response instanceof Response); + } +} diff --git a/tests/Unit/Library/Providers/RouterTest.php b/tests/Unit/Library/Providers/RouterTest.php new file mode 100644 index 00000000..b0961b0e --- /dev/null +++ b/tests/Unit/Library/Providers/RouterTest.php @@ -0,0 +1,71 @@ + + * + * 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\Library\Providers; + +use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Api\Providers\RouterProvider; +use Phalcon\Di\FactoryDefault; +use Phalcon\Mvc\Micro; +use Phalcon\Mvc\RouterInterface; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +final class RouterTest extends AbstractUnitTestCase +{ + public function testRegistration(): void + { + $diContainer = new FactoryDefault(); + $application = new Micro($diContainer); + $diContainer->setShared('application', $application); + $provider = new ConfigProvider(); + $provider->register($diContainer); + $provider = new RouterProvider(); + $provider->register($diContainer); + + /** @var RouterInterface $router */ + $router = $application->getRouter(); + $routes = $router->getRoutes(); + $expected = [ + ['POST', '/login'], + ['POST', '/companies'], + ['GET', '/users'], + ['GET', '/users/{recordId:[0-9]+}'], + ['GET', '/companies'], + ['GET', '/companies/{recordId:[0-9]+}'], + ['GET', '/companies/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/companies/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individuals'], + ['GET', '/individuals/{recordId:[0-9]+}'], + ['GET', '/individuals/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individuals/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individual-types'], + ['GET', '/individual-types/{recordId:[0-9]+}'], + ['GET', '/individual-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individual-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/products'], + ['GET', '/products/{recordId:[0-9]+}'], + ['GET', '/products/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/products/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/product-types'], + ['GET', '/product-types/{recordId:[0-9]+}'], + ['GET', '/product-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/product-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ]; + + $this->assertSame(24, count($routes)); + foreach ($routes as $index => $route) { + $this->assertSame($expected[$index][0], $route->getHttpMethods()); + $this->assertSame($expected[$index][1], $route->getPattern()); + } + } +} From d77f54f65c89cd113faa9893ed0e9146b24cf132 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 15:06:30 -0500 Subject: [PATCH 14/47] [#42] - upgrade talon to v0.8.0 for rest test support --- composer.json | 2 +- composer.lock | 455 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 447 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index b78387d6..9a9243ab 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", "phalcon/phalcon": "^6.0@alpha", - "phalcon/talon": "^0.7.0", + "phalcon/talon": "^0.8.0", "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^10.5", "squizlabs/php_codesniffer": "^4.0" diff --git a/composer.lock b/composer.lock index 1f661c10..c356695c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a303fd850830e8d6dca752d6e5dcf143", + "content-hash": "5a8df31cadf46a83c1c9c036bb54dfb8", "packages": [ { "name": "cakephp/chronos", @@ -2510,29 +2510,30 @@ }, { "name": "phalcon/talon", - "version": "v0.7.0", + "version": "v0.8.0", "source": { "type": "git", "url": "https://github.com/phalcon/talon.git", - "reference": "7e5da75bf6fe1e2ff4d861282f562a8f49c6525d" + "reference": "b8557e7056395df23ed2634d284b6b54362c4c25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/talon/zipball/7e5da75bf6fe1e2ff4d861282f562a8f49c6525d", - "reference": "7e5da75bf6fe1e2ff4d861282f562a8f49c6525d", + "url": "https://api.github.com/repos/phalcon/talon/zipball/b8557e7056395df23ed2634d284b6b54362c4c25", + "reference": "b8557e7056395df23ed2634d284b6b54362c4c25", "shasum": "" }, "require": { "phalcon/cli-options-parser": "^2.0", "php": "^8.1", "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0" + "symfony/dom-crawler": "^6.4 || ^7.0", + "symfony/http-client": "^6.4 || ^7.0", + "symfony/mime": "^6.4 || ^7.0" }, "require-dev": { "ext-pdo": "*", "ext-sqlite3": "*", "friendsofphp/php-cs-fixer": "^3", - "infection/infection": "^0.29.9", "pds/composer-script-names": "^1", "pds/skeleton": "^1", "phalcon/phalcon": "^6.0@alpha", @@ -2570,7 +2571,7 @@ ], "support": { "issues": "https://github.com/phalcon/talon/issues", - "source": "https://github.com/phalcon/talon/tree/v0.7.0" + "source": "https://github.com/phalcon/talon/tree/v0.8.0" }, "funding": [ { @@ -2582,7 +2583,7 @@ "type": "open_collective" } ], - "time": "2026-07-10T12:07:59+00:00" + "time": "2026-07-15T19:50:31+00:00" }, { "name": "phalcon/traits", @@ -4487,6 +4488,442 @@ ], "time": "2026-05-19T20:33:22+00:00" }, + { + "name": "symfony/http-client", + "version": "v6.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/b71b312ca0f211fbb19a20a51bde50fbefb66a99", + "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.4.42" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-12T09:42:32+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.4.41", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.4.41" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-23T14:40:34+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "dc21118016c039a66235cf93d96b435ffb282412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-25T15:22:23+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:51:48+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", From 8a002e1a41a20a6b57e255a0c5f2364068e02695 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 16:13:43 -0500 Subject: [PATCH 15/47] [#42] - convert api suite to talon format --- tests/.env.test | 6 + tests/Api/AbstractApiTestCase.php | 157 +++++++++++++ tests/Api/Companies/AbstractGetTestCase.php | 43 ++++ tests/Api/Companies/AddTest.php | 108 +++++++++ tests/Api/Companies/GetFieldsTest.php | 111 +++++++++ tests/Api/Companies/GetIncludesTest.php | 177 ++++++++++++++ tests/Api/Companies/GetSortTest.php | 101 ++++++++ tests/Api/Companies/GetTest.php | 84 +++++++ tests/Api/IndividualTypes/GetTest.php | 149 ++++++++++++ tests/Api/Individuals/GetTest.php | 229 ++++++++++++++++++ tests/Api/LoginTest.php | 60 +++++ tests/Api/NotFoundTest.php | 25 ++ tests/Api/ProductTypes/GetTest.php | 148 ++++++++++++ tests/Api/Products/GetTest.php | 236 +++++++++++++++++++ tests/Api/Users/GetTest.php | 248 ++++++++++++++++++++ 15 files changed, 1882 insertions(+) create mode 100644 tests/Api/AbstractApiTestCase.php create mode 100644 tests/Api/Companies/AbstractGetTestCase.php create mode 100644 tests/Api/Companies/AddTest.php create mode 100644 tests/Api/Companies/GetFieldsTest.php create mode 100644 tests/Api/Companies/GetIncludesTest.php create mode 100644 tests/Api/Companies/GetSortTest.php create mode 100644 tests/Api/Companies/GetTest.php create mode 100644 tests/Api/IndividualTypes/GetTest.php create mode 100644 tests/Api/Individuals/GetTest.php create mode 100644 tests/Api/LoginTest.php create mode 100644 tests/Api/NotFoundTest.php create mode 100644 tests/Api/ProductTypes/GetTest.php create mode 100644 tests/Api/Products/GetTest.php create mode 100644 tests/Api/Users/GetTest.php diff --git a/tests/.env.test b/tests/.env.test index 7f364909..c80a0430 100644 --- a/tests/.env.test +++ b/tests/.env.test @@ -12,3 +12,9 @@ DATA_MYSQL_CHARSET=utf8mb4 DATA_REDIS_HOST=redis DATA_REDIS_PORT=6379 + +# The api suite drives the running application over real HTTP. nginx listens on +# 80 inside the compose network; the 8080 in APP_URL is the host-side mapping +# only and does not resolve from here. APP_URL stays as it is - it is what the +# application echoes back in its `links`, and both sides must agree on it. +TALON_REST_URL=http://nginx diff --git a/tests/Api/AbstractApiTestCase.php b/tests/Api/AbstractApiTestCase.php new file mode 100644 index 00000000..d9363840 --- /dev/null +++ b/tests/Api/AbstractApiTestCase.php @@ -0,0 +1,157 @@ + + * + * 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\Api; + +use Phalcon\Api\Constants\Flags; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Talon\Http\HttpCode; +use Phalcon\Talon\Traits\RestAssertionsTrait; +use Phalcon\Talon\Traits\RestTrait; + +use function json_decode; +use function json_encode; +use function sha1; + +/** + * Ported from the Codeception `ApiTester` actor. + * + * The suite pairs the application container with real HTTP: records are created + * in-process through the models, while requests cross the network to the running + * server. That is what `api.suite.yml` described as `Helper\Integration` plus + * `REST: depends: PhpBrowser`, and it is why this extends the integration case + * rather than Talon's AbstractRestTestCase - the record helpers and the REST + * surface are both needed, and PHP allows only one parent. + */ +abstract class AbstractApiTestCase extends AbstractIntegrationTestCase +{ + use RestAssertionsTrait; + use RestTrait; + + protected function addApiUserRecord(): Users + { + return $this->haveRecordWithFields( + Users::class, + [ + 'status' => Flags::ACTIVE, + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'issuer' => Data::$testIssuer, + 'tokenPassword' => Data::$testTokenPassword, + 'tokenId' => Data::$testTokenId, + ] + ); + } + + protected function apiLogin(): string + { + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $response = json_decode($this->grabResponse(), true); + $data = $response['data'] ?? []; + + return $data['token'] ?? ''; + } + + protected function assertErrorJsonResponse(string $message): void + { + $this->assertResponseContainsJson( + [ + 'jsonapi' => [ + 'version' => '1.0', + ], + 'errors' => [ + $message, + ], + ] + ); + } + + protected function assertResponseIs400(): void + { + $this->assertErrorResponse(HttpCode::BAD_REQUEST); + } + + protected function assertResponseIs404(): void + { + $this->assertErrorResponse(HttpCode::NOT_FOUND); + } + + protected function assertResponseIsSuccessful(int $code = HttpCode::OK): void + { + $this->assertResponseIsJson(); + $this->assertResponseCodeIs($code); + $this->assertResponseMatchesJsonType( + [ + 'jsonapi' => [ + 'version' => 'string', + ], + 'meta' => [ + 'timestamp' => 'string:date', + 'hash' => 'string', + ], + ] + ); + + $this->assertHash(); + } + + /** + * @param array $data + */ + protected function assertSuccessJsonResponse(string $key = 'data', array $data = []): void + { + $this->assertResponseContainsJson([$key => $data]); + } + + private function assertErrorResponse(int $code): void + { + $this->assertResponseIsJson(); + $this->assertResponseCodeIs($code); + $this->assertResponseMatchesJsonType( + [ + 'jsonapi' => [ + 'version' => 'string', + ], + 'meta' => [ + 'timestamp' => 'string:date', + 'hash' => 'string', + ], + ] + ); + + $response = json_decode($this->grabResponse(), true); + $this->assertSame(HttpCode::getDescription($code), $response['errors'][0]); + + $this->assertHash(); + } + + /** + * The application signs each payload: sha1 over the timestamp followed by + * the body with the `meta` and `jsonapi` envelopes removed. + */ + private function assertHash(): void + { + $response = json_decode($this->grabResponse(), true); + $timestamp = $response['meta']['timestamp']; + $hash = $response['meta']['hash']; + + unset($response['meta'], $response['jsonapi']); + + $this->assertSame($hash, sha1($timestamp . json_encode($response))); + } +} diff --git a/tests/Api/Companies/AbstractGetTestCase.php b/tests/Api/Companies/AbstractGetTestCase.php new file mode 100644 index 00000000..26561d6a --- /dev/null +++ b/tests/Api/Companies/AbstractGetTestCase.php @@ -0,0 +1,43 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Tests\Api\AbstractApiTestCase; + +/** + * Ported from the Codeception `GetBase` helper. + */ +abstract class AbstractGetTestCase extends AbstractApiTestCase +{ + /** + * Returns [$company, $prdOne, $prdTwo, $indOne, $indTwo]. + * + * @return array + */ + protected function addRecords(): array + { + $company = $this->addCompanyRecord('com-a'); + $indType = $this->addIndividualTypeRecord('type-a-'); + $indOne = $this->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + $indTwo = $this->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + $prdType = $this->addProductTypeRecord('type-a-'); + $prdOne = $this->addProductRecord('prd-a-', $prdType->get('id')); + $prdTwo = $this->addProductRecord('prd-b-', $prdType->get('id')); + + $this->addCompanyXProduct($company->get('id'), $prdOne->get('id')); + $this->addCompanyXProduct($company->get('id'), $prdTwo->get('id')); + + return [$company, $prdOne, $prdTwo, $indOne, $indTwo]; + } +} diff --git a/tests/Api/Companies/AddTest.php b/tests/Api/Companies/AddTest.php new file mode 100644 index 00000000..022ceb28 --- /dev/null +++ b/tests/Api/Companies/AddTest.php @@ -0,0 +1,108 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Http\Response; +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; + +use function Phalcon\Api\Core\appUrl; +use function uniqid; + +final class AddTest extends AbstractApiTestCase +{ + public function testAddNewCompany(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + $name = uniqid('com'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendPost( + Data::$companiesUrl, + Data::companyAddJson( + $name, + '123 Phalcon way', + 'World', + '555-444-7777' + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(Response::CREATED); + + $company = $this->getRecordWithFields( + Companies::class, + [ + 'name' => $name, + ] + ); + $this->assertNotEquals(false, $company); + + $this->assertHttpHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))); + $this->assertSuccessJsonResponse( + 'data', + Data::companiesAddResponse($company) + ); + + $this->assertNotEquals(false, $company->delete()); + } + + public function testAddNewCompanyWithExistingName(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + $name = uniqid('com'); + + $this->haveRecordWithFields( + Companies::class, + [ + 'name' => $name, + ] + ); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendPost( + Data::$companiesUrl, + Data::companyAddJson( + $name, + '123 Phalcon way', + 'World', + '555-444-7777' + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertErrorJsonResponse('The company name already exists in the database'); + } + + public function testAddNewCompanyWithoutPostingName(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendPost( + Data::$companiesUrl, + Data::companyAddJson( + '', + '123 Phalcon way', + 'World', + '555-444-7777' + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertErrorJsonResponse('The company name is required'); + } +} diff --git a/tests/Api/Companies/GetFieldsTest.php b/tests/Api/Companies/GetFieldsTest.php new file mode 100644 index 00000000..c88edf51 --- /dev/null +++ b/tests/Api/Companies/GetFieldsTest.php @@ -0,0 +1,111 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Support\Data; + +use function count; +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetFieldsTest extends AbstractGetTestCase +{ + public function testGetCompaniesWithIncludesAndFields(): void + { + $this->runCompaniesWithIncludesAndFields(); + } + + public function testGetCompaniesWithIncludesAndUnknownFields(): void + { + $this->runCompaniesWithIncludesAndFields(',unknown-product-field'); + } + + private function runCompaniesWithIncludesAndFields(string $fields = ''): void + { + [$com, $prdOne, $prdTwo] = $this->addRecords(); + + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$companiesRecordIncludesUrl, + $com->get('id'), + Relationships::PRODUCTS + ) . + '&fields[' . Relationships::COMPANIES . ']=id,name,city' . + '&fields[' . Relationships::PRODUCTS . ']=id,name,price' . $fields + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + + $element = [ + 'type' => Relationships::COMPANIES, + 'id' => $com->get('id'), + 'attributes' => [ + 'name' => $com->get('name'), + 'city' => $com->get('city'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id') + ), + ], + ]; + + $element['relationships'][Relationships::PRODUCTS] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdTwo->get('id'), + ], + ], + ]; + + $included = []; + $included[] = Data::productFieldsResponse($prdOne); + $included[] = Data::productFieldsResponse($prdTwo); + + $this->assertSuccessJsonResponse('data', [$element]); + + if (count($included) > 0) { + $this->assertSuccessJsonResponse('included', $included); + } + } +} diff --git a/tests/Api/Companies/GetIncludesTest.php b/tests/Api/Companies/GetIncludesTest.php new file mode 100644 index 00000000..a608907c --- /dev/null +++ b/tests/Api/Companies/GetIncludesTest.php @@ -0,0 +1,177 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Support\Data; + +use function count; +use function implode; +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetIncludesTest extends AbstractGetTestCase +{ + public function testGetCompaniesWithIncludesAllIncludes(): void + { + $this->checkIncludes([Relationships::INDIVIDUALS, Relationships::PRODUCTS]); + } + + public function testGetCompaniesWithIncludesIndividuals(): void + { + $this->checkIncludes([Relationships::INDIVIDUALS]); + } + + public function testGetCompaniesWithIncludesProducts(): void + { + $this->checkIncludes([Relationships::PRODUCTS]); + } + + public function testGetCompanyUnknownInclude(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $company = $this->addCompanyRecord('com-a-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesRecordIncludesUrl, $company->get('id'), 'unknown')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($company), + ] + ); + } + + /** + * @param array $includes + */ + private function checkIncludes(array $includes = []): void + { + [$com, $prdOne, $prdTwo, $indOne, $indTwo] = $this->addRecords(); + + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$companiesRecordIncludesUrl, + $com->get('id'), + implode(',', $includes) + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + + $element = [ + 'type' => Relationships::COMPANIES, + 'id' => $com->get('id'), + 'attributes' => [ + 'name' => $com->get('name'), + 'address' => $com->get('address'), + 'city' => $com->get('city'), + 'phone' => $com->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id') + ), + ], + ]; + + $included = []; + foreach ($includes as $include) { + if (Relationships::INDIVIDUALS === $include) { + $element['relationships'][Relationships::INDIVIDUALS] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + ], + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indOne->get('id'), + ], + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indTwo->get('id'), + ], + ], + ]; + + $included[] = Data::individualResponse($indOne); + $included[] = Data::individualResponse($indTwo); + } + + if (Relationships::PRODUCTS === $include) { + $element['relationships'][Relationships::PRODUCTS] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdTwo->get('id'), + ], + ], + ]; + + $included[] = Data::productResponse($prdOne); + $included[] = Data::productResponse($prdTwo); + } + } + + $this->assertSuccessJsonResponse('data', [$element]); + + if (count($included) > 0) { + $this->assertSuccessJsonResponse('included', $included); + } + } +} diff --git a/tests/Api/Companies/GetSortTest.php b/tests/Api/Companies/GetSortTest.php new file mode 100644 index 00000000..bb86aecc --- /dev/null +++ b/tests/Api/Companies/GetSortTest.php @@ -0,0 +1,101 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Tests\Support\Data; + +use function sprintf; + +final class GetSortTest extends AbstractGetTestCase +{ + public function testGetCompaniesInvalidSort(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->addCompanyRecord('com-a-', '', 'city-b'); + $this->addCompanyRecord('com-b-', '', 'city-b'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesSortUrl, 'unknown')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs400(); + } + + public function testGetCompaniesMultipleSort(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $comOne = $this->addCompanyRecord('com-a-', '', 'city-b'); + $comTwo = $this->addCompanyRecord('com-b-', '', 'city-b'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesSortUrl, 'city,name')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($comOne), + Data::companiesResponse($comTwo), + ] + ); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesSortUrl, 'city,-name')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($comTwo), + Data::companiesResponse($comOne), + ] + ); + } + + public function testGetCompaniesSingleSort(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $comOne = $this->addCompanyRecord('com-a-'); + $comTwo = $this->addCompanyRecord('com-b-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesSortUrl, 'name')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($comOne), + Data::companiesResponse($comTwo), + ] + ); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesSortUrl, '-name')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($comTwo), + Data::companiesResponse($comOne), + ] + ); + } +} diff --git a/tests/Api/Companies/GetTest.php b/tests/Api/Companies/GetTest.php new file mode 100644 index 00000000..612ccf45 --- /dev/null +++ b/tests/Api/Companies/GetTest.php @@ -0,0 +1,84 @@ + + * + * 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\Api\Companies; + +use Phalcon\Api\Tests\Support\Data; + +use function sprintf; + +final class GetTest extends AbstractGetTestCase +{ + public function testGetCompanies(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $comOne = $this->addCompanyRecord('com-a-'); + $comTwo = $this->addCompanyRecord('com-b-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$companiesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($comOne), + Data::companiesResponse($comTwo), + ] + ); + } + + public function testGetCompaniesNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$companiesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse(); + } + + public function testGetCompany(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $company = $this->addCompanyRecord('com-a-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesRecordUrl, $company->get('id'))); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::companiesResponse($company), + ] + ); + } + + public function testGetUnknownCompany(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$companiesRecordUrl, 1)); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs404(); + } +} diff --git a/tests/Api/IndividualTypes/GetTest.php b/tests/Api/IndividualTypes/GetTest.php new file mode 100644 index 00000000..35544162 --- /dev/null +++ b/tests/Api/IndividualTypes/GetTest.php @@ -0,0 +1,149 @@ + + * + * 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\Api\IndividualTypes; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; + +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetTest extends AbstractApiTestCase +{ + public function testGetIndividualTypes(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $typeOne = $this->addIndividualTypeRecord('type-a-'); + $typeTwo = $this->addIndividualTypeRecord('type-b-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$individualTypesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::individualTypeResponse($typeOne), + Data::individualTypeResponse($typeTwo), + ] + ); + } + + public function testGetIndividualTypesNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$individualTypesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse(); + } + + public function testGetIndividualTypesWithIncludesIndividuals(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $company = $this->addCompanyRecord('com-a'); + $individualType = $this->addIndividualTypeRecord('type-a-'); + $individualOne = $this->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); + $individualTwo = $this->addIndividualRecord('prd-b-', $company->get('id'), $individualType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$individualTypesRecordIncludesUrl, + $individualType->get('id'), + Relationships::INDIVIDUALS + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + 'attributes' => [ + 'name' => $individualType->get('name'), + 'description' => $individualType->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id') + ), + ], + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id'), + Relationships::INDIVIDUALS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id'), + Relationships::INDIVIDUALS + ), + ], + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individualOne->get('id'), + ], + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individualTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $this->assertSuccessJsonResponse( + 'included', + [ + Data::individualResponse($individualOne), + Data::individualResponse($individualTwo), + ] + ); + } + + public function testGetUnknownIndividualTypes(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$individualTypesRecordUrl, 1)); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs404(); + } +} diff --git a/tests/Api/Individuals/GetTest.php b/tests/Api/Individuals/GetTest.php new file mode 100644 index 00000000..83eed330 --- /dev/null +++ b/tests/Api/Individuals/GetTest.php @@ -0,0 +1,229 @@ + + * + * 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\Api\Individuals; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; + +use function count; +use function implode; +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetTest extends AbstractApiTestCase +{ + public function testGetIndividual(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $company = $this->addCompanyRecord('com-a-'); + $individualType = $this->addIndividualTypeRecord('prt-a-'); + $individual = $this->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$individualsRecordUrl, $individual->get('id'))); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::individualResponse($individual), + ] + ); + } + + public function testGetIndividuals(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $company = $this->addCompanyRecord('com-a-'); + $individualType = $this->addIndividualTypeRecord('prt-a-'); + $individualOne = $this->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); + $individualTwo = $this->addIndividualRecord('ind-b-', $company->get('id'), $individualType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$individualsUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::individualResponse($individualOne), + Data::individualResponse($individualTwo), + ] + ); + } + + public function testGetIndividualsNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$individualsUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse(); + } + + public function testGetIndividualsWithIncludesAllIncludes(): void + { + $this->checkIncludes([Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES]); + } + + public function testGetIndividualsWithIncludesCompanies(): void + { + $this->checkIncludes([Relationships::COMPANIES]); + } + + public function testGetIndividualsWithRelationshipIndividualTypes(): void + { + $this->checkIncludes([Relationships::INDIVIDUAL_TYPES]); + } + + public function testGetUnknownIndividual(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$individualsRecordUrl, 1)); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs404(); + } + + /** + * Returns [$individual, $individualType, $company]. + * + * @return array + */ + private function addRecords(): array + { + $company = $this->addCompanyRecord('com-a'); + $individualType = $this->addIndividualTypeRecord('prt-a-'); + $individual = $this->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); + + return [$individual, $individualType, $company]; + } + + /** + * @param array $includes + */ + private function checkIncludes(array $includes = []): void + { + [$individual, $individualType, $company] = $this->addRecords(); + + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$individualsRecordIncludesUrl, + $individual->get('id'), + implode(',', $includes) + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + + $element = [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + ]; + + $included = []; + foreach ($includes as $include) { + if (Relationships::COMPANIES === $include) { + $element['relationships'][Relationships::COMPANIES] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + 'type' => Relationships::COMPANIES, + 'id' => $company->get('id'), + ], + ]; + + $included[] = Data::companiesResponse($company); + } + + if (Relationships::INDIVIDUAL_TYPES === $include) { + $element['relationships'][Relationships::INDIVIDUAL_TYPES] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + ], + ]; + + $included[] = Data::individualTypeResponse($individualType); + } + } + + $this->assertSuccessJsonResponse('data', [$element]); + + if (count($included) > 0) { + $this->assertSuccessJsonResponse('included', $included); + } + } +} diff --git a/tests/Api/LoginTest.php b/tests/Api/LoginTest.php new file mode 100644 index 00000000..0d2459fc --- /dev/null +++ b/tests/Api/LoginTest.php @@ -0,0 +1,60 @@ + + * + * 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\Api; + +use Phalcon\Api\Constants\Flags; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Support\Data; + +use function json_decode; + +final class LoginTest extends AbstractApiTestCase +{ + public function testLoginKnownUser(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'status' => Flags::ACTIVE, + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'issuer' => Data::$testIssuer, + 'tokenPassword' => Data::$strongPassphrase, + 'tokenId' => Data::$testTokenId, + ] + ); + + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $response = json_decode($this->grabResponse(), true); + + $this->assertTrue(isset($response['data'])); + $this->assertTrue(isset($response['data']['token'])); + $this->assertTrue(isset($response['meta'])); + } + + public function testLoginUnknownUser(): void + { + $this->sendPost( + Data::$loginUrl, + [ + 'username' => 'user', + 'password' => 'pass', + ] + ); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Incorrect credentials'); + } +} diff --git a/tests/Api/NotFoundTest.php b/tests/Api/NotFoundTest.php new file mode 100644 index 00000000..bfa9e9b5 --- /dev/null +++ b/tests/Api/NotFoundTest.php @@ -0,0 +1,25 @@ + + * + * 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\Api; + +use Phalcon\Api\Tests\Support\Data; + +final class NotFoundTest extends AbstractApiTestCase +{ + public function testCheckNotFoundRoute(): void + { + $this->sendGet(Data::$wrongUrl); + $this->assertResponseIs404(); + } +} diff --git a/tests/Api/ProductTypes/GetTest.php b/tests/Api/ProductTypes/GetTest.php new file mode 100644 index 00000000..06ff8332 --- /dev/null +++ b/tests/Api/ProductTypes/GetTest.php @@ -0,0 +1,148 @@ + + * + * 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\Api\ProductTypes; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; + +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetTest extends AbstractApiTestCase +{ + public function testGetProductTypes(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $typeOne = $this->addProductTypeRecord('type-a-'); + $typeTwo = $this->addProductTypeRecord('type-b-'); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$productTypesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::productTypeResponse($typeOne), + Data::productTypeResponse($typeTwo), + ] + ); + } + + public function testGetProductTypesNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$productTypesUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse(); + } + + public function testGetProductTypesWithIncludesProducts(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $productType = $this->addProductTypeRecord('type-a-'); + $productOne = $this->addProductRecord('prd-a-', $productType->get('id')); + $productTwo = $this->addProductRecord('prd-b-', $productType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$productTypesRecordIncludesUrl, + $productType->get('id'), + Relationships::PRODUCTS + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + 'attributes' => [ + 'name' => $productType->get('name'), + 'description' => $productType->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id') + ), + ], + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $productOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $productTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $this->assertSuccessJsonResponse( + 'included', + [ + Data::productResponse($productOne), + Data::productResponse($productTwo), + ] + ); + } + + public function testGetUnknownProductTypes(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$productTypesRecordUrl, 1)); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs404(); + } +} diff --git a/tests/Api/Products/GetTest.php b/tests/Api/Products/GetTest.php new file mode 100644 index 00000000..93c2f3d7 --- /dev/null +++ b/tests/Api/Products/GetTest.php @@ -0,0 +1,236 @@ + + * + * 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\Api\Products; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; + +use function count; +use function implode; +use function Phalcon\Api\Core\envValue; +use function sprintf; + +final class GetTest extends AbstractApiTestCase +{ + public function testGetProduct(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $productType = $this->addProductTypeRecord('prt-a-'); + $product = $this->addProductRecord('prd-a-', $productType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$productsRecordUrl, $product->get('id'))); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::productResponse($product), + ] + ); + } + + public function testGetProducts(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $productType = $this->addProductTypeRecord('prt-a-'); + $productOne = $this->addProductRecord('prd-a-', $productType->get('id')); + $productTwo = $this->addProductRecord('prd-b-', $productType->get('id')); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$productsUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::productResponse($productOne), + Data::productResponse($productTwo), + ] + ); + } + + public function testGetProductsNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$productsUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse(); + } + + public function testGetProductsWithIncludesAllIncludes(): void + { + $this->checkIncludes([Relationships::COMPANIES, Relationships::PRODUCT_TYPES]); + } + + public function testGetProductsWithIncludesCompanies(): void + { + $this->checkIncludes([Relationships::COMPANIES]); + } + + public function testGetProductsWithIncludesProductTypes(): void + { + $this->checkIncludes([Relationships::PRODUCT_TYPES]); + } + + public function testGetUnknownProduct(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(sprintf(Data::$productsRecordUrl, 1)); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIs404(); + } + + /** + * Returns [$product, $productType, $comOne, $comTwo]. + * + * @return array + */ + private function addRecords(): array + { + $comOne = $this->addCompanyRecord('com-a'); + $comTwo = $this->addCompanyRecord('com-b'); + $productType = $this->addProductTypeRecord('prt-a-'); + $product = $this->addProductRecord('prd-a-', $productType->get('id')); + + $this->addCompanyXProduct($comOne->get('id'), $product->get('id')); + $this->addCompanyXProduct($comTwo->get('id'), $product->get('id')); + + return [$product, $productType, $comOne, $comTwo]; + } + + /** + * @param array $includes + */ + private function checkIncludes(array $includes = []): void + { + [$product, $productType, $comOne, $comTwo] = $this->addRecords(); + + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet( + sprintf( + Data::$productsRecordIncludesUrl, + $product->get('id'), + implode(',', $includes) + ) + ); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + + $element = [ + 'type' => Relationships::PRODUCTS, + 'id' => $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id') + ), + ], + ]; + + $included = []; + foreach ($includes as $include) { + if (Relationships::COMPANIES === $include) { + $element['relationships'][Relationships::COMPANIES] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $comOne->get('id'), + ], + [ + 'type' => Relationships::COMPANIES, + 'id' => $comTwo->get('id'), + ], + ], + ]; + + $included[] = Data::companiesResponse($comOne); + $included[] = Data::companiesResponse($comTwo); + } + + if (Relationships::PRODUCT_TYPES === $include) { + $element['relationships'][Relationships::PRODUCT_TYPES] = [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + ], + ]; + + $included[] = Data::productTypeResponse($productType); + } + } + + $this->assertSuccessJsonResponse('data', [$element]); + + if (count($included) > 0) { + $this->assertSuccessJsonResponse('included', $included); + } + } +} diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php new file mode 100644 index 00000000..93f2e3af --- /dev/null +++ b/tests/Api/Users/GetTest.php @@ -0,0 +1,248 @@ + + * + * 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\Api\Users; + +use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Api\AbstractApiTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\TokenTrait; +use Phalcon\Encryption\Security\JWT\Builder; +use Phalcon\Encryption\Security\JWT\Signer\Hmac; + +use function explode; +use function json_decode; +use function time; +use function usleep; + +final class GetTest extends AbstractApiTestCase +{ + use TokenTrait; + + public function testGetManyUsers(): void + { + $userOne = $this->haveRecordWithFields( + Users::class, + [ + 'status' => 1, + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'issuer' => Data::$testIssuer, + 'tokenPassword' => Data::$strongPassphrase, + 'tokenId' => Data::$testTokenId, + ] + ); + + $userTwo = $this->haveRecordWithFields( + Users::class, + [ + 'status' => 1, + 'username' => Data::$testUsername . '1', + 'password' => Data::$testPassword . '1', + 'issuer' => Data::$testIssuer . '1', + 'tokenPassword' => Data::$strongPassphrase . '1', + 'tokenId' => Data::$testTokenId . '1', + ] + ); + + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::userResponse($userOne), + Data::userResponse($userTwo), + ] + ); + } + + public function testGetManyUsersWithNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + } + + public function testLoginKnownUserCorrectToken(): void + { + $this->addApiUserRecord(); + $this->apiLogin(); + } + + public function testLoginKnownUserExpiredToken(): void + { + $record = $this->addApiUserRecord(); + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $signer = new Hmac(); + $builder = new Builder($signer); + + $token = $builder + ->setIssuer(Data::$testIssuer) + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setIssuedAt(time() - 3600) + ->setNotBefore(time() - 3590) + ->setExpirationTime(time()) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + usleep(1000000); + $expiredToken = $token->getToken(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $expiredToken); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Invalid Token (verification)'); + } + + public function testLoginKnownUserGetUnknownUser(): void + { + $this->addApiUserRecord(); + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $response = json_decode($this->grabResponse(), true); + $token = $response['data']['token']; + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl . '/1'); + $this->assertResponseIs404(); + } + + public function testLoginKnownUserIncorrectSignature(): void + { + $record = $this->addApiUserRecord(); + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $signer = new Hmac(); + $builder = new Builder($signer); + + $token = $builder + ->setIssuer(Data::$testIssuer) + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setIssuedAt(time() - 3600) + ->setNotBefore(time() - 3590) + ->setExpirationTime(time() + 3000) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + $wrongToken = $token->getToken(); + $parts = explode('.', $wrongToken); + $wrongToken = $parts[0] . '.' . $parts[1] . '.'; + + $this->haveHttpHeader('Authorization', 'Bearer ' . $wrongToken); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Invalid Token (verification)'); + } + + public function testLoginKnownUserInvalidToken(): void + { + $record = $this->addApiUserRecord(); + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $signer = new Hmac(); + $builder = new Builder($signer); + + $token = $builder + ->setIssuer(Data::$testIssuer) + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setIssuedAt(time()) + ->setNotBefore(time()) + ->setExpirationTime(time() + 3000) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + $invalidToken = $token->getToken() . 'xx'; + + $this->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Invalid Token (verification)'); + } + + public function testLoginKnownUserInvalidUserInToken(): void + { + $record = $this->addApiUserRecord(); + $this->unsetHttpHeader('Authorization'); + $this->sendPost(Data::$loginUrl, Data::loginJson()); + $this->assertResponseIsSuccessful(); + + $signer = new Hmac(); + $builder = new Builder($signer); + + $token = $builder + ->setIssuer(Data::$testIssuer) + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setIssuedAt(time() - 3600) + ->setNotBefore(time() - 3590) + ->setExpirationTime(time() + 3000) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + $invalidToken = $token->getToken(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Invalid Token (verification)'); + } + + public function testLoginKnownUserNoToken(): void + { + $this->unsetHttpHeader('Authorization'); + $this->sendGet(Data::$usersUrl . '/1'); + $this->assertResponseIsSuccessful(); + $this->assertErrorJsonResponse('Invalid Token'); + } + + public function testLoginKnownUserValidToken(): void + { + $record = $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + $this->assertSuccessJsonResponse( + 'data', + [ + Data::userResponse($record), + ] + ); + } +} From 2a02e3f955e42c154a00a88129a2ade7b22b5dc4 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 16:30:41 -0500 Subject: [PATCH 16/47] [#42] - cast cache lifetime to int --- library/Core/config.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/Core/config.php b/library/Core/config.php index e81137fe..41d73835 100644 --- a/library/Core/config.php +++ b/library/Core/config.php @@ -33,7 +33,9 @@ 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), 'port' => envValue('DATA_API_REDIS_PORT', 6379), 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), - 'lifetime' => envValue('CACHE_LIFETIME', 86400), + // Cast: env values arrive as strings, and AbstractAdapter::getTtl() + // is typed to return int. + 'lifetime' => (int) envValue('CACHE_LIFETIME', 86400), 'prefix' => 'data-', ], ], @@ -48,7 +50,9 @@ 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), 'port' => envValue('DATA_API_REDIS_PORT', 6379), 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), - 'lifetime' => envValue('CACHE_LIFETIME', 86400), + // Cast: env values arrive as strings, and AbstractAdapter::getTtl() + // is typed to return int. + 'lifetime' => (int) envValue('CACHE_LIFETIME', 86400), 'prefix' => 'metadata-', ], ], From c7ff6580b274fd3f3a393b522f1227688ef71881 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 19:59:21 -0500 Subject: [PATCH 17/47] [#42] - validate the submitted token against the expected claims --- library/Middleware/TokenValidationMiddleware.php | 2 +- library/Models/Users.php | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/library/Middleware/TokenValidationMiddleware.php b/library/Middleware/TokenValidationMiddleware.php index d0d97c9a..362dcf55 100755 --- a/library/Middleware/TokenValidationMiddleware.php +++ b/library/Middleware/TokenValidationMiddleware.php @@ -58,7 +58,7 @@ public function call(Micro $api): bool /** @var Users $user */ $user = $this->getUserByToken($config, $cache, $token); - $errors = $token->validate($user->getValidationData()); + $errors = $token->validate($user->getValidationData($token)); if (true !== empty($errors)) { $this->halt( diff --git a/library/Models/Users.php b/library/Models/Users.php index b73817e2..3e0d3796 100644 --- a/library/Models/Users.php +++ b/library/Models/Users.php @@ -20,6 +20,7 @@ use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; use Phalcon\Encryption\Security\JWT\Signer\Hmac; +use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Encryption\Security\JWT\Validator; @@ -72,16 +73,23 @@ public function getToken(): string } /** - * Returns the Validator object for this record (JWT) + * Returns the Validator for the token that was sent to us, carrying the + * values this record and the environment expect that token to hold. + * + * @param Token $token The token from the request - it is the one validated * * @return Validator * @throws ModelException */ - public function getValidationData(): Validator + public function getValidationData(Token $token): Validator { - $token = $this->getBuilderToken(); + $validator = new Validator($token, 10); - return new Validator($token, 10); + return $validator + ->set(Enum::AUDIENCE, $this->getTokenAudience()) + ->set(Enum::ISSUER, $this->get('issuer')) + ->set(Enum::ID, $this->get('tokenId')) + ; } /** From d6100635d5ceff8db8fffb88ef60d75f569ee662 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 19:59:21 -0500 Subject: [PATCH 18/47] [#42] - pin phalcon to the v6.0.0 branch --- composer.json | 2 +- composer.lock | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 9a9243ab..227a9ca7 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "pds/composer-script-names": "^1.0", "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", - "phalcon/phalcon": "^6.0@alpha", + "phalcon/phalcon": "v6.0.x-dev", "phalcon/talon": "^0.8.0", "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^10.5", diff --git a/composer.lock b/composer.lock index c356695c..a21e4254 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5a8df31cadf46a83c1c9c036bb54dfb8", + "content-hash": "80a428428767cc31b3bf1868d8bbb597", "packages": [ { "name": "cakephp/chronos", @@ -2427,16 +2427,16 @@ }, { "name": "phalcon/phalcon", - "version": "v6.0.0alpha4", + "version": "v6.0.x-dev", "source": { "type": "git", "url": "https://github.com/phalcon/phalcon.git", - "reference": "4568057c441baf0a729c3eca7585dcb0c5c97d2f" + "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/phalcon/zipball/4568057c441baf0a729c3eca7585dcb0c5c97d2f", - "reference": "4568057c441baf0a729c3eca7585dcb0c5c97d2f", + "url": "https://api.github.com/repos/phalcon/phalcon/zipball/17f4299d3bdf46487638fb6869fedca65396f3b2", + "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2", "shasum": "" }, "require": { @@ -2477,6 +2477,7 @@ "ext-redis": "to use Cache\\Adapter\\Redis, Session\\Adapter\\Redis, Storage\\Adapter\\Redis", "ext-yaml": "to use Config\\Adapter\\Yaml" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -2494,7 +2495,7 @@ ], "support": { "issues": "https://github.com/phalcon/phalcon/issues", - "source": "https://github.com/phalcon/phalcon/tree/v6.0.0alpha4" + "source": "https://github.com/phalcon/phalcon/tree/v6.0.x" }, "funding": [ { @@ -2506,7 +2507,7 @@ "type": "open_collective" } ], - "time": "2026-07-13T18:05:01+00:00" + "time": "2026-07-15T23:27:22+00:00" }, { "name": "phalcon/talon", @@ -4978,7 +4979,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phalcon/phalcon": 15 + "phalcon/phalcon": 20 }, "prefer-stable": false, "prefer-lowest": false, From 6a2c9b0f1e1c08b64b1acb8130c2a8c8d587d883 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:02:12 -0500 Subject: [PATCH 19/47] [#42] - cast resource ids to string in api expectations --- tests/Api/Companies/GetFieldsTest.php | 6 +++--- tests/Api/Companies/GetIncludesTest.php | 10 +++++----- tests/Api/IndividualTypes/GetTest.php | 6 +++--- tests/Api/Individuals/GetTest.php | 6 +++--- tests/Api/ProductTypes/GetTest.php | 6 +++--- tests/Api/Products/GetTest.php | 8 ++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/Api/Companies/GetFieldsTest.php b/tests/Api/Companies/GetFieldsTest.php index c88edf51..12c900d6 100644 --- a/tests/Api/Companies/GetFieldsTest.php +++ b/tests/Api/Companies/GetFieldsTest.php @@ -54,7 +54,7 @@ private function runCompaniesWithIncludesAndFields(string $fields = ''): void $element = [ 'type' => Relationships::COMPANIES, - 'id' => $com->get('id'), + 'id' => (string) $com->get('id'), 'attributes' => [ 'name' => $com->get('name'), 'city' => $com->get('city'), @@ -89,11 +89,11 @@ private function runCompaniesWithIncludesAndFields(string $fields = ''): void 'data' => [ [ 'type' => Relationships::PRODUCTS, - 'id' => $prdOne->get('id'), + 'id' => (string) $prdOne->get('id'), ], [ 'type' => Relationships::PRODUCTS, - 'id' => $prdTwo->get('id'), + 'id' => (string) $prdTwo->get('id'), ], ], ]; diff --git a/tests/Api/Companies/GetIncludesTest.php b/tests/Api/Companies/GetIncludesTest.php index a608907c..c8b910a0 100644 --- a/tests/Api/Companies/GetIncludesTest.php +++ b/tests/Api/Companies/GetIncludesTest.php @@ -80,7 +80,7 @@ private function checkIncludes(array $includes = []): void $element = [ 'type' => Relationships::COMPANIES, - 'id' => $com->get('id'), + 'id' => (string) $com->get('id'), 'attributes' => [ 'name' => $com->get('name'), 'address' => $com->get('address'), @@ -120,11 +120,11 @@ private function checkIncludes(array $includes = []): void 'data' => [ [ 'type' => Relationships::INDIVIDUALS, - 'id' => $indOne->get('id'), + 'id' => (string) $indOne->get('id'), ], [ 'type' => Relationships::INDIVIDUALS, - 'id' => $indTwo->get('id'), + 'id' => (string) $indTwo->get('id'), ], ], ]; @@ -154,11 +154,11 @@ private function checkIncludes(array $includes = []): void 'data' => [ [ 'type' => Relationships::PRODUCTS, - 'id' => $prdOne->get('id'), + 'id' => (string) $prdOne->get('id'), ], [ 'type' => Relationships::PRODUCTS, - 'id' => $prdTwo->get('id'), + 'id' => (string) $prdTwo->get('id'), ], ], ]; diff --git a/tests/Api/IndividualTypes/GetTest.php b/tests/Api/IndividualTypes/GetTest.php index 35544162..876fbf23 100644 --- a/tests/Api/IndividualTypes/GetTest.php +++ b/tests/Api/IndividualTypes/GetTest.php @@ -80,7 +80,7 @@ public function testGetIndividualTypesWithIncludesIndividuals(): void [ [ 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => $individualType->get('id'), + 'id' => (string) $individualType->get('id'), 'attributes' => [ 'name' => $individualType->get('name'), 'description' => $individualType->get('description'), @@ -114,11 +114,11 @@ public function testGetIndividualTypesWithIncludesIndividuals(): void 'data' => [ [ 'type' => Relationships::INDIVIDUALS, - 'id' => $individualOne->get('id'), + 'id' => (string) $individualOne->get('id'), ], [ 'type' => Relationships::INDIVIDUALS, - 'id' => $individualTwo->get('id'), + 'id' => (string) $individualTwo->get('id'), ], ], ], diff --git a/tests/Api/Individuals/GetTest.php b/tests/Api/Individuals/GetTest.php index 83eed330..1009dccd 100644 --- a/tests/Api/Individuals/GetTest.php +++ b/tests/Api/Individuals/GetTest.php @@ -143,7 +143,7 @@ private function checkIncludes(array $includes = []): void $element = [ 'type' => Relationships::INDIVIDUALS, - 'id' => $individual->get('id'), + 'id' => (string) $individual->get('id'), 'attributes' => [ 'companyId' => $individual->get('companyId'), 'typeId' => $individual->get('typeId'), @@ -185,7 +185,7 @@ private function checkIncludes(array $includes = []): void ], 'data' => [ 'type' => Relationships::COMPANIES, - 'id' => $company->get('id'), + 'id' => (string) $company->get('id'), ], ]; @@ -212,7 +212,7 @@ private function checkIncludes(array $includes = []): void ], 'data' => [ 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => $individualType->get('id'), + 'id' => (string) $individualType->get('id'), ], ]; diff --git a/tests/Api/ProductTypes/GetTest.php b/tests/Api/ProductTypes/GetTest.php index 06ff8332..61b64786 100644 --- a/tests/Api/ProductTypes/GetTest.php +++ b/tests/Api/ProductTypes/GetTest.php @@ -79,7 +79,7 @@ public function testGetProductTypesWithIncludesProducts(): void [ [ 'type' => Relationships::PRODUCT_TYPES, - 'id' => $productType->get('id'), + 'id' => (string) $productType->get('id'), 'attributes' => [ 'name' => $productType->get('name'), 'description' => $productType->get('description'), @@ -113,11 +113,11 @@ public function testGetProductTypesWithIncludesProducts(): void 'data' => [ [ 'type' => Relationships::PRODUCTS, - 'id' => $productOne->get('id'), + 'id' => (string) $productOne->get('id'), ], [ 'type' => Relationships::PRODUCTS, - 'id' => $productTwo->get('id'), + 'id' => (string) $productTwo->get('id'), ], ], ], diff --git a/tests/Api/Products/GetTest.php b/tests/Api/Products/GetTest.php index 93c2f3d7..f150c8fc 100644 --- a/tests/Api/Products/GetTest.php +++ b/tests/Api/Products/GetTest.php @@ -145,7 +145,7 @@ private function checkIncludes(array $includes = []): void $element = [ 'type' => Relationships::PRODUCTS, - 'id' => $product->get('id'), + 'id' => (string) $product->get('id'), 'attributes' => [ 'typeId' => $productType->get('id'), 'name' => $product->get('name'), @@ -186,11 +186,11 @@ private function checkIncludes(array $includes = []): void 'data' => [ [ 'type' => Relationships::COMPANIES, - 'id' => $comOne->get('id'), + 'id' => (string) $comOne->get('id'), ], [ 'type' => Relationships::COMPANIES, - 'id' => $comTwo->get('id'), + 'id' => (string) $comTwo->get('id'), ], ], ]; @@ -219,7 +219,7 @@ private function checkIncludes(array $includes = []): void ], 'data' => [ 'type' => Relationships::PRODUCT_TYPES, - 'id' => $productType->get('id'), + 'id' => (string) $productType->get('id'), ], ]; From 2d0bcaac5a91e04e5a83b1e972f2c8c84a467e7c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:06:26 -0500 Subject: [PATCH 20/47] [#42] - convert cli suite to talon format --- tests/Cli/CheckHelpTaskTest.php | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/Cli/CheckHelpTaskTest.php diff --git a/tests/Cli/CheckHelpTaskTest.php b/tests/Cli/CheckHelpTaskTest.php new file mode 100644 index 00000000..97de12bf --- /dev/null +++ b/tests/Cli/CheckHelpTaskTest.php @@ -0,0 +1,45 @@ + + * + * 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\Cli; + +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; + +use function dirname; +use function exec; +use function implode; + +use const PHP_EOL; + +/** + * Ported from the Codeception `CheckHelpTaskCest`. Codeception's Cli module + * supplied `runShellCommand()`; Talon has no equivalent, so the binary is + * invoked directly. The path is absolute because the working directory a test + * runs from is not guaranteed. + */ +final class CheckHelpTaskTest extends AbstractUnitTestCase +{ + public function testCheckHelp(): void + { + $output = []; + $exitCode = 1; + + exec(dirname(__FILE__, 3) . '/runCli 2>&1', $output, $exitCode); + + $shellOutput = implode(PHP_EOL, $output); + + $this->assertSame(0, $exitCode); + $this->assertStringContainsString('--help', $shellOutput); + $this->assertStringContainsString('--clear-cache', $shellOutput); + } +} From 4383cbbaa733717b18f9d4ba44f43b2c27c21989 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:06:26 -0500 Subject: [PATCH 21/47] [#42] - remove codeception suites, support classes and configs --- .scrutinizer.yml_bak | 216 --------- resources/phpcs.xml | 1 - runTests | 3 - tests/Integration/Library/ModelTest.php | 178 +++++++ .../Library/Models/IndividualTypesTest.php | 61 +++ .../Library/Models/IndividualsTest.php} | 50 +- .../Library/Models/ProductTypesTest.php | 61 +++ .../Library/Models/ProductsTest.php} | 50 +- .../Library/Models/UsersTest.php} | 70 ++- tests/_bootstrap.php | 15 - tests/_support/ApiTester.php | 201 -------- tests/_support/CliTester.php | 29 -- tests/_support/Helper/Api.php | 21 - tests/_support/Helper/Cli.php | 13 - tests/_support/Helper/Integration.php | 438 ------------------ tests/_support/Helper/Unit.php | 31 -- tests/_support/IntegrationTester.php | 29 -- tests/_support/Page/Data.php | 331 ------------- tests/_support/UnitTester.php | 29 -- tests/_support/_generated/.gitignore | 2 - tests/api.suite.yml | 15 - tests/api/Companies/AddCest.php | 108 ----- tests/api/Companies/GetBase.php | 25 - tests/api/Companies/GetCest.php | 89 ---- tests/api/Companies/GetFieldsCest.php | 101 ---- tests/api/Companies/GetIncludesCest.php | 173 ------- tests/api/Companies/GetSortCest.php | 110 ----- tests/api/IndividualTypes/GetCest.php | 158 ------- tests/api/Individuals/GetCest.php | 246 ---------- tests/api/LoginCest.php | 49 -- tests/api/NotFoundCest.php | 15 - tests/api/ProductTypes/GetCest.php | 157 ------- tests/api/Products/GetCest.php | 250 ---------- tests/api/Users/GetCest.php | 237 ---------- tests/api/_bootstrap.php | 3 - tests/cli.suite.yml | 7 - tests/cli/CheckHelpTaskCest.php | 16 - tests/cli/_bootstrap.php | 1 - tests/integration.suite.yml | 6 - tests/integration/_bootstrap.php | 1 - tests/integration/library/ModelCest.php | 210 --------- .../library/Models/CompaniesCest.php | 119 ----- .../library/Models/CompaniesXProductsCest.php | 71 --- .../library/Models/IndividualTypesCest.php | 65 --- .../library/Models/ProductTypesCest.php | 65 --- .../integration/library/Traits/QueryCest.php | 184 -------- .../Transformers/BaseTransformerCest.php | 41 -- .../IndividualsTransformerCest.php | 149 ------ .../Transformers/ProductsTransformerCest.php | 156 ------- .../Validation/CompaniesValidatorCest.php | 26 -- tests/unit.suite.yml | 10 - tests/unit/BootstrapCest.php | 24 - tests/unit/_bootstrap.php | 3 - tests/unit/cli/BaseCest.php | 42 -- tests/unit/cli/BootstrapCest.php | 34 -- tests/unit/cli/ClearCacheCest.php | 65 --- tests/unit/config/AutoloaderCest.php | 50 -- tests/unit/config/ConfigCest.php | 20 - tests/unit/config/FunctionsCest.php | 103 ---- tests/unit/config/ProvidersCest.php | 45 -- tests/unit/library/BootstrapCest.php | 21 - tests/unit/library/Constants/FlagsCest.php | 15 - .../library/Constants/RelationshipsCest.php | 19 - tests/unit/library/ErrorHandlerCest.php | 57 --- tests/unit/library/Http/ResponseCest.php | 132 ------ tests/unit/library/Providers/CacheCest.php | 29 -- tests/unit/library/Providers/ConfigCest.php | 36 -- tests/unit/library/Providers/DatabaseCest.php | 30 -- .../library/Providers/ErrorHandlerCest.php | 34 -- tests/unit/library/Providers/LoggerCest.php | 30 -- .../library/Providers/ModelsMetadataCest.php | 29 -- tests/unit/library/Providers/ResponseCest.php | 26 -- tests/unit/library/Providers/RouterCest.php | 64 --- 73 files changed, 376 insertions(+), 5224 deletions(-) delete mode 100644 .scrutinizer.yml_bak delete mode 100755 runTests create mode 100644 tests/Integration/Library/ModelTest.php create mode 100644 tests/Integration/Library/Models/IndividualTypesTest.php rename tests/{integration/library/Models/IndividualsCest.php => Integration/Library/Models/IndividualsTest.php} (63%) create mode 100644 tests/Integration/Library/Models/ProductTypesTest.php rename tests/{integration/library/Models/ProductsCest.php => Integration/Library/Models/ProductsTest.php} (60%) rename tests/{integration/library/Models/UsersCest.php => Integration/Library/Models/UsersTest.php} (61%) delete mode 100644 tests/_bootstrap.php delete mode 100644 tests/_support/ApiTester.php delete mode 100644 tests/_support/CliTester.php delete mode 100644 tests/_support/Helper/Api.php delete mode 100644 tests/_support/Helper/Cli.php delete mode 100644 tests/_support/Helper/Integration.php delete mode 100644 tests/_support/Helper/Unit.php delete mode 100644 tests/_support/IntegrationTester.php delete mode 100644 tests/_support/Page/Data.php delete mode 100644 tests/_support/UnitTester.php delete mode 100644 tests/_support/_generated/.gitignore delete mode 100644 tests/api.suite.yml delete mode 100644 tests/api/Companies/AddCest.php delete mode 100644 tests/api/Companies/GetBase.php delete mode 100644 tests/api/Companies/GetCest.php delete mode 100644 tests/api/Companies/GetFieldsCest.php delete mode 100644 tests/api/Companies/GetIncludesCest.php delete mode 100644 tests/api/Companies/GetSortCest.php delete mode 100644 tests/api/IndividualTypes/GetCest.php delete mode 100644 tests/api/Individuals/GetCest.php delete mode 100644 tests/api/LoginCest.php delete mode 100644 tests/api/NotFoundCest.php delete mode 100644 tests/api/ProductTypes/GetCest.php delete mode 100644 tests/api/Products/GetCest.php delete mode 100644 tests/api/Users/GetCest.php delete mode 100644 tests/api/_bootstrap.php delete mode 100644 tests/cli.suite.yml delete mode 100644 tests/cli/CheckHelpTaskCest.php delete mode 100644 tests/cli/_bootstrap.php delete mode 100644 tests/integration.suite.yml delete mode 100644 tests/integration/_bootstrap.php delete mode 100644 tests/integration/library/ModelCest.php delete mode 100644 tests/integration/library/Models/CompaniesCest.php delete mode 100644 tests/integration/library/Models/CompaniesXProductsCest.php delete mode 100644 tests/integration/library/Models/IndividualTypesCest.php delete mode 100644 tests/integration/library/Models/ProductTypesCest.php delete mode 100644 tests/integration/library/Traits/QueryCest.php delete mode 100644 tests/integration/library/Transformers/BaseTransformerCest.php delete mode 100644 tests/integration/library/Transformers/IndividualsTransformerCest.php delete mode 100644 tests/integration/library/Transformers/ProductsTransformerCest.php delete mode 100644 tests/integration/library/Validation/CompaniesValidatorCest.php delete mode 100644 tests/unit.suite.yml delete mode 100644 tests/unit/BootstrapCest.php delete mode 100644 tests/unit/_bootstrap.php delete mode 100755 tests/unit/cli/BaseCest.php delete mode 100644 tests/unit/cli/BootstrapCest.php delete mode 100755 tests/unit/cli/ClearCacheCest.php delete mode 100755 tests/unit/config/AutoloaderCest.php delete mode 100755 tests/unit/config/ConfigCest.php delete mode 100755 tests/unit/config/FunctionsCest.php delete mode 100755 tests/unit/config/ProvidersCest.php delete mode 100755 tests/unit/library/BootstrapCest.php delete mode 100644 tests/unit/library/Constants/FlagsCest.php delete mode 100644 tests/unit/library/Constants/RelationshipsCest.php delete mode 100755 tests/unit/library/ErrorHandlerCest.php delete mode 100755 tests/unit/library/Http/ResponseCest.php delete mode 100644 tests/unit/library/Providers/CacheCest.php delete mode 100644 tests/unit/library/Providers/ConfigCest.php delete mode 100644 tests/unit/library/Providers/DatabaseCest.php delete mode 100644 tests/unit/library/Providers/ErrorHandlerCest.php delete mode 100644 tests/unit/library/Providers/LoggerCest.php delete mode 100644 tests/unit/library/Providers/ModelsMetadataCest.php delete mode 100644 tests/unit/library/Providers/ResponseCest.php delete mode 100644 tests/unit/library/Providers/RouterCest.php diff --git a/.scrutinizer.yml_bak b/.scrutinizer.yml_bak deleted file mode 100644 index 5c14e50a..00000000 --- a/.scrutinizer.yml_bak +++ /dev/null @@ -1,216 +0,0 @@ -build: - root_path: './' - environment: - selenium: false - memcached: true - elasticsearch: false - rabbitmq: false - postgresql: false - redis: false - node: false - php: - version: 7.1.12 - ini: - 'date.timezone': 'UTC' - hosts: - api.phalcon.ld: '127.0.0.1' - apache2: - modules: ['rewrite'] - sites: - api: - web_root: 'public' - host: 'api.phalcon.ld' - variables: - DATA_API_MYSQL_HOST: "localhost" - DATA_API_MYSQL_PASS: "password" - DATA_API_MYSQL_USER: "root" - DATA_API_MYSQL_NAME: "gonano" - DATA_API_MEMCACHED_HOST: "127.0.0.1" - DATA_API_MEMCACHED_PORT: 11211 - DATA_API_MEMCACHED_WEIGHT: 100 - APP_IP: "api.phalcon.ld" - - cache: - directories: - - ~/cphalcon - - dependencies: - before: - - cd /home/scrutinizer/build - - cp storage/ci/.env.example .env - - cp storage/ci/phinx.php.example phinx.php - - command: 'cd ~/ && git clone https://github.com/jbboehr/php-psr.git && cd php-psr && phpize && ./configure && make && make test && sudo make install' - - sed -i '$ a \\nextension=psr.so\n' /home/scrutinizer/.phpenv/versions/7.1.12/etc/php.ini - - - command: 'cd ~/ && git clone -q --depth=1 https://github.com/phalcon/cphalcon.git -b 4.0.x && cd cphalcon/build && ./install' - not_if: 'test -e /home/scrutinizer/cphalcon/build/php7/64bits/modules/phalcon.so' - - - command: 'cp -v /home/scrutinizer/cphalcon/build/php7/64bits/modules/phalcon.so /home/scrutinizer/.phpenv/versions/7.1.12/lib/php/extensions/no-debug-zts-20160303/' - only_if: 'test -e /home/scrutinizer/cphalcon/build/php7/64bits/modules/phalcon.so' - - sed -i '$ a \\n[Phalcon]\nextension=phalcon.so\n' /home/scrutinizer/.phpenv/versions/7.1.12/etc/php.ini - - php -m | grep -i Phalcon - - sudo /etc/init.d/apache2 restart - - mysql -e "CREATE DATABASE gonano" - - mysql -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('password');" - - mysql -e "FLUSH PRIVILEGES" - - cd /home/scrutinizer/build - - composer install --no-interaction - - ./vendor/bin/phinx migrate - - ./vendor/bin/codecept build - -# project_setup: -# override: true - - tests: - override: - - phpcs-run --standard=PSR2 --extensions=php --ignore=tests/*,storage/*,vendor/* ./ - - - command: './vendor/bin/codecept run --coverage-xml' - coverage: - file: './tests/_output/coverage.xml' - format: 'clover' - - php-scrutinizer-run --enable-security-analysis - -filter: - paths: ['./*'] - excluded_paths: - - 'tests/*' - - 'storage/*' - - 'vendor/*' - - 'phinx.php' - -build_failure_conditions: - # No classes/methods with a rating of D or worse - - 'elements.rating(<= C).exists' - # No new classes/methods with a rating of D or worse allowed - - 'elements.rating(<= C).new.exists' - - # No coding style issues allowed - Removing it for now until we fix index.php - - 'issues.label("coding-style").exists' - # No new coding style issues allowed - - 'issues.label("coding-style").new.exists' - - # More than 5 new coding style issues. - - 'issues.label("coding-style").new.count > 5' - # New issues of major or higher severity - - 'issues.severity(>= MAJOR).new.exists' - - # Code Quality Rating drops below 8 - - 'project.metric("scrutinizer.quality", < 8)' - # Code Coverage drops below 60% - - 'project.metric("scrutinizer.test_coverage", < 0.60)' - - # Code Coverage decreased from previous inspection by more than 10% - - 'project.metric_change("scrutinizer.test_coverage", < -0.10)' - -tools: - php_analyzer: true - php_mess_detector: true - php_code_sniffer: - config: - standard: PSR2 - php_loc: - enabled: true - excluded_dirs: ['vendor', 'tests', 'storage'] - php_cpd: - enabled: true - excluded_dirs: ['vendor', 'tests', 'storage'] - -checks: - php: - verify_property_names: true - verify_argument_usable_as_reference: true - verify_access_scope_valid: true - variable_existence: true - useless_calls: true - use_statement_alias_conflict: true - unused_variables: true - unused_properties: true - unused_parameters: true - unused_methods: true - unreachable_code: true - too_many_arguments: true - symfony_request_injection: true - switch_fallthrough_commented: true - sql_injection_vulnerabilities: true - simplify_boolean_return: true - security_vulnerabilities: true - return_in_constructor: true - return_doc_comments: true - return_doc_comment_if_not_inferrable: true - require_scope_for_methods: true - require_php_tag_first: true - property_assignments: true - properties_in_camelcaps: true - precedence_mistakes: true - precedence_in_conditions: true - phpunit_assertions: true - parse_doc_comments: true - parameters_in_camelcaps: true - parameter_non_unique: true - parameter_doc_comments: true - param_doc_comment_if_not_inferrable: true - overriding_private_members: true - overriding_parameter: true - non_commented_empty_catch_block: true - no_trait_type_hints: true - no_trailing_whitespace: true - no_short_variable_names: - minimum: '3' - no_short_open_tag: true - no_short_method_names: - minimum: '3' - no_property_on_interface: true - no_non_implemented_abstract_methods: true - no_long_variable_names: - maximum: '20' - no_goto: true - no_exit: true - no_eval: true - no_error_suppression: true - no_debug_code: true - more_specific_types_in_doc_comments: true - missing_arguments: true - method_calls_on_non_object: true - instanceof_class_exists: true - foreach_usable_as_reference: true - foreach_traversable: true - fix_use_statements: - remove_unused: true - preserve_multiple: true - preserve_blanklines: false - order_alphabetically: true - fix_line_ending: true - fix_doc_comments: true - encourage_shallow_comparison: true - duplication: true - deprecated_code_usage: true - deadlock_detection_in_loops: true - comparison_always_same_result: true - code_rating: true - closure_use_not_conflicting: true - closure_use_modifiable: true - check_method_contracts: - verify_interface_like_constraints: true - verify_documented_constraints: true - verify_parent_constraints: true - catch_class_exists: true - call_to_parent_method: true - avoid_superglobals: true - avoid_length_functions_in_loops: true - avoid_duplicate_types: true - avoid_closing_tag: true - assignment_of_null_return: true - argument_type_checks: true - remove_extra_empty_lines: true - naming_conventions: - local_variable: '^[a-z][a-zA-Z0-9]*$' - abstract_class_name: ^Abstract|Factory$ - utility_class_name: '^[A-Z][a-zA-Z0-9]*$' - constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$' - property_name: '^[a-z][a-zA-Z0-9]*$' - method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$' - parameter_name: '^[a-z][a-zA-Z0-9]*$' - interface_name: '^[A-Z][a-zA-Z0-9]*Interface$' - exception_name: '^[A-Z][a-zA-Z0-9]*Exception$' - isser_method_name: '^(?:is|has|should|may|supports|before|after)' diff --git a/resources/phpcs.xml b/resources/phpcs.xml index e5a5d214..987c155b 100644 --- a/resources/phpcs.xml +++ b/resources/phpcs.xml @@ -11,7 +11,6 @@ */public/* */_output/* - */_support/* ../api ../cli ../library diff --git a/runTests b/runTests deleted file mode 100755 index 51361088..00000000 --- a/runTests +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -./vendor/bin/codecept run --coverage --coverage-html --coverage-xml diff --git a/tests/Integration/Library/ModelTest.php b/tests/Integration/Library/ModelTest.php new file mode 100644 index 00000000..0d7badc7 --- /dev/null +++ b/tests/Integration/Library/ModelTest.php @@ -0,0 +1,178 @@ + + * + * 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\Integration\Library; + +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Logger\Logger; +use Phalcon\Messages\Message; + +use function Phalcon\Api\Core\appPath; + +final class ModelTest extends AbstractIntegrationTestCase +{ + public function testModelGetSetFields(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', + ] + ); + } + + public function testModelSetNonExistingFields(): void + { + $this->expectException(ModelException::class); + + $fixture = new Users(); + $fixture->set('id', 1000) + ->set('some_field', true) + ->save() + ; + } + + public function testModelGetNonExistingFields(): void + { + /** @var Users $user */ + $user = $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', + ] + ); + + $this->expectException(ModelException::class); + + $user->get('some_field'); + } + + public function testModelUpdateFields(): void + { + /** @var Users $user */ + $user = $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', + ] + ); + + $user->set('username', 'testusername') + ->save() + ; + + $this->assertSame($user->get('username'), 'testusername'); + $this->assertSame($user->get('password'), 'testpass'); + $this->assertSame($user->get('issuer'), 'phalcon.io'); + $this->assertSame($user->get('tokenPassword'), '12345'); + $this->assertSame($user->get('tokenId'), '00110011'); + } + + public function testModelUpdateFieldsNotSanitized(): void + { + /** @var Users $user */ + $user = $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', + + ] + ); + + $user->set('password', 'abcde\nfg') + ->save() + ; + $this->assertSame($user->get('password'), 'abcde\nfg'); + + /** Not sanitized */ + $user->set('password', 'abcde\nfg', false) + ->save() + ; + $this->assertSame($user->get('password'), 'abcde\nfg'); + } + + public function testCheckModelMessages(): void + { + $user = $this->mockWithConstructor( + Users::class, + [], + [ + 'save' => false, + 'getMessages' => [ + new Message('error 1'), + new Message('error 2'), + ], + ] + ); + + $result = $user + ->set('username', 'test') + ->save() + ; + $this->assertFalse($result); + + $this->assertSame('error 1
error 2
', $user->getModelMessages()); + } + + public function testCheckModelMessagesWithLogger(): void + { + /** @var Logger $logger */ + $logger = $this->grabFromDi('logger'); + $user = $this->mockWithConstructor( + Users::class, + [], + [ + 'save' => false, + 'getMessages' => [ + new Message('error 1'), + new Message('error 2'), + ], + ] + ); + + $fileName = appPath('storage/logs/api.log'); + $result = $user + ->set('username', 'test') + ->save() + ; + $this->assertFalse($result); + $this->assertSame('error 1
error 2
', $user->getModelMessages()); + + $user->getModelMessages($logger); + + $this->assertFileContentsContains($fileName, "error 1\n"); + $this->assertFileContentsContains($fileName, "error 2\n"); + } +} diff --git a/tests/Integration/Library/Models/IndividualTypesTest.php b/tests/Integration/Library/Models/IndividualTypesTest.php new file mode 100644 index 00000000..828878c1 --- /dev/null +++ b/tests/Integration/Library/Models/IndividualTypesTest.php @@ -0,0 +1,61 @@ + + * + * 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\Integration\Library\Models; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Filter\Filter; + +final class IndividualTypesTest extends AbstractIntegrationTestCase +{ + public function testValidateModel(): void + { + $this->haveModelDefinition( + IndividualTypes::class, + [ + 'id', + 'name', + 'description', + ] + ); + } + + public function testValidateFilters(): void + { + $model = new IndividualTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } + + public function testValidateRelationships(): void + { + $actual = $this->getModelRelationships(IndividualTypes::class); + $expected = [ + [ + 2, + 'id', + Individuals::class, + 'typeId', + ['alias' => Relationships::INDIVIDUALS, 'reusable' => true], + ], + ]; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/integration/library/Models/IndividualsCest.php b/tests/Integration/Library/Models/IndividualsTest.php similarity index 63% rename from tests/integration/library/Models/IndividualsCest.php rename to tests/Integration/Library/Models/IndividualsTest.php index 13d2eeb7..7eafb041 100644 --- a/tests/integration/library/Models/IndividualsCest.php +++ b/tests/Integration/Library/Models/IndividualsTest.php @@ -1,24 +1,30 @@ + * + * 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\Integration\Library\Models; -use IntegrationTester; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\Companies; use Phalcon\Api\Models\Individuals; use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; use Phalcon\Filter\Filter; -class IndividualsCest +final class IndividualsTest extends AbstractIntegrationTestCase { - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateModel(IntegrationTester $I) + public function testValidateModel(): void { - $I->haveModelDefinition( + $this->haveModelDefinition( Individuals::class, [ 'id', @@ -33,12 +39,7 @@ public function validateModel(IntegrationTester $I) ); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) + public function testValidateFilters(): void { $model = new Individuals(); $expected = [ @@ -51,33 +52,28 @@ public function validateFilters(IntegrationTester $I) 'last' => Filter::FILTER_STRING, 'suffix' => Filter::FILTER_STRING, ]; - $I->assertSame($expected, $model->getModelFilters()); + $this->assertSame($expected, $model->getModelFilters()); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) + public function testValidateRelationships(): void { - $actual = $I->getModelRelationships(Individuals::class); + $actual = $this->getModelRelationships(Individuals::class); $expected = [ [ 0, 'companyId', Companies::class, 'id', - ['alias' => Relationships::COMPANIES, 'reusable' => true] + ['alias' => Relationships::COMPANIES, 'reusable' => true], ], [ 1, 'typeId', IndividualTypes::class, 'id', - ['alias' => Relationships::INDIVIDUAL_TYPES, 'reusable' => true] + ['alias' => Relationships::INDIVIDUAL_TYPES, 'reusable' => true], ], ]; - $I->assertSame($expected, $actual); + $this->assertSame($expected, $actual); } } diff --git a/tests/Integration/Library/Models/ProductTypesTest.php b/tests/Integration/Library/Models/ProductTypesTest.php new file mode 100644 index 00000000..11ce60a0 --- /dev/null +++ b/tests/Integration/Library/Models/ProductTypesTest.php @@ -0,0 +1,61 @@ + + * + * 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\Integration\Library\Models; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Models\Products; +use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Filter\Filter; + +final class ProductTypesTest extends AbstractIntegrationTestCase +{ + public function testValidateModel(): void + { + $this->haveModelDefinition( + ProductTypes::class, + [ + 'id', + 'name', + 'description', + ] + ); + } + + public function testValidateFilters(): void + { + $model = new ProductTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } + + public function testValidateRelationships(): void + { + $actual = $this->getModelRelationships(ProductTypes::class); + $expected = [ + [ + 2, + 'id', + Products::class, + 'typeId', + ['alias' => Relationships::PRODUCTS, 'reusable' => true], + ], + ]; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/integration/library/Models/ProductsCest.php b/tests/Integration/Library/Models/ProductsTest.php similarity index 60% rename from tests/integration/library/Models/ProductsCest.php rename to tests/Integration/Library/Models/ProductsTest.php index 007225b7..f39c19b8 100644 --- a/tests/integration/library/Models/ProductsCest.php +++ b/tests/Integration/Library/Models/ProductsTest.php @@ -1,24 +1,30 @@ + * + * 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\Integration\Library\Models; -use IntegrationTester; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\Companies; use Phalcon\Api\Models\Products; use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; use Phalcon\Filter\Filter; -class ProductsCest +final class ProductsTest extends AbstractIntegrationTestCase { - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateModel(IntegrationTester $I) + public function testValidateModel(): void { - $I->haveModelDefinition( + $this->haveModelDefinition( Products::class, [ 'id', @@ -31,12 +37,7 @@ public function validateModel(IntegrationTester $I) ); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) + public function testValidateFilters(): void { $model = new Products(); $expected = [ @@ -47,34 +48,29 @@ public function validateFilters(IntegrationTester $I) 'quantity' => Filter::FILTER_ABSINT, 'price' => Filter::FILTER_FLOAT, ]; - $I->assertSame($expected, $model->getModelFilters()); + $this->assertSame($expected, $model->getModelFilters()); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) + public function testValidateRelationships(): void { - $actual = $I->getModelRelationships(Products::class); + $actual = $this->getModelRelationships(Products::class); $expected = [ [ 0, 'typeId', ProductTypes::class, 'id', - ['alias' => Relationships::PRODUCT_TYPES, 'reusable' => true] + ['alias' => Relationships::PRODUCT_TYPES, 'reusable' => true], ], [ 4, 'id', Companies::class, 'id', - ['alias' => Relationships::COMPANIES, 'reusable' => true] + ['alias' => Relationships::COMPANIES, 'reusable' => true], ], ]; - $I->assertSame($expected, $actual); + $this->assertSame($expected, $actual); } } diff --git a/tests/integration/library/Models/UsersCest.php b/tests/Integration/Library/Models/UsersTest.php similarity index 61% rename from tests/integration/library/Models/UsersCest.php rename to tests/Integration/Library/Models/UsersTest.php index b30626d4..2d49d1ef 100644 --- a/tests/integration/library/Models/UsersCest.php +++ b/tests/Integration/Library/Models/UsersTest.php @@ -1,30 +1,37 @@ + * + * 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\Integration\Library\Models; -use IntegrationTester; -use Page\Data; -use Phalcon\Api\Exception\ModelException; use Phalcon\Api\Models\Users; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; -use Phalcon\Filter\Filter; use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Signer\Hmac; use Phalcon\Encryption\Security\JWT\Validator; +use Phalcon\Filter\Filter; + +use function count; +use function time; -class UsersCest +final class UsersTest extends AbstractIntegrationTestCase { use TokenTrait; - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateModel(IntegrationTester $I) + public function testValidateModel(): void { - $I->haveModelDefinition( + $this->haveModelDefinition( Users::class, [ 'id', @@ -38,12 +45,7 @@ public function validateModel(IntegrationTester $I) ); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) + public function testValidateFilters(): void { $model = new Users(); $expected = [ @@ -55,20 +57,13 @@ public function validateFilters(IntegrationTester $I) 'tokenPassword' => Filter::FILTER_STRING, 'tokenId' => Filter::FILTER_STRING, ]; - $I->assertSame($expected, $model->getModelFilters()); + $this->assertSame($expected, $model->getModelFilters()); } - /** - * @param IntegrationTester $I - * - * @return void - * @throws ModelException - * @throws ValidatorException - */ - public function checkValidationData(IntegrationTester $I) + public function testCheckValidationData(): void { /** @var Users $user */ - $user = $I->haveRecordWithFields( + $user = $this->haveRecordWithFields( Users::class, [ 'username' => Data::$testUsername, @@ -92,18 +87,13 @@ public function checkValidationData(IntegrationTester $I) ; $class = Validator::class; - $actual = $user->getValidationData(); - $I->assertInstanceOf($class, $actual); + $actual = $user->getValidationData($token); + $this->assertInstanceOf($class, $actual); } - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) + public function testValidateRelationships(): void { - $actual = $I->getModelRelationships(Users::class); - $I->assertSame(0, count($actual)); + $actual = $this->getModelRelationships(Users::class); + $this->assertSame(0, count($actual)); } } diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php deleted file mode 100644 index eca86772..00000000 --- a/tests/_bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -seeResponseIsJson(); - $this->seeResponseCodeIs($code); - $this->seeResponseMatchesJsonType( - [ - 'jsonapi' => [ - 'version' => 'string' - ], - // 'data' => 'array', - // 'errors' => 'array', - 'meta' => [ - 'timestamp' => 'string:date', - 'hash' => 'string', - ] - ] - ); - - $this->checkHash(); - } - - /** - * Checks if the response was successful - * - * @return void - */ - public function seeResponseIs400(): void - { - $this->checkErrorResponse(HttpCode::BAD_REQUEST); - } - - /** - * Checks if the response was successful - * - * @return void - */ - public function seeResponseIs404(): void - { - $this->checkErrorResponse(HttpCode::NOT_FOUND); - } - - /** - * @param string $message - * - * @return void - */ - public function seeErrorJsonResponse(string $message) - { - $this->seeResponseContainsJson( - [ - 'jsonapi' => [ - 'version' => '1.0', - ], - 'errors' => [ - $message, - ], - ] - ); - } - - /** - * @param string $key - * @param array $data - * - * @return void - */ - public function seeSuccessJsonResponse(string $key = 'data', array $data = []) - { - $this->seeResponseContainsJson([$key => $data]); - } - - /** - * @return mixed|string - */ - public function apiLogin() - { - $this->deleteHeader('Authorization'); - $this->sendPOST(DataPage::$loginUrl, DataPage::loginJson()); - $this->seeResponseIsSuccessful(); - - $response = $this->grabResponse(); - $response = json_decode($response, true); - $data = $response['data'] ?? []; - $token = $data['token'] ?? ''; - - return $token; - } - - /** - * @return mixed - */ - public function addApiUserRecord() - { - return $this->haveRecordWithFields( - Users::class, - [ - 'status' => Flags::ACTIVE, - 'username' => DataPage::$testUsername, - 'password' => DataPage::$testPassword, - 'issuer' => DataPage::$testIssuer, - 'tokenPassword' => DataPage::$testTokenPassword, - 'tokenId' => DataPage::$testTokenId, - ] - ); - } - - /** - * @return void - */ - private function checkHash(): void - { - $response = $this->grabResponse(); - $response = json_decode($response, true); - $timestamp = $response['meta']['timestamp']; - $hash = $response['meta']['hash']; - unset($response['meta'], $response['jsonapi']); - - $actual = sha1($timestamp . json_encode($response)); - $this->assertEquals($hash, $actual); - } - - /** - * @param int $code - * - * @return void - */ - private function checkErrorResponse(int $code): void - { - $this->seeResponseMatchesJsonType( - [ - 'jsonapi' => [ - 'version' => 'string' - ], - 'meta' => [ - 'timestamp' => 'string:date', - 'hash' => 'string', - ] - ] - ); - - $response = $this->grabResponse(); - $response = json_decode($response, true); - $timestamp = $response['meta']['timestamp']; - $hash = $response['meta']['hash']; - $this->assertEquals(HttpCode::getDescription($code), $response['errors'][0]); - unset($response['jsonapi'], $response['meta']); - $this->assertEquals($hash, sha1($timestamp . json_encode($response))); - - - $this->seeResponseIsJson(); - $this->seeResponseCodeIs($code); - $this->seeResponseMatchesJsonType( - [ - 'jsonapi' => [ - 'version' => 'string' - ], - 'meta' => [ - 'timestamp' => 'string:date', - 'hash' => 'string', - ] - ] - ); - - $this->checkHash(); - } -} diff --git a/tests/_support/CliTester.php b/tests/_support/CliTester.php deleted file mode 100644 index c899029d..00000000 --- a/tests/_support/CliTester.php +++ /dev/null @@ -1,29 +0,0 @@ -assertRegExp($regexp, $content); - } -} diff --git a/tests/_support/Helper/Cli.php b/tests/_support/Helper/Cli.php deleted file mode 100644 index b8ab791e..00000000 --- a/tests/_support/Helper/Cli.php +++ /dev/null @@ -1,13 +0,0 @@ - false]; - - /** - * Test initializer - */ - public function _before(TestInterface $test) - { - PhDI::reset(); - - $app = new Api(); - $this->diContainer = $app->getContainer(); - - if ($this->options['rollback']) { - $this->diContainer->get('db') - ->begin() - ; - } - $this->savedModels = []; - $this->savedRecords = []; - } - - public function _after(TestInterface $test) - { - if (!$this->options['rollback']) { - foreach ($this->savedRecords as $record) { - $record->delete(); - } - } else { - $this->diContainer->get('db') - ->rollback() - ; - } - $this->diContainer->get('db') - ->close() - ; - } - - /** - * @param string $namePrefix - * @param string $addressPrefix - * @param string $cityPrefix - * @param string $phonePrefix - * - * @return Companies - */ - public function addCompanyRecord( - string $namePrefix = '', - string $addressPrefix = '', - string $cityPrefix = '', - string $phonePrefix = '' - ) { - return $this->haveRecordWithFields( - Companies::class, - [ - 'name' => uniqid($namePrefix), - 'address' => uniqid($addressPrefix), - 'city' => uniqid($cityPrefix), - 'phone' => uniqid($phonePrefix), - ] - ); - } - - /** - * @param int $companyId - * @param int $productId - * - * @return CompaniesXProducts - */ - public function addCompanyXProduct(int $companyId, int $productId) - { - return $this->haveRecordWithFields( - CompaniesXProducts::class, - [ - 'companyId' => $companyId, - 'productId' => $productId, - ] - ); - } - - /** - * @param string $namePrefix - * - * @return IndividualTypes - */ - public function addIndividualTypeRecord(string $namePrefix = '') - { - return $this->haveRecordWithFields( - IndividualTypes::class, - [ - 'name' => uniqid($namePrefix), - 'description' => uniqid(), - ] - ); - } - - /** - * @param string $namePrefix - * @param int $comId - * @param int $typeId - * - * @return Individuals - */ - public function addIndividualRecord(string $namePrefix = '', int $comId = 0, int $typeId = 0) - { - return $this->haveRecordWithFields( - Individuals::class, - [ - 'companyId' => $comId, - 'typeId' => $typeId, - 'prefix' => uniqid(), - 'first' => uniqid($namePrefix), - 'middle' => uniqid(), - 'last' => uniqid(), - 'suffix' => uniqid(), - ] - ); - } - - /** - * @param string $namePrefix - * @param int $typeId - * - * @return Products - */ - public function addProductRecord(string $namePrefix = '', int $typeId = 0) - { - return $this->haveRecordWithFields( - Products::class, - [ - 'name' => uniqid($namePrefix), - 'typeId' => $typeId, - 'description' => uniqid(), - 'quantity' => 25, - 'price' => 19.99, - ] - ); - } - - /** - * @param string $namePrefix - * - * @return ProductTypes - */ - public function addProductTypeRecord(string $namePrefix = '') - { - return $this->haveRecordWithFields( - ProductTypes::class, - [ - 'name' => uniqid($namePrefix), - 'description' => uniqid(), - ] - ); - } - - /** - * @return mixed - */ - public function grabDi() - { - return $this->diContainer; - } - - /** - * @param string $name - * - * @return mixed - */ - public function grabFromDi(string $name) - { - return $this->diContainer->get($name); - } - - /** - * Returns the relationships that a model has - * - * @param string $class - * - * @return array - */ - public function getModelRelationships(string $class): array - { - /** @var AbstractModel $class */ - $model = new $class(); - $manager = $model->getModelsManager(); - $relationships = $manager->getRelations($class); - - $data = []; - foreach ($relationships as $relationship) { - $data[] = [ - $relationship->getType(), - $relationship->getFields(), - $relationship->getReferencedModel(), - $relationship->getReferencedFields(), - $relationship->getOptions(), - ]; - } - - return $data; - } - - /** - * Get a record from $modelName with fields provided - * - * @param string $modelName - * @param array $fields - * - * @return bool|AbstractModel - */ - public function getRecordWithFields(string $modelName, $fields = []) - { - $record = false; - if (count($fields) > 0) { - $conditions = ''; - $bind = []; - foreach ($fields as $field => $value) { - $conditions .= sprintf( - '%s = :%s: AND ', - $field, - $field - ); - $bind[$field] = $value; - } - - $conditions = rtrim($conditions, ' AND '); - - /** @var AbstractModel $record */ - $record = $modelName::findFirst( - [ - 'conditions' => $conditions, - 'bind' => $bind, - ] - ); - } - - return $record; - } - - /** - * @param array $configData - */ - public function haveConfig(array $configData) - { - $config = new PhConfig($configData); - $this->diContainer->set('config', $config); - } - - /** - * Checks model fields - * - * @param string $modelName - * @param array $fields - */ - public function haveModelDefinition(string $modelName, array $fields) - { - /** @var AbstractModel $model */ - $model = new $modelName; - $metadata = $model->getModelsMetaData(); - $attributes = $metadata->getAttributes($model); - - $this->assertEquals( - count($fields), - count($attributes), - "Field count not correct for $modelName" - ); - - foreach ($fields as $value) { - $this->assertContains( - $value, - $attributes, - "Field not exists in $modelName" - ); - } - } - - /** - * Create a record for $modelName with fields provided - * - * @param string $modelName - * @param array $fields - * - * @return mixed - */ - public function haveRecordWithFields(string $modelName, array $fields = []) - { - $record = new $modelName; - foreach ($fields as $key => $val) { - $record->set($key, $val); - } - - $result = $record->save(); - $this->savedModels[$modelName] = $fields; - $this->assertNotSame(false, $result); - - $this->savedRecords[] = $record; - - return $record; - } - - /** - * @param string $name - * @param mixed $service - */ - public function haveService(string $name, $service) - { - $this->diContainer->set($name, $service); - } - - /** - * @param string $name - */ - public function removeService(string $name) - { - if ($this->diContainer->has($name)) { - $this->diContainer->remove($name); - } - } - - /** - * Check that record created with haveRecordWithFields can be fetched and - * all its fields contain valid values - * - * @param $modelName - * @param $by - * @param array $except - * - * @return mixed - */ - public function seeRecordFieldsValid($modelName, $by, array $except = []) - { - if (!isset($this->savedModels[$modelName])) { - throw new TestRuntimeException( - 'Should be used after haveModelWithFields with ' . $modelName - ); - } - $fields = $this->savedModels[$modelName]; - if (!is_array($by)) { - $by = [$by]; - } - $bySelector = implode( - ' AND ', - array_map( - function ($key) { - return "$key = :$key:"; - }, - $by - ) - ); - $byBind = []; - foreach ($by as $byVal) { - if (!isset($fields[$byVal])) { - throw new TestRuntimeException("Field $byVal is not set"); - } - $byBind[$byVal] = $fields[$byVal]; - } - $record = call_user_func( - [ - $modelName, 'findFirst', - ], - [ - 'conditions' => $bySelector, - 'bind' => $byBind, - ] - ); - if (!$record) { - $this->fail("Record $modelName for $by not found"); - } - - foreach ($fields as $key => $val) { - if (isset($except[$key])) { - continue; - } - $this->assertEquals( - $val, - $record->get($key), - "Field in $modelName for $key not valid" - ); - } - - return $record; - } - - /** - * Checks that record exists and has provided fields - * - * @param $model - * @param $by - * @param $fields - */ - public function seeRecordSaved($model, $by, $fields) - { - $this->savedModels[$model] = array_merge($by, $fields); - $record = $this->seeRecordFieldsValid( - $model, - array_keys($by), - array_keys($by) - ); - $this->savedRecords[] = $record; - } -} diff --git a/tests/_support/Helper/Unit.php b/tests/_support/Helper/Unit.php deleted file mode 100644 index 7159e23d..00000000 --- a/tests/_support/Helper/Unit.php +++ /dev/null @@ -1,31 +0,0 @@ - self::$testUsername, - 'password' => self::$testPassword, - ]; - } - - /** - * @param $name - * @param string $address - * @param string $city - * @param string $phone - * - * @return array - */ - public static function companyAddJson($name, $address = '', $city = '', $phone = '') - { - return [ - 'name' => $name, - 'address' => $address, - 'city' => $city, - 'phone' => $phone, - ]; - } - - /** - * @param Companies $record - * - * @return array - * @throws ModelException - */ - public static function companiesAddResponse(Companies $record): array - { - return [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'name' => $record->get('name'), - 'address' => $record->get('address'), - 'city' => $record->get('city'), - 'phone' => $record->get('phone'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], - ]; - } - - /** - * @param Companies $record - * - * @return array - * @throws ModelException - */ - public static function companiesResponse(Companies $record): array - { - return [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'name' => $record->get('name'), - 'address' => $record->get('address'), - 'city' => $record->get('city'), - 'phone' => $record->get('phone'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], - 'relationships' => [ - Relationships::PRODUCTS => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/products', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - 'related' => sprintf( - '%s/%s/%s/products', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ] - ], - Relationships::INDIVIDUALS => [ - "links" => [ - 'self' => sprintf( - '%s/%s/%s/relationships/individuals', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - 'related' => sprintf( - '%s/%s/%s/individuals', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], - ], - ], - ]; - } - - /** - * @param Individuals $record - * - * @return array - * @throws ModelException - */ - public static function individualResponse(Individuals $record): array - { - return [ - 'type' => Relationships::INDIVIDUALS, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'companyId' => $record->get('companyId'), - 'typeId' => $record->get('typeId'), - 'prefix' => $record->get('prefix'), - 'first' => $record->get('first'), - 'middle' => $record->get('middle'), - 'last' => $record->get('last'), - 'suffix' => $record->get('suffix'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUALS, - $record->get('id') - ), - ], - ]; - } - - /** - * @param IndividualTypes $record - * - * @return array - * @throws ModelException - */ - public static function individualTypeResponse(IndividualTypes $record): array - { - return [ - 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'name' => $record->get('name'), - 'description' => $record->get('description'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUAL_TYPES, - $record->get('id') - ), - ], - ]; - } - - /** - * @param Products $record - * - * @return array - * @throws ModelException - */ - public static function productResponse(Products $record): array - { - return [ - 'type' => Relationships::PRODUCTS, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'typeId' => $record->get('typeId'), - 'name' => $record->get('name'), - 'description' => $record->get('description'), - 'quantity' => $record->get('quantity'), - 'price' => $record->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCTS, - $record->get('id') - ), - ], - ]; - } - - /** - * @param Products $record - * - * @return array - * @throws ModelException - */ - public static function productFieldsResponse(Products $record): array - { - return [ - 'type' => Relationships::PRODUCTS, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'name' => $record->get('name'), - 'price' => $record->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCTS, - $record->get('id') - ), - ], - ]; - } - - /** - * @param ProductTypes $record - * - * @return array - * @throws ModelException - */ - public static function productTypeResponse(ProductTypes $record): array - { - return [ - 'type' => Relationships::PRODUCT_TYPES, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'name' => $record->get('name'), - 'description' => $record->get('description'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCT_TYPES, - $record->get('id') - ), - ], - ]; - } - - /** - * @param AbstractModel $record - * - * @return array - * @throws ModelException - */ - public static function userResponse(AbstractModel $record) - { - return [ - 'type' => Relationships::USERS, - 'id' => (string) $record->get('id'), - 'attributes' => [ - 'status' => $record->get('status'), - 'username' => $record->get('username'), - 'issuer' => $record->get('issuer'), - 'tokenPassword' => $record->get('tokenPassword'), - 'tokenId' => $record->get('tokenId'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::USERS, - $record->get('id') - ), - ], - ]; - } -} diff --git a/tests/_support/UnitTester.php b/tests/_support/UnitTester.php deleted file mode 100644 index 9c79d3b6..00000000 --- a/tests/_support/UnitTester.php +++ /dev/null @@ -1,29 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - $name = uniqid('com'); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST( - Data::$companiesUrl, - Data::companyAddJson( - $name, - '123 Phalcon way', - 'World', - '555-444-7777' - ) - ); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(Response::CREATED); - - $company = $I->getRecordWithFields( - Companies::class, - [ - 'name' => $name, - ] - ); - $I->assertNotEquals(false, $company); - - $I->seeHttpHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))); - $I->seeSuccessJsonResponse( - 'data', - Data::companiesAddResponse($company) - ); - - $I->assertNotEquals(false, $company->delete()); - } - - /** - * @param ApiTester $I - */ - public function addNewCompanyWithExistingName(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - $name = uniqid('com'); - $I->haveRecordWithFields( - Companies::class, - [ - 'name' => $name, - ] - ); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST( - Data::$companiesUrl, - Data::companyAddJson( - $name, - '123 Phalcon way', - 'World', - '555-444-7777' - ) - ); - $I->deleteHeader('Authorization'); - $I->seeErrorJsonResponse('The company name already exists in the database'); - } - - /** - * @param ApiTester $I - */ - public function addNewCompanyWithoutPostingName(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST( - Data::$companiesUrl, - Data::companyAddJson( - '', - '123 Phalcon way', - 'World', - '555-444-7777' - ) - ); - $I->deleteHeader('Authorization'); - $I->deleteHeader('Authorization'); - $I->seeErrorJsonResponse('The company name is required'); - } -} diff --git a/tests/api/Companies/GetBase.php b/tests/api/Companies/GetBase.php deleted file mode 100644 index 4cbbcbcf..00000000 --- a/tests/api/Companies/GetBase.php +++ /dev/null @@ -1,25 +0,0 @@ -addCompanyRecord('com-a'); - $indType = $I->addIndividualTypeRecord('type-a-'); - $indOne = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); - $indTwo = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); - $prdType = $I->addProductTypeRecord('type-a-'); - $prdOne = $I->addProductRecord('prd-a-', $prdType->get('id')); - $prdTwo = $I->addProductRecord('prd-b-', $prdType->get('id')); - $I->addCompanyXProduct($company->get('id'), $prdOne->get('id')); - $I->addCompanyXProduct($company->get('id'), $prdTwo->get('id')); - - return [$company, $prdOne, $prdTwo, $indOne, $indTwo]; - } -} diff --git a/tests/api/Companies/GetCest.php b/tests/api/Companies/GetCest.php deleted file mode 100644 index 5a3c0b23..00000000 --- a/tests/api/Companies/GetCest.php +++ /dev/null @@ -1,89 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - $company = $I->addCompanyRecord('com-a-'); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesRecordUrl, $company->get('id'))); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($company), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getUnknownCompany(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesRecordUrl, 1)); - $I->deleteHeader('Authorization'); - $I->seeResponseIs404(); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getCompanies(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $comOne */ - $comOne = $I->addCompanyRecord('com-a-'); - /** @var Companies $comTwo */ - $comTwo = $I->addCompanyRecord('com-b-'); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$companiesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($comOne), - Data::companiesResponse($comTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getCompaniesNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$companiesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse(); - } -} diff --git a/tests/api/Companies/GetFieldsCest.php b/tests/api/Companies/GetFieldsCest.php deleted file mode 100644 index 7f3b61b4..00000000 --- a/tests/api/Companies/GetFieldsCest.php +++ /dev/null @@ -1,101 +0,0 @@ -runTestsCompaniesWithIncludesAndFields($I); - } - - public function getCompaniesWithIncludesAndUnknownFields(ApiTester $I) - { - $this->runTestsCompaniesWithIncludesAndFields($I, ',unknown-product-field'); - } - - private function runTestsCompaniesWithIncludesAndFields(ApiTester $I, string $fields = '') - { - list($com, $prdOne, $prdTwo) = $this->addRecords($I); - - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$companiesRecordIncludesUrl, - $com->get('id'), - Relationships::PRODUCTS - ) . - '&fields[' . Relationships::COMPANIES . ']=id,name,city' . - '&fields[' . Relationships::PRODUCTS . ']=id,name,price' . $fields - ); - - $I->deleteHeader('Authorization'); - - $I->seeResponseIsSuccessful(); - - $included = []; - $element = [ - 'type' => Relationships::COMPANIES, - 'id' => $com->get('id'), - 'attributes' => [ - 'name' => $com->get('name'), - 'city' => $com->get('city'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id') - ), - ], - ]; - - $element['relationships'][Relationships::PRODUCTS] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::PRODUCTS - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::PRODUCTS - ), - ], - 'data' => [ - [ - 'type' => Relationships::PRODUCTS, - 'id' => $prdOne->get('id'), - ], - [ - 'type' => Relationships::PRODUCTS, - 'id' => $prdTwo->get('id'), - ], - ], - ]; - - $included[] = Data::productFieldsResponse($prdOne); - $included[] = Data::productFieldsResponse($prdTwo); - - $I->seeSuccessJsonResponse('data', [$element]); - - if (count($included) > 0) { - $I->seeSuccessJsonResponse('included', $included); - } - } -} diff --git a/tests/api/Companies/GetIncludesCest.php b/tests/api/Companies/GetIncludesCest.php deleted file mode 100644 index 83bc23e4..00000000 --- a/tests/api/Companies/GetIncludesCest.php +++ /dev/null @@ -1,173 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - $company = $I->addCompanyRecord('com-a-'); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesRecordIncludesUrl, $company->get('id'), 'unknown')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($company), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getCompaniesWithIncludesAllIncludes(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::INDIVIDUALS, Relationships::PRODUCTS]); - } - - /** - * @param ApiTester $I - */ - public function getCompaniesWithIncludesIndividuals(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::INDIVIDUALS]); - } - - /** - * @param ApiTester $I - */ - public function getCompaniesWithIncludesProducts(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::PRODUCTS]); - } - - private function checkIncludes(ApiTester $I, array $includes = []) - { - list($com, $prdOne, $prdTwo, $indOne, $indTwo) = $this->addRecords($I); - - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$companiesRecordIncludesUrl, - $com->get('id'), - implode(',', $includes) - ) - ); - - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - - $element = [ - 'type' => Relationships::COMPANIES, - 'id' => $com->get('id'), - 'attributes' => [ - 'name' => $com->get('name'), - 'address' => $com->get('address'), - 'city' => $com->get('city'), - 'phone' => $com->get('phone'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id') - ), - ], - ]; - - $included = []; - foreach ($includes as $include) { - if (Relationships::INDIVIDUALS === $include) { - $element['relationships'][Relationships::INDIVIDUALS] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::INDIVIDUALS - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::INDIVIDUALS - ), - ], - 'data' => [ - [ - 'type' => Relationships::INDIVIDUALS, - 'id' => $indOne->get('id'), - ], - [ - 'type' => Relationships::INDIVIDUALS, - 'id' => $indTwo->get('id'), - ], - ], - ]; - - $included[] = Data::individualResponse($indOne); - $included[] = Data::individualResponse($indTwo); - } - - if (Relationships::PRODUCTS === $include) { - $element['relationships'][Relationships::PRODUCTS] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::PRODUCTS - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::COMPANIES, - $com->get('id'), - Relationships::PRODUCTS - ), - ], - 'data' => [ - [ - 'type' => Relationships::PRODUCTS, - 'id' => $prdOne->get('id'), - ], - [ - 'type' => Relationships::PRODUCTS, - 'id' => $prdTwo->get('id'), - ], - ], - ]; - - $included[] = Data::productResponse($prdOne); - $included[] = Data::productResponse($prdTwo); - } - } - - $I->seeSuccessJsonResponse('data', [$element]); - - if (count($included) > 0) { - $I->seeSuccessJsonResponse('included', $included); - } - } -} diff --git a/tests/api/Companies/GetSortCest.php b/tests/api/Companies/GetSortCest.php deleted file mode 100644 index cf8558e2..00000000 --- a/tests/api/Companies/GetSortCest.php +++ /dev/null @@ -1,110 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $comOne */ - $comOne = $I->addCompanyRecord('com-a-'); - /** @var Companies $comTwo */ - $comTwo = $I->addCompanyRecord('com-b-'); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesSortUrl, 'name')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($comOne), - Data::companiesResponse($comTwo), - ] - ); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesSortUrl, '-name')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($comTwo), - Data::companiesResponse($comOne), - ] - ); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getCompaniesMultipleSort(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $comOne */ - $comOne = $I->addCompanyRecord('com-a-', '', 'city-b'); - /** @var Companies $comTwo */ - $comTwo = $I->addCompanyRecord('com-b-', '', 'city-b'); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesSortUrl, 'city,name')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($comOne), - Data::companiesResponse($comTwo), - ] - ); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesSortUrl, 'city,-name')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::companiesResponse($comTwo), - Data::companiesResponse($comOne), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getCompaniesInvalidSort(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $comOne */ - $I->addCompanyRecord('com-a-', '', 'city-b'); - /** @var Companies $comTwo */ - $I->addCompanyRecord('com-b-', '', 'city-b'); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$companiesSortUrl, 'unknown')); - $I->deleteHeader('Authorization'); - $I->seeResponseIs400(); - } -} diff --git a/tests/api/IndividualTypes/GetCest.php b/tests/api/IndividualTypes/GetCest.php deleted file mode 100644 index df876e76..00000000 --- a/tests/api/IndividualTypes/GetCest.php +++ /dev/null @@ -1,158 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - $typeOne = $I->addIndividualTypeRecord('type-a-'); - $typeTwo = $I->addIndividualTypeRecord('type-b-'); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$individualTypesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::individualTypeResponse($typeOne), - Data::individualTypeResponse($typeTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getUnknownIndividualTypes(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$individualTypesRecordUrl, 1)); - $I->deleteHeader('Authorization'); - $I->seeResponseIs404(); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getIndividualTypesWithIncludesIndividuals(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var $company */ - $company = $I->addCompanyRecord('com-a'); - /** @var IndividualTypes $individualType */ - $individualType = $I->addIndividualTypeRecord('type-a-'); - /** @var Individuals $individualOne */ - $individualOne = $I->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); - /** @var Individuals $individualTwo */ - $individualTwo = $I->addIndividualRecord('prd-b-', $company->get('id'), $individualType->get('id')); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$individualTypesRecordIncludesUrl, - $individualType->get('id'), - Relationships::INDIVIDUALS - ) - ); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - [ - 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => $individualType->get('id'), - 'attributes' => [ - 'name' => $individualType->get('name'), - 'description' => $individualType->get('description'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUAL_TYPES, - $individualType->get('id') - ), - ], - 'relationships' => [ - Relationships::INDIVIDUALS => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL'), - Relationships::INDIVIDUAL_TYPES, - $individualType->get('id'), - Relationships::INDIVIDUALS - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUAL_TYPES, - $individualType->get('id'), - Relationships::INDIVIDUALS - ), - ], - 'data' => [ - [ - 'type' => Relationships::INDIVIDUALS, - 'id' => $individualOne->get('id'), - ], - [ - 'type' => Relationships::INDIVIDUALS, - 'id' => $individualTwo->get('id'), - ], - ], - ], - ], - ], - ] - ); - - $I->seeSuccessJsonResponse( - 'included', - [ - Data::individualResponse($individualOne), - Data::individualResponse($individualTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getIndividualTypesNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$individualTypesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse(); - } -} diff --git a/tests/api/Individuals/GetCest.php b/tests/api/Individuals/GetCest.php deleted file mode 100644 index def81ee8..00000000 --- a/tests/api/Individuals/GetCest.php +++ /dev/null @@ -1,246 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $company */ - $company = $I->addCompanyRecord('com-a-'); - /** @var IndividualTypes $individualType */ - $individualType = $I->addIndividualTypeRecord('prt-a-'); - /** @var Individuals $individual */ - $individual = $I->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$individualsRecordUrl, $individual->get('id'))); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::individualResponse($individual), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getUnknownIndividual(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$individualsRecordUrl, 1)); - $I->deleteHeader('Authorization'); - $I->seeResponseIs404(); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getIndividuals(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var Companies $company */ - $company = $I->addCompanyRecord('com-a-'); - /** @var IndividualTypes $individualType */ - $individualType = $I->addIndividualTypeRecord('prt-a-'); - /** @var Individuals $individualOne */ - $individualOne = $I->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); - /** @var Individuals $individualTwo */ - $individualTwo = $I->addIndividualRecord('ind-b-', $company->get('id'), $individualType->get('id')); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$individualsUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::individualResponse($individualOne), - Data::individualResponse($individualTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getIndividualsWithIncludesAllIncludes(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES]); - } - - /** - * @param ApiTester $I - */ - public function getIndividualsWithIncludesCompanies(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::COMPANIES]); - } - - /** - * @param ApiTester $I - */ - public function getIndividualsWithRelationshipIndividualTypes(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::INDIVIDUAL_TYPES]); - } - - /** - * @param ApiTester $I - */ - public function getIndividualsNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$individualsUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse(); - } - - private function addRecords(ApiTester $I): array - { - /** @var Companies $company */ - $company = $I->addCompanyRecord('com-a'); - /** @var IndividualTypes $individualType */ - $individualType = $I->addIndividualTypeRecord('prt-a-'); - /** @var Individuals $individual */ - $individual = $I->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); - - - return [$individual, $individualType, $company]; - } - - private function checkIncludes(ApiTester $I, array $includes = []) - { - list($individual, $individualType, $company) = $this->addRecords($I); - - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$individualsRecordIncludesUrl, - $individual->get('id'), - implode(',', $includes) - ) - ); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - - $element = [ - 'type' => Relationships::INDIVIDUALS, - 'id' => $individual->get('id'), - 'attributes' => [ - 'companyId' => $individual->get('companyId'), - 'typeId' => $individual->get('typeId'), - 'prefix' => $individual->get('prefix'), - 'first' => $individual->get('first'), - 'middle' => $individual->get('middle'), - 'last' => $individual->get('last'), - 'suffix' => $individual->get('suffix'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::INDIVIDUALS, - $individual->get('id') - ), - ], - ]; - - $included = []; - foreach ($includes as $include) { - if (Relationships::COMPANIES === $include) { - $element['relationships'][Relationships::COMPANIES] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::COMPANIES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::COMPANIES - ), - ], - 'data' => [ - 'type' => Relationships::COMPANIES, - 'id' => $company->get('id'), - ], - ]; - - $included[] = Data::companiesResponse($company); - } - - if (Relationships::INDIVIDUAL_TYPES === $include) { - $element['relationships'][Relationships::INDIVIDUAL_TYPES] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::INDIVIDUAL_TYPES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::INDIVIDUAL_TYPES - ), - ], - 'data' => [ - 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => $individualType->get('id'), - ], - ]; - - $included[] = Data::individualTypeResponse($individualType); - } - } - - $I->seeSuccessJsonResponse('data', [$element]); - - if (count($included) > 0) { - $I->seeSuccessJsonResponse('included', $included); - } - } -} diff --git a/tests/api/LoginCest.php b/tests/api/LoginCest.php deleted file mode 100644 index 1f482f49..00000000 --- a/tests/api/LoginCest.php +++ /dev/null @@ -1,49 +0,0 @@ -sendPOST( - Data::$loginUrl, - [ - 'username' => 'user', - 'password' => 'pass', - ] - ); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Incorrect credentials'); - } - - public function loginKnownUser(ApiTester $I) - { - $I->haveRecordWithFields( - Users::class, - [ - 'status' => Flags::ACTIVE, - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'issuer' => Data::$testIssuer, - 'tokenPassword' => Data::$strongPassphrase, - 'tokenId' => Data::$testTokenId, - ] - ); - - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - $response = $I->grabResponse(); - $data = json_decode($response, true); - $I->assertTrue(isset($data['data'])); - $I->assertTrue(isset($data['data']['token'])); - $I->assertTrue(isset($data['meta'])); - } -} diff --git a/tests/api/NotFoundCest.php b/tests/api/NotFoundCest.php deleted file mode 100644 index fdbbcb60..00000000 --- a/tests/api/NotFoundCest.php +++ /dev/null @@ -1,15 +0,0 @@ -sendGET(Data::$wrongUrl); - $I->seeResponseIs404(); - } -} diff --git a/tests/api/ProductTypes/GetCest.php b/tests/api/ProductTypes/GetCest.php deleted file mode 100644 index 1b102c6c..00000000 --- a/tests/api/ProductTypes/GetCest.php +++ /dev/null @@ -1,157 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - $typeOne = $I->addProductTypeRecord('type-a-'); - $typeTwo = $I->addProductTypeRecord('type-b-'); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$productTypesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::productTypeResponse($typeOne), - Data::productTypeResponse($typeTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getUnknownProductTypes(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$productTypesRecordUrl, 1)); - $I->deleteHeader('Authorization'); - $I->seeResponseIs404(); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getProductTypesWithIncludesProducts(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var ProductTypes $productType */ - $productType = $I->addProductTypeRecord('type-a-'); - /** @var Products $productOne */ - $productOne = $I->addProductRecord('prd-a-', $productType->get('id')); - /** @var Products $productTwo */ - $productTwo = $I->addProductRecord('prd-b-', $productType->get('id')); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$productTypesRecordIncludesUrl, - $productType->get('id'), - Relationships::PRODUCTS - ) - ); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - - $I->seeSuccessJsonResponse( - 'data', - [ - [ - 'type' => Relationships::PRODUCT_TYPES, - 'id' => $productType->get('id'), - 'attributes' => [ - 'name' => $productType->get('name'), - 'description' => $productType->get('description'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCT_TYPES, - $productType->get('id') - ), - ], - 'relationships' => [ - Relationships::PRODUCTS => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL'), - Relationships::PRODUCT_TYPES, - $productType->get('id'), - Relationships::PRODUCTS - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCT_TYPES, - $productType->get('id'), - Relationships::PRODUCTS - ), - ], - 'data' => [ - [ - 'type' => Relationships::PRODUCTS, - 'id' => $productOne->get('id'), - ], - [ - 'type' => Relationships::PRODUCTS, - 'id' => $productTwo->get('id'), - ], - ], - ], - ], - ], - ] - ); - - $I->seeSuccessJsonResponse( - 'included', - [ - Data::productResponse($productOne), - Data::productResponse($productTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getProductTypesNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$productTypesUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse(); - } -} diff --git a/tests/api/Products/GetCest.php b/tests/api/Products/GetCest.php deleted file mode 100644 index b96bb9c7..00000000 --- a/tests/api/Products/GetCest.php +++ /dev/null @@ -1,250 +0,0 @@ -addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var ProductTypes $productType */ - $productType = $I->addProductTypeRecord('prt-a-'); - /** @var Products $product */ - $product = $I->addProductRecord('prd-a-', $productType->get('id')); - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$productsRecordUrl, $product->get('id'))); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::productResponse($product), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getUnknownProduct(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(sprintf(Data::$productsRecordUrl, 1)); - $I->deleteHeader('Authorization'); - $I->seeResponseIs404(); - } - - /** - * @param ApiTester $I - * - * @throws ModelException - */ - public function getProducts(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - /** @var ProductTypes $productType */ - $productType = $I->addProductTypeRecord('prt-a-'); - /** @var Products $productOne */ - $productOne = $I->addProductRecord('prd-a-', $productType->get('id')); - /** @var Products $productTwo */ - $productTwo = $I->addProductRecord('prd-b-', $productType->get('id')); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$productsUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::productResponse($productOne), - Data::productResponse($productTwo), - ] - ); - } - - /** - * @param ApiTester $I - */ - public function getProductsWithIncludesAllIncludes(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::COMPANIES, Relationships::PRODUCT_TYPES]); - } - - /** - * @param ApiTester $I - */ - public function getProductsWithIncludesCompanies(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::COMPANIES]); - } - - /** - * @param ApiTester $I - */ - public function getProductsWithIncludesProductTypes(ApiTester $I) - { - $this->checkIncludes($I, [Relationships::PRODUCT_TYPES]); - } - - /** - * @param ApiTester $I - */ - public function getProductsNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$productsUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse(); - } - - private function addRecords(ApiTester $I): array - { - /** @var Companies $comOne */ - $comOne = $I->addCompanyRecord('com-a'); - /** @var Companies $comTwo */ - $comTwo = $I->addCompanyRecord('com-b'); - /** @var ProductTypes $productType */ - $productType = $I->addProductTypeRecord('prt-a-'); - /** @var Products $product */ - $product = $I->addProductRecord('prd-a-', $productType->get('id')); - $I->addCompanyXProduct($comOne->get('id'), $product->get('id')); - $I->addCompanyXProduct($comTwo->get('id'), $product->get('id')); - - return [$product, $productType, $comOne, $comTwo]; - } - - private function checkIncludes(ApiTester $I, array $includes = []) - { - list($product, $productType, $comOne, $comTwo) = $this->addRecords($I); - - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET( - sprintf( - Data::$productsRecordIncludesUrl, - $product->get('id'), - implode(',', $includes) - ) - ); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - - $element = [ - 'type' => Relationships::PRODUCTS, - 'id' => $product->get('id'), - 'attributes' => [ - 'typeId' => $productType->get('id'), - 'name' => $product->get('name'), - 'description' => $product->get('description'), - 'quantity' => $product->get('quantity'), - 'price' => $product->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::PRODUCTS, - $product->get('id') - ), - ], - ]; - - $included = []; - foreach ($includes as $include) { - if (Relationships::COMPANIES === $include) { - $element['relationships'][Relationships::COMPANIES] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::PRODUCTS, - $product->get('id'), - Relationships::COMPANIES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::PRODUCTS, - $product->get('id'), - Relationships::COMPANIES - ), - ], - 'data' => [ - [ - 'type' => Relationships::COMPANIES, - 'id' => $comOne->get('id'), - ], - [ - 'type' => Relationships::COMPANIES, - 'id' => $comTwo->get('id'), - ], - ], - ]; - - $included[] = Data::companiesResponse($comOne); - $included[] = Data::companiesResponse($comTwo); - } - - if (Relationships::PRODUCT_TYPES === $include) { - $element['relationships'][Relationships::PRODUCT_TYPES] = [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - envValue('APP_URL', 'localhost'), - Relationships::PRODUCTS, - $product->get('id'), - Relationships::PRODUCT_TYPES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - envValue('APP_URL', 'localhost'), - Relationships::PRODUCTS, - $product->get('id'), - Relationships::PRODUCT_TYPES - ), - ], - 'data' => [ - 'type' => Relationships::PRODUCT_TYPES, - 'id' => $productType->get('id'), - ], - ]; - - $included[] = Data::productTypeResponse($productType); - } - } - - $I->seeSuccessJsonResponse('data', [$element]); - - if (count($included) > 0) { - $I->seeSuccessJsonResponse('included', $included); - } - } -} diff --git a/tests/api/Users/GetCest.php b/tests/api/Users/GetCest.php deleted file mode 100644 index 447cc9be..00000000 --- a/tests/api/Users/GetCest.php +++ /dev/null @@ -1,237 +0,0 @@ -deleteHeader('Authorization'); - $I->sendGET(Data::$usersUrl . '/1'); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Invalid Token'); - } - - public function loginKnownUserGetUnknownUser(ApiTester $I) - { - $I->addApiUserRecord(); - $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - - $response = $I->grabResponse(); - $response = json_decode($response, true); - $data = $response['data']; - $token = $data['token']; - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$usersUrl . '/1'); - $I->seeResponseIs404(); - } - - public function loginKnownUserIncorrectSignature(ApiTester $I) - { - $record = $I->addApiUserRecord(); - $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - - $signer = new Hmac(); - $builder = new Builder($signer); - - $token = $builder - ->setIssuer(Data::$testIssuer) - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setIssuedAt(time() - 3600) - ->setNotBefore(time() - 3590) - ->setExpirationTime(time() + 3000) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - $wrongToken = $token->getToken(); - $parts = explode('.', $wrongToken); - $wrongToken = $parts[0] . '.' . $parts[1] . '.'; - - $I->haveHttpHeader('Authorization', 'Bearer ' . $wrongToken); - $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Invalid Token (verification)'); - } - - public function loginKnownUserExpiredToken(ApiTester $I) - { - $record = $I->addApiUserRecord(); - $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - - $signer = new Hmac(); - $builder = new Builder($signer); - - $token = $builder - ->setIssuer(Data::$testIssuer) - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setIssuedAt(time() - 3600) - ->setNotBefore(time() - 3590) - ->setExpirationTime(time()) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - usleep(1000000); - $expiredToken = $token->getToken(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $expiredToken); - $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Invalid Token (verification)'); - } - - public function loginKnownUserInvalidToken(ApiTester $I) - { - $record = $I->addApiUserRecord(); - $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - - $signer = new Hmac(); - $builder = new Builder($signer); - - $token = $builder - ->setIssuer(Data::$testIssuer) - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setIssuedAt(time()) - ->setNotBefore(time()) - ->setExpirationTime(time() + 3000) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - $invalidToken = $token->getToken() . 'xx'; - - $I->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); - $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Invalid Token (verification)'); - } - - public function loginKnownUserInvalidUserInToken(ApiTester $I) - { - $record = $I->addApiUserRecord(); - $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$loginUrl, Data::loginJson()); - $I->seeResponseIsSuccessful(); - - $signer = new Hmac(); - $builder = new Builder($signer); - - $token = $builder - ->setIssuer(Data::$testIssuer) - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setIssuedAt(time() - 3600) - ->setNotBefore(time() - 3590) - ->setExpirationTime(time() + 3000) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - $invalidToken = $token->getToken(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); - $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Invalid Token (verification)'); - } - - public function loginKnownUserCorrectToken(ApiTester $I) - { - $I->addApiUserRecord(); - $I->apiLogin(); - } - - public function loginKnownUserValidToken(ApiTester $I) - { - $record = $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::userResponse($record), - ] - ); - } - - public function getManyUsers(ApiTester $I) - { - $userOne = $I->haveRecordWithFields( - Users::class, - [ - 'status' => 1, - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'issuer' => Data::$testIssuer, - 'tokenPassword' => Data::$strongPassphrase, - 'tokenId' => Data::$testTokenId, - ] - ); - - $userTwo = $I->haveRecordWithFields( - Users::class, - [ - 'status' => 1, - 'username' => Data::$testUsername . '1', - 'password' => Data::$testPassword . '1', - 'issuer' => Data::$testIssuer . '1', - 'tokenPassword' => Data::$strongPassphrase . '1', - 'tokenId' => Data::$testTokenId . '1', - ] - ); - - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$usersUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - 'data', - [ - Data::userResponse($userOne), - Data::userResponse($userTwo), - ] - ); - } - - public function getManyUsersWithNoData(ApiTester $I) - { - $I->addApiUserRecord(); - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendGET(Data::$usersUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - } -} diff --git a/tests/api/_bootstrap.php b/tests/api/_bootstrap.php deleted file mode 100644 index 62817a53..00000000 --- a/tests/api/_bootstrap.php +++ /dev/null @@ -1,3 +0,0 @@ -runShellCommand('./runCli'); - $I->seeResultCodeIs(0); - $I->seeInShellOutput('--help'); - $I->seeInShellOutput('--clear-cache'); - } -} diff --git a/tests/cli/_bootstrap.php b/tests/cli/_bootstrap.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/tests/cli/_bootstrap.php +++ /dev/null @@ -1 +0,0 @@ -haveRecordWithFields( - Users::class, - [ - 'username' => 'testusername', - 'password' => 'testpass', - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenPassword' => '12345', - 'tokenId' => '00110011', - ] - ); - } - - /** - * Tests the model by setting a non existent field - * - * @param IntegrationTester $I - */ - public function modelSetNonExistingFields(IntegrationTester $I) - { - $I->expectThrowable( - ModelException::class, - function () { - $fixture = new Users(); - $fixture->set('id', 1000) - ->set('some_field', true) - ->save() - ; - } - ); - } - - /** - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function modelGetNonExistingFields(IntegrationTester $I) - { - /** @var Users $result */ - $user = $I->haveRecordWithFields( - Users::class, - [ - 'username' => 'testusername', - 'password' => 'testpass', - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenPassword' => '12345', - 'tokenId' => '00110011', - ] - ); - - $I->expectThrowable( - ModelException::class, - function () use ($user) { - $user->get('some_field'); - } - ); - } - - /** - * Tests the model update interactions - * - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function modelUpdateFields(IntegrationTester $I) - { - /** @var Users $result */ - $user = $I->haveRecordWithFields( - Users::class, - [ - 'username' => 'testusername', - 'password' => 'testpass', - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenPassword' => '12345', - 'tokenId' => '00110011', - ] - ); - - $user->set('username', 'testusername') - ->save() - ; - - $I->assertSame($user->get('username'), 'testusername'); - $I->assertSame($user->get('password'), 'testpass'); - $I->assertSame($user->get('issuer'), 'phalcon.io'); - $I->assertSame($user->get('tokenPassword'), '12345'); - $I->assertSame($user->get('tokenId'), '00110011'); - } - - /** - * @param IntegrationTester $I - */ - public function modelUpdateFieldsNotSanitized(IntegrationTester $I) - { - /** @var Users $result */ - $user = $I->haveRecordWithFields( - Users::class, - [ - 'username' => 'testusername', - 'password' => 'testpass', - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenPassword' => '12345', - 'tokenId' => '00110011', - - ] - ); - - $user->set('password', 'abcde\nfg') - ->save() - ; - $I->assertSame($user->get('password'), 'abcde\nfg'); - - /** Not sanitized */ - $user->set('password', 'abcde\nfg', false) - ->save() - ; - $I->assertSame($user->get('password'), 'abcde\nfg'); - } - - /** - * @param IntegrationTester $I - */ - public function checkModelMessages(IntegrationTester $I) - { - $user = Stub::construct( - Users::class, - [], - [ - 'save' => false, - 'getMessages' => [ - new Message('error 1'), - new Message('error 2'), - ], - ] - ); - - $result = $user - ->set('username', 'test') - ->save() - ; - $I->assertFalse($result); - - $I->assertSame('error 1
error 2
', $user->getModelMessages()); - } - - /** - * @param IntegrationTester $I - * - * @throws Exception - */ - public function checkModelMessagesWithLogger(IntegrationTester $I) - { - /** @var Logger $logger */ - $logger = $I->grabFromDi('logger'); - $user = Stub::construct( - Users::class, - [], - [ - 'save' => false, - 'getMessages' => [ - new Message('error 1'), - new Message('error 2'), - ], - ] - ); - - $fileName = appPath('storage/logs/api.log'); - $result = $user - ->set('username', 'test') - ->save() - ; - $I->assertFalse($result); - $I->assertSame('error 1
error 2
', $user->getModelMessages()); - - $user->getModelMessages($logger); - - $I->openFile($fileName); - $I->seeInThisFile("error 1\n"); - $I->seeInThisFile("error 2\n"); - } -} diff --git a/tests/integration/library/Models/CompaniesCest.php b/tests/integration/library/Models/CompaniesCest.php deleted file mode 100644 index 89d3e65b..00000000 --- a/tests/integration/library/Models/CompaniesCest.php +++ /dev/null @@ -1,119 +0,0 @@ -haveModelDefinition( - Companies::class, - [ - 'id', - 'name', - 'address', - 'city', - 'phone', - ] - ); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) - { - $model = new Companies(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'address' => Filter::FILTER_STRING, - 'city' => Filter::FILTER_STRING, - 'phone' => Filter::FILTER_STRING, - ]; - $I->assertSame($expected, $model->getModelFilters()); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) - { - $actual = $I->getModelRelationships(Companies::class); - $expected = [ - [ - 2, - 'id', - Individuals::class, - 'companyId', - ['alias' => Relationships::INDIVIDUALS, 'reusable' => true] - ], - [ - 4, - 'id', - Products::class, - 'id', - ['alias' => Relationships::PRODUCTS, 'reusable' => true] - ], - ]; - - $I->assertSame($expected, $actual); - } - - /** - * @param IntegrationTester $I - * - * @return void - * @throws ModelException - */ - public function validateUniqueName(IntegrationTester $I) - { - $companyOne = new Companies(); - /** @var Companies $companyOne */ - $result = $companyOne - ->set('name', 'acme') - ->set('address', '123 Phalcon way') - ->set('city', 'World') - ->set('phone', '555-999-4444') - ->save() - ; - $I->assertNotEquals(false, $result); - - $companyTwo = new Companies(); - /** @var Companies $companyTwo */ - $result = $companyTwo - ->set('name', 'acme') - ->set('address', '123 Phalcon way') - ->set('city', 'World') - ->set('phone', '555-999-4444') - ->save() - ; - $I->assertSame(false, $result); - $I->assertSame(1, count($companyTwo->getMessages())); - - $messages = $companyTwo->getMessages(); - $expected = 'The company name already exists in the database'; - $actual = $messages[0]->getMessage(); - $I->assertSame($expected, $actual); - - $result = $companyOne->delete(); - $I->assertNotEquals(false, $result); - } -} diff --git a/tests/integration/library/Models/CompaniesXProductsCest.php b/tests/integration/library/Models/CompaniesXProductsCest.php deleted file mode 100644 index 6c817d0c..00000000 --- a/tests/integration/library/Models/CompaniesXProductsCest.php +++ /dev/null @@ -1,71 +0,0 @@ -haveModelDefinition( - CompaniesXProducts::class, - [ - 'companyId', - 'productId', - ] - ); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) - { - $model = new CompaniesXProducts(); - $expected = [ - 'companyId' => Filter::FILTER_ABSINT, - 'productId' => Filter::FILTER_ABSINT, - ]; - $I->assertSame($expected, $model->getModelFilters()); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) - { - $actual = $I->getModelRelationships(CompaniesXProducts::class); - $expected = [ - [ - 0, - 'companyId', - Companies::class, - 'id', - ['alias' => Relationships::COMPANIES, 'reusable' => true] - ], - [ - 0, - 'productId', - Products::class, - 'id', - ['alias' => Relationships::PRODUCTS, 'reusable' => true] - ], - ]; - $I->assertSame($expected, $actual); - } -} diff --git a/tests/integration/library/Models/IndividualTypesCest.php b/tests/integration/library/Models/IndividualTypesCest.php deleted file mode 100644 index 1deaae78..00000000 --- a/tests/integration/library/Models/IndividualTypesCest.php +++ /dev/null @@ -1,65 +0,0 @@ -haveModelDefinition( - IndividualTypes::class, - [ - 'id', - 'name', - 'description', - ] - ); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) - { - $model = new IndividualTypes(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - $I->assertSame($expected, $model->getModelFilters()); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) - { - $actual = $I->getModelRelationships(IndividualTypes::class); - $expected = [ - [ - 2, - 'id', - Individuals::class, - 'typeId', - ['alias' => Relationships::INDIVIDUALS, 'reusable' => true] - ], - ]; - $I->assertSame($expected, $actual); - } -} diff --git a/tests/integration/library/Models/ProductTypesCest.php b/tests/integration/library/Models/ProductTypesCest.php deleted file mode 100644 index c425be88..00000000 --- a/tests/integration/library/Models/ProductTypesCest.php +++ /dev/null @@ -1,65 +0,0 @@ -haveModelDefinition( - ProductTypes::class, - [ - 'id', - 'name', - 'description', - ] - ); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateFilters(IntegrationTester $I) - { - $model = new ProductTypes(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - $I->assertSame($expected, $model->getModelFilters()); - } - - /** - * @param IntegrationTester $I - * - * @return void - */ - public function validateRelationships(IntegrationTester $I) - { - $actual = $I->getModelRelationships(ProductTypes::class); - $expected = [ - [ - 2, - 'id', - Products::class, - 'typeId', - ['alias' => Relationships::PRODUCTS, 'reusable' => true] - ], - ]; - $I->assertSame($expected, $actual); - } -} diff --git a/tests/integration/library/Traits/QueryCest.php b/tests/integration/library/Traits/QueryCest.php deleted file mode 100644 index cec2ee92..00000000 --- a/tests/integration/library/Traits/QueryCest.php +++ /dev/null @@ -1,184 +0,0 @@ -haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - /** @var Cache $cache */ - $cache = $I->grabFromDi('cache'); - /** @var Config $config */ - $config = $I->grabFromDi('config'); - $dbUser = $this->getUserByUsernameAndPassword( - $config, - $cache, - Data::$testUsername, - Data::$testPassword - ); - - $I->assertNotNull($dbUser); - } - - /** - * @param IntegrationTester $I - * - */ - public function checkGetUserByWrongUsernameAndPasswordReturnsNull(IntegrationTester $I) - { - /** @var Users $result */ - $I->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - /** @var Cache $cache */ - $cache = $I->grabFromDi('cache'); - /** @var Config $config */ - $config = $I->grabFromDi('config'); - $dbUser = $this->getUserByUsernameAndPassword( - $config, - $cache, - Data::$testUsername, - 'nothing' - ); - - $I->assertNull($dbUser); - } - - /** - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function checkGetUserByWrongTokenReturnsNull(IntegrationTester $I) - { - /** @var Users $result */ - $I->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - $signer = new Hmac(); - $builder = new Builder($signer); - $token = $builder - ->setIssuer('https://somedomain.com') - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - /** @var Cache $cache */ - $cache = $I->grabFromDi('cache'); - /** @var Config $config */ - $config = $I->grabFromDi('config'); - $actual = $this->getUserByToken($config, $cache, $token); - - $I->assertNull($actual); - } - - public function getCompaniesCachedData(IntegrationTester $I) - { - $configData = require appPath('./library/Core/config.php'); - $I->assertTrue($configData['app']['devMode']); - - $configData['app']['devMode'] = false; - /** @var Config $config */ - $config = new Config($configData); - $container = $I->grabDi(); - $container->set('config', $config); - $I->assertFalse($config->path('app.devMode')); - - /** @var Cache $cache */ - $cache = $I->grabFromDi('cache'); - $cache->clear(); - /** @var Config $config */ - $config = $I->grabFromDi('config'); - $I->assertFalse($config->path('app.devMode')); - - /** - * Company 1 - */ - $comName = uniqid('com-cached-'); - $comOne = $I->haveRecordWithFields( - Companies::class, - [ - 'name' => $comName, - 'address' => uniqid(), - 'city' => uniqid(), - 'phone' => uniqid(), - ] - ); - - $results = $this->getRecords($config, $cache, Companies::class); - $I->assertSame(1, count($results)); - $I->assertSame($comName, $results[0]->get('name')); - $I->assertSame($comOne->get('address'), $results[0]->get('address')); - $I->assertSame($comOne->get('city'), $results[0]->get('city')); - $I->assertSame($comOne->get('phone'), $results[0]->get('phone')); - - /** - * Get the record again but ensure the name has been changed - */ - $result = $comOne->set('name', 'com-cached-change') - ->save() - ; - $I->assertNotEquals(false, $result); - - /** - * This should return the cached result - */ - $results = $this->getRecords($config, $cache, Companies::class); - $I->assertSame(1, count($results)); - $I->assertSame($comName, $results[0]->get('name')); - $I->assertSame($comOne->get('address'), $results[0]->get('address')); - $I->assertSame($comOne->get('city'), $results[0]->get('city')); - $I->assertSame($comOne->get('phone'), $results[0]->get('phone')); - } -} diff --git a/tests/integration/library/Transformers/BaseTransformerCest.php b/tests/integration/library/Transformers/BaseTransformerCest.php deleted file mode 100644 index 48e8a685..00000000 --- a/tests/integration/library/Transformers/BaseTransformerCest.php +++ /dev/null @@ -1,41 +0,0 @@ -haveRecordWithFields( - Companies::class, - [ - 'name' => 'acme', - 'address' => '123 Phalcon way', - 'city' => 'World', - 'phone' => '555-999-4444', - ] - ); - - $transformer = new BaseTransformer(); - $expected = [ - 'id' => $company->get('id'), - 'name' => $company->get('name'), - 'address' => $company->get('address'), - 'city' => $company->get('city'), - 'phone' => $company->get('phone'), - ]; - - $I->assertSame($expected, $transformer->transform($company)); - } -} diff --git a/tests/integration/library/Transformers/IndividualsTransformerCest.php b/tests/integration/library/Transformers/IndividualsTransformerCest.php deleted file mode 100644 index 647e83a4..00000000 --- a/tests/integration/library/Transformers/IndividualsTransformerCest.php +++ /dev/null @@ -1,149 +0,0 @@ -haveRecordWithFields( - Companies::class, - [ - 'name' => uniqid('com-a-'), - 'address' => uniqid(), - 'city' => uniqid(), - 'phone' => uniqid(), - ] - ); - - /** @var IndividualTypes $individualType */ - $individualType = $I->haveRecordWithFields( - IndividualTypes::class, - [ - 'name' => 'my type', - 'description' => 'description of my type', - ] - ); - - /** @var Individuals $individual */ - $individual = $I->haveRecordWithFields( - Individuals::class, - [ - 'companyId' => $company->get('id'), - 'typeId' => $individualType->get('id'), - 'prefix' => uniqid(), - 'first' => uniqid('first-'), - 'middle' => uniqid(), - 'last' => uniqid('last-'), - 'suffix' => uniqid(), - ] - ); - - $url = envValue('APP_URL', 'http://localhost'); - $manager = new Manager(); - $manager->setSerializer(new JsonApiSerializer($url)); - $manager->parseIncludes([Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES]); - $resource = new Collection([$individual], new IndividualsTransformer(), Relationships::INDIVIDUALS); - $results = $manager->createData($resource) - ->toArray() - ; - $expected = [ - 'data' => [ - [ - 'type' => Relationships::INDIVIDUALS, - 'id' => (string) $individual->get('id'), - 'attributes' => [ - 'companyId' => $individual->get('companyId'), - 'typeId' => $individual->get('typeId'), - 'prefix' => $individual->get('prefix'), - 'first' => $individual->get('first'), - 'middle' => $individual->get('middle'), - 'last' => $individual->get('last'), - 'suffix' => $individual->get('suffix'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - $url, - Relationships::INDIVIDUALS, - $individual->get('id') - ), - ], - 'relationships' => [ - Relationships::COMPANIES => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - $url, - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::COMPANIES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - $url, - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::COMPANIES - ), - ], - 'data' => [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $company->get('id'), - ], - ], - Relationships::INDIVIDUAL_TYPES => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - $url, - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::INDIVIDUAL_TYPES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - $url, - Relationships::INDIVIDUALS, - $individual->get('id'), - Relationships::INDIVIDUAL_TYPES - ), - ], - 'data' => [ - 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => (string) $individualType->get('id'), - ], - ], - ], - ], - ], - 'included' => [ - Data::companiesResponse($company), - Data::individualTypeResponse($individualType), - ], - ]; - - $I->assertSame($expected, $results); - } -} diff --git a/tests/integration/library/Transformers/ProductsTransformerCest.php b/tests/integration/library/Transformers/ProductsTransformerCest.php deleted file mode 100644 index e27a6e7a..00000000 --- a/tests/integration/library/Transformers/ProductsTransformerCest.php +++ /dev/null @@ -1,156 +0,0 @@ -haveRecordWithFields( - Companies::class, - [ - 'name' => uniqid('com-a-'), - 'address' => uniqid(), - 'city' => uniqid(), - 'phone' => uniqid(), - ] - ); - - /** @var ProductTypes $productType */ - $productType = $I->haveRecordWithFields( - ProductTypes::class, - [ - 'name' => 'my type', - 'description' => 'description of my type', - ] - ); - - /** @var Products $product */ - $product = $I->haveRecordWithFields( - Products::class, - [ - 'name' => 'my product', - 'typeId' => $productType->get('id'), - 'description' => 'my product description', - 'quantity' => 99, - 'price' => 19.99, - ] - ); - - /** @var CompaniesXProducts $glue */ - $glue = $I->haveRecordWithFields( - CompaniesXProducts::class, - [ - 'companyId' => $company->get('id'), - 'productId' => $product->get('id'), - ] - ); - - $url = envValue('APP_URL', 'http://localhost'); - $manager = new Manager(); - $manager->setSerializer(new JsonApiSerializer($url)); - $manager->parseIncludes([Relationships::COMPANIES, Relationships::PRODUCT_TYPES]); - $resource = new Collection([$product], new ProductsTransformer(), Relationships::PRODUCTS); - $results = $manager->createData($resource) - ->toArray() - ; - $expected = [ - 'data' => [ - [ - 'type' => Relationships::PRODUCTS, - 'id' => (string) $product->get('id'), - 'attributes' => [ - 'typeId' => $productType->get('id'), - 'name' => $product->get('name'), - 'description' => $product->get('description'), - 'quantity' => $product->get('quantity'), - 'price' => $product->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - $url, - Relationships::PRODUCTS, - $product->get('id') - ), - ], - 'relationships' => [ - Relationships::COMPANIES => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - $url, - Relationships::PRODUCTS, - $product->get('id'), - Relationships::COMPANIES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - $url, - Relationships::PRODUCTS, - $product->get('id'), - Relationships::COMPANIES - ), - ], - 'data' => [ - [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $company->get('id'), - ], - ], - ], - Relationships::PRODUCT_TYPES => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/%s', - $url, - Relationships::PRODUCTS, - $product->get('id'), - Relationships::PRODUCT_TYPES - ), - 'related' => sprintf( - '%s/%s/%s/%s', - $url, - Relationships::PRODUCTS, - $product->get('id'), - Relationships::PRODUCT_TYPES - ), - ], - 'data' => [ - 'type' => Relationships::PRODUCT_TYPES, - 'id' => (string) $productType->get('id'), - ], - ], - ], - ], - ], - 'included' => [ - Data::companiesResponse($company), - Data::productTypeResponse($productType), - ], - ]; - - $I->assertEquals($expected, $results); - } -} diff --git a/tests/integration/library/Validation/CompaniesValidatorCest.php b/tests/integration/library/Validation/CompaniesValidatorCest.php deleted file mode 100644 index 9b5c2720..00000000 --- a/tests/integration/library/Validation/CompaniesValidatorCest.php +++ /dev/null @@ -1,26 +0,0 @@ - '', - 'address' => '123 Phalcon way', - 'city' => 'World', - 'phone' => '555-999-4444', - ]; - $messages = $validation->validate($_POST); - $I->assertSame(1, count($messages)); - $I->assertSame('The company name is required', $messages[0]->getMessage()); - } -} diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml deleted file mode 100644 index 7ae98b2e..00000000 --- a/tests/unit.suite.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for unit or integration tests. - -actor: UnitTester -modules: - enabled: - - Asserts - - Filesystem - - Helper\Unit diff --git a/tests/unit/BootstrapCest.php b/tests/unit/BootstrapCest.php deleted file mode 100644 index d0e0f39a..00000000 --- a/tests/unit/BootstrapCest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertSame('1.0', $results['jsonapi']['version']); - $I->assertTrue(empty($results['data'])); - $I->assertSame(HttpCode::getDescription(404), $results['errors'][0]); - } -} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php deleted file mode 100644 index 62817a53..00000000 --- a/tests/unit/_bootstrap.php +++ /dev/null @@ -1,3 +0,0 @@ -setDI($container); - - ob_start(); - $task->mainAction(); - $actual = ob_get_contents(); - ob_end_clean(); - - $year = date('Y'); - $expected = "" - . "******************************************************" . PHP_EOL - . " Phalcon Team | (C) {$year}" . PHP_EOL - . "******************************************************" . PHP_EOL - . "" . PHP_EOL - . "Usage: runCli " . PHP_EOL - . "" . PHP_EOL - . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL - . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL - . PHP_EOL; - - $I->assertSame($expected, $actual); - } -} diff --git a/tests/unit/cli/BootstrapCest.php b/tests/unit/cli/BootstrapCest.php deleted file mode 100644 index a3cdc410..00000000 --- a/tests/unit/cli/BootstrapCest.php +++ /dev/null @@ -1,34 +0,0 @@ -" . PHP_EOL - . "" . PHP_EOL - . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL - . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL - . PHP_EOL; - - $I->assertSame($expected, $actual); - } -} diff --git a/tests/unit/cli/ClearCacheCest.php b/tests/unit/cli/ClearCacheCest.php deleted file mode 100755 index 2326de6e..00000000 --- a/tests/unit/cli/ClearCacheCest.php +++ /dev/null @@ -1,65 +0,0 @@ -register($container); - $cache = new CacheDataProvider(); - $cache->register($container); - $task = new ClearcacheTask(); - $task->setDI($container); - - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); - $count = iterator_count($iterator); - - $this->createFile(); - $this->createFile(); - $this->createFile(); - $this->createFile(); - - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); - $I->assertSame($count + 4, iterator_count($iterator)); - - ob_start(); - $task->mainAction(); - $actual = ob_get_contents(); - ob_end_clean(); - - $I->assertGreaterOrEquals(0, strpos($actual, 'Clearing Cache folders')); - $I->assertGreaterOrEquals(0, strpos($actual, 'Cleared Cache folders')); - - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); - $I->assertSame(1, iterator_count($iterator)); - } - - private function createFile() - { - $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; - $pointer = fopen($name, 'wb'); - fwrite($pointer, 'test'); - fclose($pointer); - } -} diff --git a/tests/unit/config/AutoloaderCest.php b/tests/unit/config/AutoloaderCest.php deleted file mode 100755 index 998f7ca0..00000000 --- a/tests/unit/config/AutoloaderCest.php +++ /dev/null @@ -1,50 +0,0 @@ -assertNotEquals(false, $_ENV['APP_DEBUG']); - $I->assertNotEquals(false, $_ENV['APP_ENV']); - $I->assertNotEquals(false, $_ENV['APP_URL']); - $I->assertNotEquals(false, $_ENV['APP_NAME']); - $I->assertNotEquals(false, $_ENV['APP_BASE_URI']); - $I->assertNotEquals(false, $_ENV['APP_SUPPORT_EMAIL']); - $I->assertNotEquals(false, $_ENV['APP_TIMEZONE']); - $I->assertNotEquals(false, $_ENV['CACHE_PREFIX']); - $I->assertNotEquals(false, $_ENV['CACHE_LIFETIME']); - $I->assertNotEquals(false, $_ENV['DATA_API_MYSQL_NAME']); - $I->assertNotEquals(false, $_ENV['LOGGER_DEFAULT_FILENAME']); - $I->assertNotEquals(false, $_ENV['VERSION']); - - $I->assertSame('true', $_ENV['APP_DEBUG']); - $I->assertSame('development', $_ENV['APP_ENV']); - $I->assertSame('http://api.phalcon.ld', $_ENV['APP_URL']); - $I->assertSame('/', $_ENV['APP_BASE_URI']); - $I->assertSame('team@phalcon.io', $_ENV['APP_SUPPORT_EMAIL']); - $I->assertSame('UTC', $_ENV['APP_TIMEZONE']); - $I->assertSame('api_cache_', $_ENV['CACHE_PREFIX']); - $I->assertSame('86400', $_ENV['CACHE_LIFETIME']); - $I->assertSame('api', $_ENV['LOGGER_DEFAULT_FILENAME']); - $I->assertSame('20180401', $_ENV['VERSION']); - } - - public function checkAutoloader(UnitTester $I) - { - require appPath('library/Core/autoload.php'); - - $class = new Response(); - $I->assertTrue($class instanceof Response); - $I->assertTrue(function_exists('Phalcon\Api\Core\envValue')); - } -} diff --git a/tests/unit/config/ConfigCest.php b/tests/unit/config/ConfigCest.php deleted file mode 100755 index 5cec3af1..00000000 --- a/tests/unit/config/ConfigCest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertTrue(is_array($config)); - $I->assertTrue(isset($config['app'])); - $I->assertTrue(isset($config['cache'])); - } -} diff --git a/tests/unit/config/FunctionsCest.php b/tests/unit/config/FunctionsCest.php deleted file mode 100755 index 0dde1e6c..00000000 --- a/tests/unit/config/FunctionsCest.php +++ /dev/null @@ -1,103 +0,0 @@ -store = $_ENV ?? []; - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function __after(UnitTester $I) - { - $_ENV = $this->store; - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkApppath(UnitTester $I) - { - $path = dirname(dirname(dirname(__DIR__))); - $I->assertSame($path, appPath()); - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkApppathWithParameter(UnitTester $I) - { - $path = dirname(dirname(dirname(__DIR__))) . '/library/Core/config.php'; - $I->assertSame($path, appPath('library/Core/config.php')); - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkEnvvalueAsFalse(UnitTester $I) - { - $_ENV['SOMEVAL'] = false; - $I->assertFalse(envValue('SOMEVAL')); - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkEnvvalueAsTrue(UnitTester $I) - { - $_ENV['SOMEVAL'] = true; - $I->assertTrue(envValue('SOMEVAL')); - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkEnvvalueWithValue(UnitTester $I) - { - $_ENV['SOMEVAL'] = 'someval'; - $I->assertSame('someval', envValue('SOMEVAL')); - } - - /** - * @param UnitTester $I - * - * @return void - */ - public function checkEnvurlWithUrl(UnitTester $I) - { - $I->assertSame( - 'http://api.phalcon.ld/companies/1', - appUrl(Relationships::COMPANIES, 1) - ); - } -} diff --git a/tests/unit/config/ProvidersCest.php b/tests/unit/config/ProvidersCest.php deleted file mode 100755 index 401b6e12..00000000 --- a/tests/unit/config/ProvidersCest.php +++ /dev/null @@ -1,45 +0,0 @@ -assertSame(ConfigProvider::class, $providers[0]); - $I->assertSame(LoggerProvider::class, $providers[1]); - $I->assertSame(ErrorHandlerProvider::class, $providers[2]); - $I->assertSame(DatabaseProvider::class, $providers[3]); - $I->assertSame(ModelsMetadataProvider::class, $providers[4]); - $I->assertSame(RequestProvider::class, $providers[5]); - $I->assertSame(ResponseProvider::class, $providers[6]); - $I->assertSame(RouterProvider::class, $providers[7]); - } - - public function checkCliProviders(UnitTester $I) - { - $providers = require(appPath('cli/config/providers.php')); - - $I->assertSame(ConfigProvider::class, $providers[0]); - $I->assertSame(LoggerProvider::class, $providers[1]); - $I->assertSame(ErrorHandlerProvider::class, $providers[2]); - $I->assertSame(DatabaseProvider::class, $providers[3]); - $I->assertSame(ModelsMetadataProvider::class, $providers[4]); - $I->assertSame(CliDispatcherProvider::class, $providers[5]); - } -} diff --git a/tests/unit/library/BootstrapCest.php b/tests/unit/library/BootstrapCest.php deleted file mode 100755 index 0c155ff8..00000000 --- a/tests/unit/library/BootstrapCest.php +++ /dev/null @@ -1,21 +0,0 @@ -assertTrue($bootstrap->getContainer() instanceof FactoryDefault); - $I->assertTrue($bootstrap->getResponse() instanceof Response); - $I->assertTrue($bootstrap->getApplication() instanceof Micro); - } -} diff --git a/tests/unit/library/Constants/FlagsCest.php b/tests/unit/library/Constants/FlagsCest.php deleted file mode 100644 index ef32ae64..00000000 --- a/tests/unit/library/Constants/FlagsCest.php +++ /dev/null @@ -1,15 +0,0 @@ -assertSame(1, Flags::ACTIVE); - $I->assertSame(2, Flags::INACTIVE); - } -} diff --git a/tests/unit/library/Constants/RelationshipsCest.php b/tests/unit/library/Constants/RelationshipsCest.php deleted file mode 100644 index 7830a634..00000000 --- a/tests/unit/library/Constants/RelationshipsCest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertSame('companies', Relationships::COMPANIES); - $I->assertSame('individual-types', Relationships::INDIVIDUAL_TYPES); - $I->assertSame('individuals', Relationships::INDIVIDUALS); - $I->assertSame('product-types', Relationships::PRODUCT_TYPES); - $I->assertSame('products', Relationships::PRODUCTS); - $I->assertSame('users', Relationships::USERS); - } -} diff --git a/tests/unit/library/ErrorHandlerCest.php b/tests/unit/library/ErrorHandlerCest.php deleted file mode 100755 index 45452ad7..00000000 --- a/tests/unit/library/ErrorHandlerCest.php +++ /dev/null @@ -1,57 +0,0 @@ -register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - - /** @var Config $config */ - $config = $diContainer->getShared('config'); - /** @var Logger $logger */ - $logger = $diContainer->getShared('logger'); - $handler = new ErrorHandler($logger, $config); - - $handler->handle(1, 'test error', 'file.php', 4); - $fileName = appPath('storage/logs/api.log'); - $I->openFile($fileName); - $expected = '[ERROR] [#:1]-[L: 4] : test error (file.php)'; - $I->seeInThisFile($expected); - } - - public function logErrorOnShutdown(UnitTester $I) - { - $diContainer = new FactoryDefault(); - $provider = new ConfigProvider(); - $provider->register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - - /** @var Config $config */ - $config = $diContainer->getShared('config'); - /** @var Logger $logger */ - $logger = $diContainer->getShared('logger'); - $handler = new ErrorHandler($logger, $config); - - $handler->shutdown(); - $fileName = appPath('storage/logs/api.log'); - $I->openFile($fileName); - $expected = '[INFO] Shutdown completed'; - $I->seeInThisFile($expected); - } -} diff --git a/tests/unit/library/Http/ResponseCest.php b/tests/unit/library/Http/ResponseCest.php deleted file mode 100755 index 5d9c6764..00000000 --- a/tests/unit/library/Http/ResponseCest.php +++ /dev/null @@ -1,132 +0,0 @@ -setPayloadSuccess('test') - ; - - $contents = $response->getContent(); - $I->assertTrue(is_string($contents)); - - $payload = $this->checkPayload($I, $response); - - $I->assertFalse(isset($payload['errors'])); - $I->assertSame('test', $payload['data']); - } - - private function checkPayload(UnitTester $I, Response $response, bool $error = false): array - { - $contents = $response->getContent(); - $I->assertTrue(is_string($contents)); - - $payload = json_decode($contents, true); - if (true === $error) { - $I->assertTrue(isset($payload['errors'])); - } else { - $I->assertTrue(isset($payload['data'])); - } - - return $payload; - } - - public function checkResponseWithArrayPayload(UnitTester $I) - { - $response = new Response(); - - $response - ->setPayloadSuccess(['a' => 'b']) - ; - - $payload = $this->checkPayload($I, $response); - - $I->assertFalse(isset($payload['errors'])); - $I->assertSame(['a' => 'b'], $payload['data']); - } - - public function checkResponseWithErrorCode(UnitTester $I) - { - $response = new Response(); - - $response - ->setPayloadError('error content') - ; - - $payload = $this->checkPayload($I, $response, true); - - $I->assertFalse(isset($payload['data'])); - $I->assertSame('error content', $payload['errors'][0]); - } - - public function checkResponseWithModelErrors(UnitTester $I) - { - $messages = [ - new Message('hello'), - new Message('goodbye'), - ]; - $response = new Response(); - $response - ->setPayloadErrors($messages) - ; - - $payload = $this->checkPayload($I, $response, true); - - $I->assertFalse(isset($payload['data'])); - $I->assertSame(2, count($payload['errors'])); - $I->assertSame('hello', $payload['errors'][0]); - $I->assertSame('goodbye', $payload['errors'][1]); - } - - public function checkResponseWithValidationErrors(UnitTester $I) - { - $group = new Messages(); - $message = new Message('hello'); - $group->appendMessage($message); - $message = new Message('goodbye'); - $group->appendMessage($message); - - $response = new Response(); - $response - ->setPayloadErrors($group) - ; - - $payload = $this->checkPayload($I, $response, true); - - $I->assertFalse(isset($payload['data'])); - $I->assertSame(2, count($payload['errors'])); - $I->assertSame('hello', $payload['errors'][0]); - $I->assertSame('goodbye', $payload['errors'][1]); - } - - public function checkHttpCodes(UnitTester $I) - { - $response = new Response(); - $I->assertSame('200 (OK)', $response->getHttpCodeDescription($response::OK)); - $I->assertSame('301 (Moved Permanently)', $response->getHttpCodeDescription($response::MOVED_PERMANENTLY)); - $I->assertSame('302 (Found)', $response->getHttpCodeDescription($response::FOUND)); - $I->assertSame('307 (Temporary Redirect)', $response->getHttpCodeDescription($response::TEMPORARY_REDIRECT)); - $I->assertSame('308 (Permanent Redirect)', $response->getHttpCodeDescription($response::PERMANENTLY_REDIRECT)); - $I->assertSame('400 (Bad Request)', $response->getHttpCodeDescription($response::BAD_REQUEST)); - $I->assertSame('401 (Unauthorized)', $response->getHttpCodeDescription($response::UNAUTHORIZED)); - $I->assertSame('403 (Forbidden)', $response->getHttpCodeDescription($response::FORBIDDEN)); - $I->assertSame('404 (Not Found)', $response->getHttpCodeDescription($response::NOT_FOUND)); - $I->assertSame('500 (Internal Server Error)', $response->getHttpCodeDescription($response::INTERNAL_SERVER_ERROR)); - $I->assertSame('501 (Not Implemented)', $response->getHttpCodeDescription($response::NOT_IMPLEMENTED)); - $I->assertSame('502 (Bad Gateway)', $response->getHttpCodeDescription($response::BAD_GATEWAY)); - $I->assertSame('999 (Undefined code)', $response->getHttpCodeDescription(999)); - } -} diff --git a/tests/unit/library/Providers/CacheCest.php b/tests/unit/library/Providers/CacheCest.php deleted file mode 100644 index fbe4c460..00000000 --- a/tests/unit/library/Providers/CacheCest.php +++ /dev/null @@ -1,29 +0,0 @@ -register($diContainer); - $provider = new CacheDataProvider(); - $provider->register($diContainer); - - $I->assertTrue($diContainer->has('cache')); - /** @var Cache $cache */ - $cache = $diContainer->getShared('cache'); - $I->assertTrue($cache instanceof Cache); - } -} diff --git a/tests/unit/library/Providers/ConfigCest.php b/tests/unit/library/Providers/ConfigCest.php deleted file mode 100644 index 04604f25..00000000 --- a/tests/unit/library/Providers/ConfigCest.php +++ /dev/null @@ -1,36 +0,0 @@ -register($diContainer); - - $I->assertTrue($diContainer->has('config')); - $config = $diContainer->getShared('config'); - $I->assertTrue($config instanceof Config); - - $configArray = $config->toArray(); - $I->assertTrue(isset($configArray['app']['version'])); - $I->assertTrue(isset($configArray['app']['timezone'])); - $I->assertTrue(isset($configArray['app']['debug'])); - $I->assertTrue(isset($configArray['app']['env'])); - $I->assertTrue(isset($configArray['app']['devMode'])); - $I->assertTrue(isset($configArray['app']['baseUri'])); - $I->assertTrue(isset($configArray['app']['supportEmail'])); - $I->assertTrue(isset($configArray['app']['time'])); - $I->assertTrue(isset($configArray['cache'])); - } -} diff --git a/tests/unit/library/Providers/DatabaseCest.php b/tests/unit/library/Providers/DatabaseCest.php deleted file mode 100644 index aa933d39..00000000 --- a/tests/unit/library/Providers/DatabaseCest.php +++ /dev/null @@ -1,30 +0,0 @@ -register($diContainer); - $provider = new DatabaseProvider(); - $provider->register($diContainer); - - $I->assertTrue($diContainer->has('db')); - /** @var Mysql $db */ - $db = $diContainer->getShared('db'); - $I->assertTrue($db instanceof Mysql); - $I->assertSame('mysql', $db->getType()); - } -} diff --git a/tests/unit/library/Providers/ErrorHandlerCest.php b/tests/unit/library/Providers/ErrorHandlerCest.php deleted file mode 100644 index 1992e7c9..00000000 --- a/tests/unit/library/Providers/ErrorHandlerCest.php +++ /dev/null @@ -1,34 +0,0 @@ -register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - $provider = new ErrorHandlerProvider(); - $provider->register($diContainer); - - $config = $diContainer->getShared('config'); - - $I->assertSame(date_default_timezone_get(), $config->path('app.timezone')); - $I->assertSame(ini_get('display_errors'), 'Off'); - $I->assertSame(E_ALL, (int) ini_get('error_reporting')); - } -} diff --git a/tests/unit/library/Providers/LoggerCest.php b/tests/unit/library/Providers/LoggerCest.php deleted file mode 100644 index 96f19d11..00000000 --- a/tests/unit/library/Providers/LoggerCest.php +++ /dev/null @@ -1,30 +0,0 @@ -register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - - $I->assertTrue($diContainer->has('logger')); - /** @var Logger $logger */ - $logger = $diContainer->getShared('logger'); - $I->assertTrue($logger instanceof Logger); - $I->assertSame('api', $logger->getName()); - } -} diff --git a/tests/unit/library/Providers/ModelsMetadataCest.php b/tests/unit/library/Providers/ModelsMetadataCest.php deleted file mode 100644 index 406d8584..00000000 --- a/tests/unit/library/Providers/ModelsMetadataCest.php +++ /dev/null @@ -1,29 +0,0 @@ -register($diContainer); - $provider = new ModelsMetadataProvider(); - $provider->register($diContainer); - - $I->assertTrue($diContainer->has('modelsMetadata')); - /** @var Memory $metadata */ - $metadata = $diContainer->getShared('modelsMetadata'); - $I->assertTrue($metadata instanceof Memory); - } -} diff --git a/tests/unit/library/Providers/ResponseCest.php b/tests/unit/library/Providers/ResponseCest.php deleted file mode 100644 index 658c8af3..00000000 --- a/tests/unit/library/Providers/ResponseCest.php +++ /dev/null @@ -1,26 +0,0 @@ -register($diContainer); - - $I->assertTrue($diContainer->has('response')); - /** @var Response $response */ - $response = $diContainer->getShared('response'); - $I->assertTrue($response instanceof Response); - } -} diff --git a/tests/unit/library/Providers/RouterCest.php b/tests/unit/library/Providers/RouterCest.php deleted file mode 100644 index 55741577..00000000 --- a/tests/unit/library/Providers/RouterCest.php +++ /dev/null @@ -1,64 +0,0 @@ -setShared('application', $application); - $provider = new ConfigProvider(); - $provider->register($diContainer); - $provider = new RouterProvider(); - $provider->register($diContainer); - - /** @var RouterInterface $router */ - $router = $application->getRouter(); - $routes = $router->getRoutes(); - $expected = [ - ['POST', '/login'], - ['POST', '/companies'], - ['GET', '/users'], - ['GET', '/users/{recordId:[0-9]+}'], - ['GET', '/companies'], - ['GET', '/companies/{recordId:[0-9]+}'], - ['GET', '/companies/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/companies/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/individuals'], - ['GET', '/individuals/{recordId:[0-9]+}'], - ['GET', '/individuals/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/individuals/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/individual-types'], - ['GET', '/individual-types/{recordId:[0-9]+}'], - ['GET', '/individual-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/individual-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/products'], - ['GET', '/products/{recordId:[0-9]+}'], - ['GET', '/products/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/products/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/product-types'], - ['GET', '/product-types/{recordId:[0-9]+}'], - ['GET', '/product-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], - ['GET', '/product-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], - ]; - - $I->assertSame(24, count($routes)); - foreach ($routes as $index => $route) { - $I->assertSame($expected[$index][0], $route->getHttpMethods()); - $I->assertSame($expected[$index][1], $route->getPattern()); - } - } -} From 1ffaa2f28c9e0c0feed54b0fa0f3d9807f2918ab Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:25:22 -0500 Subject: [PATCH 22/47] [#42] - map talon suites to a single phpunit config --- resources/phpunit.api.xml | 17 ------------ resources/phpunit.cli.xml | 17 ------------ resources/phpunit.integration.xml | 17 ------------ resources/phpunit.xml.dist | 9 +++++++ talon.php | 44 +++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 resources/phpunit.api.xml delete mode 100644 resources/phpunit.cli.xml delete mode 100644 resources/phpunit.integration.xml create mode 100644 talon.php diff --git a/resources/phpunit.api.xml b/resources/phpunit.api.xml deleted file mode 100644 index eac3c030..00000000 --- a/resources/phpunit.api.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - ../tests/Api - - - - - ../api - ../cli - ../library - - - diff --git a/resources/phpunit.cli.xml b/resources/phpunit.cli.xml deleted file mode 100644 index 8782b194..00000000 --- a/resources/phpunit.cli.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - ../tests/Cli - - - - - ../api - ../cli - ../library - - - diff --git a/resources/phpunit.integration.xml b/resources/phpunit.integration.xml deleted file mode 100644 index e47ec9ea..00000000 --- a/resources/phpunit.integration.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - ../tests/Integration - - - - - ../api - ../cli - ../library - - - diff --git a/resources/phpunit.xml.dist b/resources/phpunit.xml.dist index 09f555e5..6e2513d0 100644 --- a/resources/phpunit.xml.dist +++ b/resources/phpunit.xml.dist @@ -6,6 +6,15 @@ ../tests/Unit + + ../tests/Integration + + + ../tests/Api + + + ../tests/Cli + diff --git a/talon.php b/talon.php new file mode 100644 index 00000000..3e173298 --- /dev/null +++ b/talon.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); + +/** + * Talon suite map. + * + * Without this file Talon derives one suite per `phpunit*.xml*` file and names + * it after the filename. All four suites live in a single PHPUnit config, so + * they are mapped here instead and told apart with `--testsuite`. That keeps + * `talon run api` working while the configuration stays in one place. + * + * `talon run all` runs every suite below, each as its own PHPUnit process. + */ +return [ + 'default' => 'unit', + 'suites' => [ + 'unit' => [ + 'config' => 'resources/phpunit.xml.dist', + 'args' => ['--testsuite', 'unit'], + ], + 'integration' => [ + 'config' => 'resources/phpunit.xml.dist', + 'args' => ['--testsuite', 'integration'], + ], + 'api' => [ + 'config' => 'resources/phpunit.xml.dist', + 'args' => ['--testsuite', 'api'], + ], + 'cli' => [ + 'config' => 'resources/phpunit.xml.dist', + 'args' => ['--testsuite', 'cli'], + ], + ], +]; From 95cb9c75d80d969bfe58a58bcb3741436e65bf48 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:29:35 -0500 Subject: [PATCH 23/47] [#42] - add php-cs-fixer config and package --- composer.json | 1 + composer.lock | 4377 +++++++++++++++++++++++++----------- resources/php-cs-fixer.php | 74 + 3 files changed, 3106 insertions(+), 1346 deletions(-) create mode 100644 resources/php-cs-fixer.php diff --git a/composer.json b/composer.json index 227a9ca7..3d8a9e24 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "allow-plugins": {} }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3", "pds/composer-script-names": "^1.0", "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", diff --git a/composer.lock b/composer.lock index a21e4254..b40f9a5c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "80a428428767cc31b3bf1868d8bbb597", + "content-hash": "11d8b2f30c0ab9dab5f6f0f3ae4c1354", "packages": [ { "name": "cakephp/chronos", @@ -1908,35 +1908,31 @@ ], "packages-dev": [ { - "name": "masterminds/html5", - "version": "2.10.1", + "name": "clue/ndjson-react", + "version": "v1.3.0", "source": { "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", - "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", "shasum": "" }, "require": { - "ext-dom": "*", - "php": ">=5.3.0" + "php": ">=5.3", + "react/stream": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, "autoload": { "psr-4": { - "Masterminds\\": "src" + "Clue\\React\\NDJson\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1945,70 +1941,76 @@ ], "authors": [ { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" - }, - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" + "name": "Christian Lück", + "email": "christian@clue.engineering" } ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" ], "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" }, - "time": "2026-06-23T18:43:15+00:00" + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" }, { - "name": "matthiasmullie/minify", - "version": "1.3.75", + "name": "composer/pcre", + "version": "3.4.0", "source": { "type": "git", - "url": "https://github.com/matthiasmullie/minify.git", - "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", - "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { - "ext-pcre": "*", - "matthiasmullie/path-converter": "~1.1", - "php": ">=5.3.0" + "php": "^7.4 || ^8.0" }, - "require-dev": { - "friendsofphp/php-cs-fixer": ">=2.0", - "matthiasmullie/scrapbook": ">=1.3", - "phpunit/phpunit": ">=4.8" + "conflict": { + "phpstan/phpstan": "<2.2.2" }, - "suggest": { - "psr/cache-implementation": "Cache implementation to use with Minify::cache" + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, - "bin": [ - "bin/minifycss", - "bin/minifyjs" - ], "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "MatthiasMullie\\Minify\\": "src/" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2017,58 +2019,64 @@ ], "authors": [ { - "name": "Matthias Mullie", - "email": "minify@mullie.eu", - "homepage": "https://www.mullie.eu", - "role": "Developer" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", - "homepage": "https://github.com/matthiasmullie/minify", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "JS", - "css", - "javascript", - "minifier", - "minify" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { - "url": "https://github.com/matthiasmullie", + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", "type": "github" } ], - "time": "2025-06-25T09:56:19+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { - "name": "matthiasmullie/path-converter", - "version": "1.1.3", + "name": "composer/semver", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/matthiasmullie/path-converter.git", - "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", - "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { - "ext-pcre": "*", - "php": ">=5.3.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "MatthiasMullie\\PathConverter\\": "src/" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2077,682 +2085,2352 @@ ], "authors": [ { - "name": "Matthias Mullie", - "email": "pathconverter@mullie.eu", - "homepage": "http://www.mullie.eu", - "role": "Developer" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "Relative path converter", - "homepage": "http://github.com/matthiasmullie/path-converter", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "converter", - "path", - "paths", - "relative" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/matthiasmullie/path-converter/issues", - "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" }, - "time": "2019-02-05T23:41:09+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "Xdebug", + "performance" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.8.0", + "name": "ergebnis/agent-detector", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", - "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", "shasum": "" }, "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Ergebnis\\AgentDetector\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" }, - "time": "2026-07-04T14:30:18+00:00" + "time": "2026-05-07T08:19:07+00:00" }, { - "name": "pds/composer-script-names", - "version": "1.0.0", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/php-pds/composer-script-names.git", - "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-pds/composer-script-names/zipball/e6a78aaeaee7cef82a995718ab2f5d0445ef8073", - "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, - "type": "standard", + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "CC-BY-SA-4.0" + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" ], - "description": "Standard for Composer script names.", - "homepage": "https://github.com/php-pds/composer-script-names", "support": { - "issues": "https://github.com/php-pds/composer-script-names/issues", - "source": "https://github.com/php-pds/composer-script-names/tree/1.0.0" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2023-04-06T13:42:16+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { - "name": "pds/skeleton", - "version": "1.0.0", + "name": "fidry/cpu-core-counter", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/php-pds/skeleton.git", - "reference": "95e476e5d629eadacbd721c5a9553e537514a231" + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-pds/skeleton/zipball/95e476e5d629eadacbd721c5a9553e537514a231", - "reference": "95e476e5d629eadacbd721c5a9553e537514a231", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, - "bin": [ - "bin/pds-skeleton" - ], - "type": "standard", + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", "autoload": { "psr-4": { - "Pds\\Skeleton\\": "src/" + "Fidry\\CpuCoreCounter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "CC-BY-SA-4.0" + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" ], - "description": "Standard for PHP package skeletons.", - "homepage": "https://github.com/php-pds/skeleton", + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.95.15", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "3e47e5d50046f87e3244acde2fe655d1a3b72555" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3e47e5d50046f87e3244acde2fe655d1a3b72555", + "reference": "3e47e5d50046f87e3244acde2fe655d1a3b72555", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.2", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.37", + "symfony/polyfill-php80": "^1.37", + "symfony/polyfill-php81": "^1.37", + "symfony/polyfill-php84": "^1.37", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.11.0", + "infection/infection": "^0.32.7", + "justinrainbow/json-schema": "^6.10.0", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31", + "symfony/polyfill-php85": "^1.38", + "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.0", + "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.15" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-07-15T09:51:47+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", + "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + }, + "time": "2026-06-23T18:43:15+00:00" + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.75", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": ">=2.0", + "matthiasmullie/scrapbook": ">=1.3", + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "https://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "https://github.com/matthiasmullie/minify", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "support": { + "issues": "https://github.com/matthiasmullie/minify/issues", + "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" + }, + "funding": [ + { + "url": "https://github.com/matthiasmullie", + "type": "github" + } + ], + "time": "2025-06-25T09:56:19+00:00" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "support": { + "issues": "https://github.com/matthiasmullie/path-converter/issues", + "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" + }, + "time": "2019-02-05T23:41:09+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "pds/composer-script-names", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/composer-script-names.git", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/composer-script-names/zipball/e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "reference": "e6a78aaeaee7cef82a995718ab2f5d0445ef8073", + "shasum": "" + }, + "type": "standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for Composer script names.", + "homepage": "https://github.com/php-pds/composer-script-names", + "support": { + "issues": "https://github.com/php-pds/composer-script-names/issues", + "source": "https://github.com/php-pds/composer-script-names/tree/1.0.0" + }, + "time": "2023-04-06T13:42:16+00:00" + }, + { + "name": "pds/skeleton", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-pds/skeleton.git", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-pds/skeleton/zipball/95e476e5d629eadacbd721c5a9553e537514a231", + "reference": "95e476e5d629eadacbd721c5a9553e537514a231", + "shasum": "" + }, + "bin": [ + "bin/pds-skeleton" + ], + "type": "standard", + "autoload": { + "psr-4": { + "Pds\\Skeleton\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC-BY-SA-4.0" + ], + "description": "Standard for PHP package skeletons.", + "homepage": "https://github.com/php-pds/skeleton", "support": { "issues": "https://github.com/php-pds/skeleton/issues", "source": "https://github.com/php-pds/skeleton/tree/1.x" }, - "time": "2017-01-25T23:30:41+00:00" + "time": "2017-01-25T23:30:41+00:00" + }, + { + "name": "phalcon/cli-options-parser", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/cli-options-parser.git", + "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/cli-options-parser/zipball/ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", + "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "pds/skeleton": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Phalcon\\Cop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalconphp.com", + "homepage": "https://phalconphp.com/en/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/cli-options-parser/graphs/contributors" + } + ], + "description": "Command line arguments/options parser.", + "homepage": "https://phalconphp.com", + "keywords": [ + "argparse", + "cli", + "command", + "command-line", + "getopt", + "line", + "option", + "optparse", + "parser", + "terminal" + ], + "support": { + "discord": "https://phalcon.io/discord/", + "issues": "https://github.com/phalcon/cli-options-parser/issues", + "source": "https://github.com/phalcon/cli-options-parser" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2023-11-24T16:04:00+00:00" + }, + { + "name": "phalcon/ide-stubs", + "version": "v5.16.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/ide-stubs.git", + "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/3eea65770216ee93d349802f6e8d8711478d9a5c", + "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "squizlabs/php_codesniffer": "^4.0", + "vimeo/psalm": "^6.16" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io/en-us/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors" + } + ], + "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.", + "homepage": "https://phalcon.io", + "keywords": [ + "Devtools", + "Eclipse", + "autocomplete", + "ide", + "netbeans", + "phalcon", + "phpstorm", + "stub", + "stubs" + ], + "support": { + "discussions": "https://phalcon.io/discussions/", + "issues": "https://github.com/phalcon/ide-stubs/issues", + "source": "https://github.com/phalcon/ide-stubs" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-06-22T18:56:53+00:00" + }, + { + "name": "phalcon/phalcon", + "version": "v6.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phalcon/phalcon.git", + "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/phalcon/zipball/17f4299d3bdf46487638fb6869fedca65396f3b2", + "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-pdo": "*", + "ext-xml": "*", + "matthiasmullie/minify": "^1.3", + "phalcon/traits": "^4.0", + "php": ">=8.1 <9.0", + "psr/event-dispatcher": "^1.0" + }, + "require-dev": { + "ext-gettext": "*", + "ext-yaml": "*", + "friendsofphp/php-cs-fixer": "^3.95", + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/phql": "1.0.x-dev", + "phalcon/talon": "^0.6.0", + "phalcon/volt": "1.0.x-dev", + "phpbench/phpbench": "^1.4", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^10.5", + "predis/predis": "^3.4", + "squizlabs/php_codesniffer": "^4.0", + "vlucas/phpdotenv": "^5.6" + }, + "suggest": { + "ext-apcu": "to use Cache\\Adapter\\Apcu, Storage\\Adapter\\Apcu", + "ext-gd": "to use Image\\Adapter\\Gd", + "ext-igbinary": "to use Storage\\Serializer\\Igbinary", + "ext-imagick": "to use Image\\Adapter\\Imagick", + "ext-memcached": "to use Cache\\Adapter\\Libmemcached, Session\\Adapter\\Libmemcached, Storage\\Adapter\\Libmemcached", + "ext-openssl": "to use Encryption\\Crypt", + "ext-pcntl": "to use signal-based graceful shutdown in Queue\\Consumer\\Worker", + "ext-redis": "to use Cache\\Adapter\\Redis, Session\\Adapter\\Redis, Storage\\Adapter\\Redis", + "ext-yaml": "to use Config\\Adapter\\Yaml" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Phalcon Framework", + "keywords": [ + "framework", + "php" + ], + "support": { + "issues": "https://github.com/phalcon/phalcon/issues", + "source": "https://github.com/phalcon/phalcon/tree/v6.0.x" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-15T23:27:22+00:00" + }, + { + "name": "phalcon/talon", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/talon.git", + "reference": "b8557e7056395df23ed2634d284b6b54362c4c25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/talon/zipball/b8557e7056395df23ed2634d284b6b54362c4c25", + "reference": "b8557e7056395df23ed2634d284b6b54362c4c25", + "shasum": "" + }, + "require": { + "phalcon/cli-options-parser": "^2.0", + "php": "^8.1", + "symfony/browser-kit": "^6.4 || ^7.0", + "symfony/dom-crawler": "^6.4 || ^7.0", + "symfony/http-client": "^6.4 || ^7.0", + "symfony/mime": "^6.4 || ^7.0" + }, + "require-dev": { + "ext-pdo": "*", + "ext-sqlite3": "*", + "friendsofphp/php-cs-fixer": "^3", + "pds/composer-script-names": "^1", + "pds/skeleton": "^1", + "phalcon/phalcon": "^6.0@alpha", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.5", + "predis/predis": "^2 || ^3", + "squizlabs/php_codesniffer": "^3 || ^4" + }, + "suggest": { + "ext-memcached": "For the Services (Memcached) helpers", + "ext-phalcon": "Phalcon C extension (^5) - one of ext-phalcon or phalcon/phalcon is required", + "phalcon/phalcon": "Phalcon PHP implementation (^6) - alternative to the C extension", + "predis/predis": "For the Services (Redis) helpers" + }, + "bin": [ + "bin/talon" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\Talon\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Test harness and Phalcon bootstrapping for PHPUnit and beyond", + "keywords": [ + "harness", + "phalcon", + "phpunit", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/phalcon/talon/issues", + "source": "https://github.com/phalcon/talon/tree/v0.8.0" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-15T19:50:31+00:00" + }, + { + "name": "phalcon/traits", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/phalcon/traits.git", + "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/traits/zipball/4e4412a78475af2d08f7e82e48e577e2a40b6896", + "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=8.1 <9.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.75", + "infection/infection": "^0.29.9", + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/talon": "^0.7", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^4.0" + }, + "suggest": { + "ext-apcu": "For Php\\ApcuTrait", + "ext-gettext": "For the gettext-based translation helpers", + "ext-igbinary": "For Php\\IgbinaryTrait", + "ext-msgpack": "For Php\\MsgpackTrait", + "ext-yaml": "For Php\\YamlTrait" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phalcon\\Traits\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io/en/team" + }, + { + "name": "Contributors", + "homepage": "https://github.com/phalcon/traits/graphs/contributors" + } + ], + "description": "Phalcon Framework reusable traits", + "homepage": "https://phalcon.io", + "keywords": [ + "framework", + "phalcon", + "php", + "traits" + ], + "support": { + "discord": "https://phalcon.io/discord/", + "issues": "https://github.com/phalcon/traits/issues", + "source": "https://github.com/phalcon/traits" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-10T19:24:02+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.5", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "reference": "909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-07-05T06:31:06+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "phalcon/cli-options-parser", - "version": "v2.0.0", + "name": "phpunit/phpunit", + "version": "10.5.64", "source": { "type": "git", - "url": "https://github.com/phalcon/cli-options-parser.git", - "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/cli-options-parser/zipball/ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", - "reference": "ec4d4cd0b7e61046b88957a4028ad01dfa14f4e6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", "shasum": "" }, "require": { - "php": ">=8.0" + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:50:35+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "pds/skeleton": "^1.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.6", - "squizlabs/php_codesniffer": "^3.7" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, + "type": "library", "autoload": { "psr-4": { - "Phalcon\\Cop\\": "src/" + "React\\ChildProcess\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Phalcon Team", - "email": "team@phalconphp.com", - "homepage": "https://phalconphp.com/en/team" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Contributors", - "homepage": "https://github.com/phalcon/cli-options-parser/graphs/contributors" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Command line arguments/options parser.", - "homepage": "https://phalconphp.com", + "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ - "argparse", - "cli", - "command", - "command-line", - "getopt", - "line", - "option", - "optparse", - "parser", - "terminal" + "event-driven", + "process", + "reactphp" ], "support": { - "discord": "https://phalcon.io/discord/", - "issues": "https://github.com/phalcon/cli-options-parser/issues", - "source": "https://github.com/phalcon/cli-options-parser" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" }, "funding": [ { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", + "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], - "time": "2023-11-24T16:04:00+00:00" + "time": "2025-12-23T15:25:20+00:00" }, { - "name": "phalcon/ide-stubs", - "version": "v5.16.0", + "name": "react/dns", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/phalcon/ide-stubs.git", - "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c" + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/3eea65770216ee93d349802f6e8d8711478d9a5c", - "reference": "3eea65770216ee93d349802f6e8d8711478d9a5c", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "pds/composer-script-names": "^1.0", - "pds/skeleton": "^1.0", - "squizlabs/php_codesniffer": "^4.0", - "vimeo/psalm": "^6.16" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Phalcon Team", - "email": "team@phalcon.io", - "homepage": "https://phalcon.io/en-us/team" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Contributors", - "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.", - "homepage": "https://phalcon.io", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "Devtools", - "Eclipse", - "autocomplete", - "ide", - "netbeans", - "phalcon", - "phpstorm", - "stub", - "stubs" + "async", + "dns", + "dns-resolver", + "reactphp" ], "support": { - "discussions": "https://phalcon.io/discussions/", - "issues": "https://github.com/phalcon/ide-stubs/issues", - "source": "https://github.com/phalcon/ide-stubs" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" }, "funding": [ { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", + "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], - "time": "2026-06-22T18:56:53+00:00" + "time": "2025-11-18T19:34:28+00:00" }, { - "name": "phalcon/phalcon", - "version": "v6.0.x-dev", + "name": "react/event-loop", + "version": "v1.6.0", "source": { "type": "git", - "url": "https://github.com/phalcon/phalcon.git", - "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/phalcon/zipball/17f4299d3bdf46487638fb6869fedca65396f3b2", - "reference": "17f4299d3bdf46487638fb6869fedca65396f3b2", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-pdo": "*", - "ext-xml": "*", - "matthiasmullie/minify": "^1.3", - "phalcon/traits": "^4.0", - "php": ">=8.1 <9.0", - "psr/event-dispatcher": "^1.0" + "php": ">=5.3.0" }, "require-dev": { - "ext-gettext": "*", - "ext-yaml": "*", - "friendsofphp/php-cs-fixer": "^3.95", - "pds/composer-script-names": "^1.0", - "pds/skeleton": "^1.0", - "phalcon/phql": "1.0.x-dev", - "phalcon/talon": "^0.6.0", - "phalcon/volt": "1.0.x-dev", - "phpbench/phpbench": "^1.4", - "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^10.5", - "predis/predis": "^3.4", - "squizlabs/php_codesniffer": "^4.0", - "vlucas/phpdotenv": "^5.6" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "suggest": { - "ext-apcu": "to use Cache\\Adapter\\Apcu, Storage\\Adapter\\Apcu", - "ext-gd": "to use Image\\Adapter\\Gd", - "ext-igbinary": "to use Storage\\Serializer\\Igbinary", - "ext-imagick": "to use Image\\Adapter\\Imagick", - "ext-memcached": "to use Cache\\Adapter\\Libmemcached, Session\\Adapter\\Libmemcached, Storage\\Adapter\\Libmemcached", - "ext-openssl": "to use Encryption\\Crypt", - "ext-pcntl": "to use signal-based graceful shutdown in Queue\\Consumer\\Worker", - "ext-redis": "to use Cache\\Adapter\\Redis, Session\\Adapter\\Redis, Storage\\Adapter\\Redis", - "ext-yaml": "to use Config\\Adapter\\Yaml" + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { - "Phalcon\\": "src/" + "React\\EventLoop\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Phalcon Framework", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "framework", - "php" + "asynchronous", + "event-loop" ], "support": { - "issues": "https://github.com/phalcon/phalcon/issues", - "source": "https://github.com/phalcon/phalcon/tree/v6.0.x" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", + "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], - "time": "2026-07-15T23:27:22+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { - "name": "phalcon/talon", - "version": "v0.8.0", + "name": "react/promise", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/phalcon/talon.git", - "reference": "b8557e7056395df23ed2634d284b6b54362c4c25" + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/talon/zipball/b8557e7056395df23ed2634d284b6b54362c4c25", - "reference": "b8557e7056395df23ed2634d284b6b54362c4c25", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { - "phalcon/cli-options-parser": "^2.0", - "php": "^8.1", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/mime": "^6.4 || ^7.0" + "php": ">=7.1.0" }, "require-dev": { - "ext-pdo": "*", - "ext-sqlite3": "*", - "friendsofphp/php-cs-fixer": "^3", - "pds/composer-script-names": "^1", - "pds/skeleton": "^1", - "phalcon/phalcon": "^6.0@alpha", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^10.5", - "predis/predis": "^2 || ^3", - "squizlabs/php_codesniffer": "^3 || ^4" - }, - "suggest": { - "ext-memcached": "For the Services (Memcached) helpers", - "ext-phalcon": "Phalcon C extension (^5) - one of ext-phalcon or phalcon/phalcon is required", - "phalcon/phalcon": "Phalcon PHP implementation (^6) - alternative to the C extension", - "predis/predis": "For the Services (Redis) helpers" + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, - "bin": [ - "bin/talon" - ], "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Phalcon\\Talon\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Test harness and Phalcon bootstrapping for PHPUnit and beyond", + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "harness", - "phalcon", - "phpunit", - "test", - "testing" + "promise", + "promises" ], "support": { - "issues": "https://github.com/phalcon/talon/issues", - "source": "https://github.com/phalcon/talon/tree/v0.8.0" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", + "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], - "time": "2026-07-15T19:50:31+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { - "name": "phalcon/traits", - "version": "4.0.0", + "name": "react/socket", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/phalcon/traits.git", - "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896" + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phalcon/traits/zipball/4e4412a78475af2d08f7e82e48e577e2a40b6896", - "reference": "4e4412a78475af2d08f7e82e48e577e2a40b6896", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": ">=8.1 <9.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.75", - "infection/infection": "^0.29.9", - "pds/composer-script-names": "^1.0", - "pds/skeleton": "^1.0", - "phalcon/talon": "^0.7", - "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^10.5", - "squizlabs/php_codesniffer": "^4.0" - }, - "suggest": { - "ext-apcu": "For Php\\ApcuTrait", - "ext-gettext": "For the gettext-based translation helpers", - "ext-igbinary": "For Php\\IgbinaryTrait", - "ext-msgpack": "For Php\\MsgpackTrait", - "ext-yaml": "For Php\\YamlTrait" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "Phalcon\\Traits\\": "src/" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Phalcon Team", - "email": "team@phalcon.io", - "homepage": "https://phalcon.io/en/team" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Contributors", - "homepage": "https://github.com/phalcon/traits/graphs/contributors" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Phalcon Framework reusable traits", - "homepage": "https://phalcon.io", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "framework", - "phalcon", - "php", - "traits" - ], - "support": { - "discord": "https://phalcon.io/discord/", - "issues": "https://github.com/phalcon/traits/issues", - "source": "https://github.com/phalcon/traits" + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" }, "funding": [ { - "url": "https://github.com/phalcon", - "type": "github" - }, - { - "url": "https://opencollective.com/phalcon", + "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], - "time": "2026-07-10T19:24:02+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\Stream\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, "funding": [ { - "url": "https://github.com/theseer", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2024-06-11T12:45:25+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "sebastian/cli-parser", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -2763,133 +4441,107 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "lead" } ], - "description": "Library for handling version information and constraints", + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, - "time": "2022-02-21T01:04:05+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" }, { - "name": "phpstan/phpstan", - "version": "2.2.5", + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", - "reference": "909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": "^7.4|^8.0" + "php": ">=8.1" }, - "conflict": { - "phpstan/phpstan-shim": "*" + "require-dev": { + "phpunit/phpunit": "^10.0" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, "autoload": { - "files": [ - "bootstrap.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Ondřej Mirtes" - }, - { - "name": "Markus Staab" - }, - { - "name": "Vincent Langlet" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2026-07-05T06:31:06+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1.x-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2904,21 +4556,14 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -2926,32 +4571,36 @@ "type": "github" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "name": "sebastian/comparator", + "version": "5.0.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { - "php": ">=8.1" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2965,58 +4614,79 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ - "filesystem", - "iterator" + "comparator", + "compare", + "equality" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { - "name": "phpunit/php-invoker", - "version": "4.0.0", + "name": "sebastian/complexity", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { - "ext-pcntl": "*", "phpunit/phpunit": "^10.0" }, - "suggest": { - "ext-pcntl": "*" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -3035,14 +4705,12 @@ "role": "lead" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" }, "funding": [ { @@ -3050,32 +4718,33 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { - "name": "phpunit/php-text-template", - "version": "3.0.1", + "name": "sebastian/diff", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3090,19 +4759,25 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "template" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -3110,20 +4785,20 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { - "name": "phpunit/php-timer", - "version": "6.0.0", + "name": "sebastian/environment", + "version": "6.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { @@ -3132,10 +4807,13 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, + "suggest": { + "ext-posix": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -3150,18 +4828,20 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ - "timer" + "Xdebug", + "environment", + "hhvm" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" }, "funding": [ { @@ -3169,66 +4849,37 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { - "name": "phpunit/phpunit", - "version": "10.5.64", + "name": "sebastian/exporter", + "version": "5.1.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", - "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-filter": "*", - "ext-json": "*", - "ext-libxml": "*", "ext-mbstring": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" + "sebastian/recursion-context": "^5.0" }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "require-dev": { + "phpunit/phpunit": "^10.5" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "5.1-dev" } }, "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], "classmap": [ "src/" ] @@ -3240,95 +4891,134 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "phpunit", - "testing", - "xunit" + "export", + "exporter" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" }, "funding": [ { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2026-07-06T14:50:35+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "sebastian/global-state", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Standard interfaces for event handling.", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ - "events", - "psr", - "psr-14" + "global state" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, - "time": "2019-01-08T18:20:26+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" }, { - "name": "sebastian/cli-parser", - "version": "2.0.1", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { @@ -3356,12 +5046,12 @@ "role": "lead" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -3369,24 +5059,26 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { - "name": "sebastian/code-unit", - "version": "2.0.0", + "name": "sebastian/object-enumerator", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "phpunit/phpunit": "^10.0" @@ -3394,7 +5086,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3409,15 +5101,14 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -3425,20 +5116,20 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", + "name": "sebastian/object-reflector", "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { @@ -3468,11 +5159,11 @@ "email": "sebastian@phpunit.de" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -3480,28 +5171,24 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { - "name": "sebastian/comparator", - "version": "5.0.5", + "name": "sebastian/recursion-context", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^10.5" @@ -3531,25 +5218,16 @@ "email": "whatthejeff@gmail.com" }, { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" }, "funding": [ { @@ -3565,28 +5243,27 @@ "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", "type": "tidelift" } ], - "time": "2026-01-24T09:25:16+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { - "name": "sebastian/complexity", - "version": "3.2.0", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", "php": ">=8.1" }, "require-dev": { @@ -3595,7 +5272,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3614,79 +5291,11 @@ "role": "lead" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:37:17+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -3694,35 +5303,29 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "sebastian/environment", - "version": "6.1.0", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { "php": ">=8.1" }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -3737,20 +5340,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -3758,474 +5356,572 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "sebastian/exporter", - "version": "5.1.4", + "name": "squizlabs/php_codesniffer", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" + "name": "Greg Sherwood", + "role": "Former lead" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Juliette Reinders Folmer", + "role": "Current lead" }, { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ - "export", - "exporter" + "phpcs", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/PHPCSStandards", "type": "github" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/jrfnl", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { - "name": "sebastian/global-state", - "version": "6.0.2", + "name": "symfony/browser-kit", + "version": "v6.4.42", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "url": "https://github.com/symfony/browser-kit.git", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/94d0d5413126d81bffcd0de509fb9daf0363babd", + "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd", "shasum": "" }, "require": { "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "symfony/dom-crawler": "^5.4|^6.0|^7.0" }, "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.42" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2026-06-08T07:06:12+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "2.0.2", + "name": "symfony/dom-crawler", + "version": "v6.4.40", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", + "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "masterminds/html5": "^2.6", + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "symfony/css-selector": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.40" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2026-05-19T20:33:22+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "5.0.0", + "name": "symfony/event-dispatcher", + "version": "v6.4.37", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "2e3bf817ba9347341ab15926700fb6320367c0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2e3bf817ba9347341ab15926700fb6320367c0e1", + "reference": "2e3bf817ba9347341ab15926700fb6320367c0e1", "shasum": "" }, "require": { "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.37" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2026-04-13T14:11:12+00:00" }, { - "name": "sebastian/object-reflector", - "version": "3.0.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "sebastian/recursion-context", - "version": "5.0.1", + "name": "symfony/finder", + "version": "v6.4.42", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "url": "https://github.com/symfony/finder.git", + "reference": "0b73dac42493acbadbba644207a715b254e9b029" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/symfony/finder/zipball/0b73dac42493acbadbba644207a715b254e9b029", + "reference": "0b73dac42493acbadbba644207a715b254e9b029", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + "source": "https://github.com/symfony/finder/tree/v6.4.42" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2026-06-26T15:18:24+00:00" }, { - "name": "sebastian/type", - "version": "4.0.0", + "name": "symfony/http-client", + "version": "v6.4.42", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "url": "https://github.com/symfony/http-client.git", + "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/symfony/http-client/zipball/b71b312ca0f211fbb19a20a51bde50fbefb66a99", + "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "source": "https://github.com/symfony/http-client/tree/v6.4.42" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2026-06-12T09:42:32+00:00" }, { - "name": "sebastian/version", - "version": "4.0.1", + "name": "symfony/http-client-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", "shasum": "" }, "require": { @@ -4233,147 +5929,180 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "4.0.1", + "name": "symfony/mime", + "version": "v6.4.41", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0525c73950de35ded110cffafb9892946d7771b5" + "url": "https://github.com/symfony/mime.git", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", - "reference": "0525c73950de35ded110cffafb9892946d7771b5", + "url": "https://api.github.com/repos/symfony/mime/zipball/5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=7.2.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { - "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", "keywords": [ - "phpcs", - "standards", - "static analysis" + "mime", + "mime-type" ], "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "source": "https://github.com/symfony/mime/tree/v6.4.41" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://github.com/jrfnl", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-11-10T16:43:36+00:00" + "time": "2026-05-23T14:40:34+00:00" }, { - "name": "symfony/browser-kit", - "version": "v6.4.42", + "name": "symfony/options-resolver", + "version": "v6.4.30", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/94d0d5413126d81bffcd0de509fb9daf0363babd", - "reference": "94d0d5413126d81bffcd0de509fb9daf0363babd", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/dom-crawler": "^5.4|^6.0|^7.0" - }, - "require-dev": { - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0" + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4393,10 +6122,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.42" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.30" }, "funding": [ { @@ -4416,39 +6150,43 @@ "type": "tidelift" } ], - "time": "2026-06-08T07:06:12+00:00" + "time": "2025-11-12T13:06:53+00:00" }, { - "name": "symfony/dom-crawler", - "version": "v6.4.40", + "name": "symfony/polyfill-intl-idn", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", - "reference": "7e65f76c28f5ed8d933f2c86698a3e2bf0de1b10", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { - "masterminds/html5": "^2.6", - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, - "require-dev": { - "symfony/css-selector": "^5.4|^6.0|^7.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4456,18 +6194,30 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.40" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { @@ -4487,62 +6237,41 @@ "type": "tidelift" } ], - "time": "2026-05-19T20:33:22+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { - "name": "symfony/http-client", - "version": "v6.4.42", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/b71b312ca0f211fbb19a20a51bde50fbefb66a99", - "reference": "b71b312ca0f211fbb19a20a51bde50fbefb66a99", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/polyfill-php83": "^1.29", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\HttpClient\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4559,13 +6288,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "http" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.42" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -4585,41 +6317,41 @@ "type": "tidelift" } ], - "time": "2026-06-12T09:42:32+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v3.7.1", + "name": "symfony/polyfill-php83", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", - "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, - "exclude-from-classmap": [ - "/Test/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4636,18 +6368,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to HTTP clients", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" }, "funding": [ { @@ -4667,52 +6397,41 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-05-27T06:51:48+00:00" }, { - "name": "symfony/mime", - "version": "v6.4.41", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/5575d37f8841e4e31d5df79ab3db078ae557ff8e", - "reference": "5575d37f8841e4e31d5df79ab3db078ae557ff8e", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.4|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Mime\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4721,22 +6440,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.41" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -4756,43 +6477,33 @@ "type": "tidelift" } ], - "time": "2026-05-23T14:40:34+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.38.1", + "name": "symfony/process", + "version": "v6.4.41", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "dc21118016c039a66235cf93d96b435ffb282412" + "url": "https://github.com/symfony/process.git", + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", - "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "url": "https://api.github.com/repos/symfony/process/zipball/c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", "shasum": "" }, "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=8.1" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4800,30 +6511,18 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" + "source": "https://github.com/symfony/process/tree/v6.4.41" }, "funding": [ { @@ -4843,41 +6542,33 @@ "type": "tidelift" } ], - "time": "2026-05-25T15:22:23+00:00" + "time": "2026-05-23T13:47:21+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.38.2", + "name": "symfony/stopwatch", + "version": "v6.4.24", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b67e94e06a05d9572c2fa354483b3e13e3cb1898", + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" + "Symfony\\Component\\Stopwatch\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4886,24 +6577,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.24" }, "funding": [ { @@ -4923,7 +6608,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:51:48+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "theseer/tokenizer", diff --git a/resources/php-cs-fixer.php b/resources/php-cs-fixer.php new file mode 100644 index 00000000..32dff070 --- /dev/null +++ b/resources/php-cs-fixer.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +$finder = PhpCsFixer\Finder::create() + ->in( + [ + __DIR__ . '/../api', + __DIR__ . '/../cli', + __DIR__ . '/../library', + __DIR__ . '/../tests', + ] + ) + // Build artifacts - phpstan's container cache and this fixer's own cache + // file live here. phpcs excludes it for the same reason. + ->exclude('_output'); + +return (new PhpCsFixer\Config()) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + // declare_strict_types is a risky rule. + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setCacheFile(__DIR__ . '/../tests/_output/.php-cs-fixer.cache') + ->setRules( + [ + // The two rules below are a local addition on top of the ordering + // rules shared with the other Phalcon projects. They are kept here + // until the global coding standard is agreed, at which point they + // should move into the shared set rather than stay a divergence. + // PSR-12 (via phpcs) checks neither, so nothing else enforces them. + 'declare_strict_types' => true, + 'no_unused_imports' => true, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha', + 'imports_order' => ['class', 'function', 'const'], + ], + 'ordered_class_elements' => [ + 'sort_algorithm' => 'alpha', + 'order' => [ + 'use_trait', + 'case', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public_static', + 'property_protected_static', + 'property_private_static', + 'property_public', + 'property_protected', + 'property_private', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public_static', + 'method_protected_static', + 'method_private_static', + 'method_public', + 'method_protected', + 'method_private', + ], + ], + ] + ) + ->setFinder($finder); From 90d050c6ea7a03ada92bee23674e60a9acfe707e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:38:31 -0500 Subject: [PATCH 24/47] [#42] - reorganize source under src/ per pds/skeleton --- cli/cli.php => bin/cli | 6 ++++-- composer.json | 4 ++-- public/index.php | 2 +- .../migrations/20180508203845_add_users_table.php | 0 ...9232740_add_token_and_audience_fields_to_users.php | 0 .../20180510004748_add_token_id_in_users.php | 0 .../migrations/20180525210751_increase_token_size.php | 0 .../migrations/20180528011826_split_token_field.php | 0 .../20180604160513_add_token_password_in_users.php | 0 .../20180607165746_remove_token_fields_from_users.php | 0 .../20180612191624_rename_domain_to_issuer.php | 0 .../migrations/20180715202028_add_companies_table.php | 0 .../migrations/20180715221034_add_products_table.php | 0 .../20180717231009_add_product_types_table.php | 0 .../20180717231024_add_individuals_table.php | 0 .../20180717231029_add_individual_types_table.php | 0 ...20180717231052_add_companies_to_products_table.php | 0 .../20180717232102_add_product_type_to_products.php | 0 .../migrations/20180723152114_rename_table_fields.php | 0 phinx.php => resources/phinx.php | 7 +++---- resources/php-cs-fixer.php | 4 +--- resources/phpcs.xml | 5 ++--- resources/phpstan.neon | 4 +--- resources/phpunit.xml.dist | 4 +--- runCli | 6 ------ .../Api/Controllers}/BaseController.php | 0 .../Api/Controllers}/Companies/AddController.php | 0 .../Api/Controllers}/Companies/GetController.php | 0 .../Controllers}/IndividualTypes/GetController.php | 0 .../Api/Controllers}/Individuals/GetController.php | 0 .../Api/Controllers}/LoginController.php | 0 .../Api/Controllers}/ProductTypes/GetController.php | 0 .../Api/Controllers}/Products/GetController.php | 0 .../Api/Controllers}/Users/GetController.php | 0 {api/config => src/Api}/providers.php | 0 {library => src}/Bootstrap/AbstractBootstrap.php | 2 +- {library => src}/Bootstrap/Api.php | 0 {library => src}/Bootstrap/Cli.php | 2 +- {library => src}/Bootstrap/Tests.php | 0 {cli/tasks => src/Cli/Tasks}/ClearcacheTask.php | 0 {cli/tasks => src/Cli/Tasks}/MainTask.php | 2 +- {cli/config => src/Cli}/providers.php | 0 {library => src}/Constants/Flags.php | 0 {library => src}/Constants/Relationships.php | 0 {library => src}/Core/autoload.php | 11 +++++++---- {library => src}/Core/config.php | 0 {library => src}/Core/functions.php | 0 {library => src}/ErrorHandler.php | 0 {library => src}/Exception/Exception.php | 0 {library => src}/Exception/HttpException.php | 0 {library => src}/Exception/ModelException.php | 0 {library => src}/Http/Request.php | 0 {library => src}/Http/Response.php | 0 .../Middleware/AuthenticationMiddleware.php | 0 {library => src}/Middleware/NotFoundMiddleware.php | 0 {library => src}/Middleware/ResponseMiddleware.php | 0 {library => src}/Middleware/TokenBase.php | 0 {library => src}/Middleware/TokenUserMiddleware.php | 0 .../Middleware/TokenValidationMiddleware.php | 0 .../Middleware/TokenVerificationMiddleware.php | 0 {library => src}/Models/Companies.php | 0 {library => src}/Models/CompaniesXProducts.php | 0 {library => src}/Models/IndividualTypes.php | 0 {library => src}/Models/Individuals.php | 0 {library => src}/Models/ProductTypes.php | 0 {library => src}/Models/Products.php | 0 {library => src}/Models/Users.php | 0 {library => src}/Mvc/Model/AbstractModel.php | 0 {library => src}/Providers/CacheDataProvider.php | 0 {library => src}/Providers/CliDispatcherProvider.php | 0 {library => src}/Providers/ConfigProvider.php | 2 +- {library => src}/Providers/DatabaseProvider.php | 0 {library => src}/Providers/ErrorHandlerProvider.php | 0 {library => src}/Providers/LoggerProvider.php | 0 {library => src}/Providers/ModelsMetadataProvider.php | 0 {library => src}/Providers/RequestProvider.php | 0 {library => src}/Providers/ResponseProvider.php | 0 {library => src}/Providers/RouterProvider.php | 0 {library => src}/Traits/FractalTrait.php | 0 {library => src}/Traits/QueryTrait.php | 0 {library => src}/Traits/ResponseTrait.php | 0 {library => src}/Traits/TokenTrait.php | 0 {library => src}/Transformers/BaseTransformer.php | 0 .../Transformers/CompaniesTransformer.php | 0 .../Transformers/IndividualTypesTransformer.php | 0 .../Transformers/IndividualsTransformer.php | 0 .../Transformers/ProductTypesTransformer.php | 0 {library => src}/Transformers/ProductsTransformer.php | 0 {library => src}/Validation/CompaniesValidator.php | 0 tests/Cli/CheckHelpTaskTest.php | 2 +- tests/Integration/Library/Traits/QueryTest.php | 2 +- tests/Unit/Cli/BaseTest.php | 2 +- tests/Unit/Cli/BootstrapTest.php | 4 ++-- tests/Unit/Config/AutoloaderTest.php | 4 ++-- tests/Unit/Config/ConfigTest.php | 2 +- tests/Unit/Config/FunctionsTest.php | 4 ++-- tests/Unit/Config/ProvidersTest.php | 4 ++-- tests/bootstrap.php | 2 +- 98 files changed, 39 insertions(+), 48 deletions(-) rename cli/cli.php => bin/cli (78%) mode change 100644 => 100755 rename {storage/db => resources}/migrations/20180508203845_add_users_table.php (100%) rename {storage/db => resources}/migrations/20180509232740_add_token_and_audience_fields_to_users.php (100%) rename {storage/db => resources}/migrations/20180510004748_add_token_id_in_users.php (100%) rename {storage/db => resources}/migrations/20180525210751_increase_token_size.php (100%) rename {storage/db => resources}/migrations/20180528011826_split_token_field.php (100%) rename {storage/db => resources}/migrations/20180604160513_add_token_password_in_users.php (100%) rename {storage/db => resources}/migrations/20180607165746_remove_token_fields_from_users.php (100%) rename {storage/db => resources}/migrations/20180612191624_rename_domain_to_issuer.php (100%) rename {storage/db => resources}/migrations/20180715202028_add_companies_table.php (100%) rename {storage/db => resources}/migrations/20180715221034_add_products_table.php (100%) rename {storage/db => resources}/migrations/20180717231009_add_product_types_table.php (100%) rename {storage/db => resources}/migrations/20180717231024_add_individuals_table.php (100%) rename {storage/db => resources}/migrations/20180717231029_add_individual_types_table.php (100%) rename {storage/db => resources}/migrations/20180717231052_add_companies_to_products_table.php (100%) rename {storage/db => resources}/migrations/20180717232102_add_product_type_to_products.php (100%) rename {storage/db => resources}/migrations/20180723152114_rename_table_fields.php (100%) rename phinx.php => resources/phinx.php (88%) delete mode 100755 runCli rename {api/controllers => src/Api/Controllers}/BaseController.php (100%) rename {api/controllers => src/Api/Controllers}/Companies/AddController.php (100%) rename {api/controllers => src/Api/Controllers}/Companies/GetController.php (100%) rename {api/controllers => src/Api/Controllers}/IndividualTypes/GetController.php (100%) rename {api/controllers => src/Api/Controllers}/Individuals/GetController.php (100%) rename {api/controllers => src/Api/Controllers}/LoginController.php (100%) rename {api/controllers => src/Api/Controllers}/ProductTypes/GetController.php (100%) rename {api/controllers => src/Api/Controllers}/Products/GetController.php (100%) rename {api/controllers => src/Api/Controllers}/Users/GetController.php (100%) rename {api/config => src/Api}/providers.php (100%) rename {library => src}/Bootstrap/AbstractBootstrap.php (96%) rename {library => src}/Bootstrap/Api.php (100%) rename {library => src}/Bootstrap/Cli.php (96%) rename {library => src}/Bootstrap/Tests.php (100%) rename {cli/tasks => src/Cli/Tasks}/ClearcacheTask.php (100%) rename {cli/tasks => src/Cli/Tasks}/MainTask.php (95%) rename {cli/config => src/Cli}/providers.php (100%) rename {library => src}/Constants/Flags.php (100%) rename {library => src}/Constants/Relationships.php (100%) rename {library => src}/Core/autoload.php (65%) rename {library => src}/Core/config.php (100%) rename {library => src}/Core/functions.php (100%) rename {library => src}/ErrorHandler.php (100%) rename {library => src}/Exception/Exception.php (100%) rename {library => src}/Exception/HttpException.php (100%) rename {library => src}/Exception/ModelException.php (100%) rename {library => src}/Http/Request.php (100%) rename {library => src}/Http/Response.php (100%) rename {library => src}/Middleware/AuthenticationMiddleware.php (100%) rename {library => src}/Middleware/NotFoundMiddleware.php (100%) rename {library => src}/Middleware/ResponseMiddleware.php (100%) rename {library => src}/Middleware/TokenBase.php (100%) rename {library => src}/Middleware/TokenUserMiddleware.php (100%) rename {library => src}/Middleware/TokenValidationMiddleware.php (100%) rename {library => src}/Middleware/TokenVerificationMiddleware.php (100%) rename {library => src}/Models/Companies.php (100%) rename {library => src}/Models/CompaniesXProducts.php (100%) rename {library => src}/Models/IndividualTypes.php (100%) rename {library => src}/Models/Individuals.php (100%) rename {library => src}/Models/ProductTypes.php (100%) rename {library => src}/Models/Products.php (100%) rename {library => src}/Models/Users.php (100%) rename {library => src}/Mvc/Model/AbstractModel.php (100%) rename {library => src}/Providers/CacheDataProvider.php (100%) rename {library => src}/Providers/CliDispatcherProvider.php (100%) rename {library => src}/Providers/ConfigProvider.php (91%) rename {library => src}/Providers/DatabaseProvider.php (100%) rename {library => src}/Providers/ErrorHandlerProvider.php (100%) rename {library => src}/Providers/LoggerProvider.php (100%) rename {library => src}/Providers/ModelsMetadataProvider.php (100%) rename {library => src}/Providers/RequestProvider.php (100%) rename {library => src}/Providers/ResponseProvider.php (100%) rename {library => src}/Providers/RouterProvider.php (100%) rename {library => src}/Traits/FractalTrait.php (100%) rename {library => src}/Traits/QueryTrait.php (100%) rename {library => src}/Traits/ResponseTrait.php (100%) rename {library => src}/Traits/TokenTrait.php (100%) rename {library => src}/Transformers/BaseTransformer.php (100%) rename {library => src}/Transformers/CompaniesTransformer.php (100%) rename {library => src}/Transformers/IndividualTypesTransformer.php (100%) rename {library => src}/Transformers/IndividualsTransformer.php (100%) rename {library => src}/Transformers/ProductTypesTransformer.php (100%) rename {library => src}/Transformers/ProductsTransformer.php (100%) rename {library => src}/Validation/CompaniesValidator.php (100%) diff --git a/cli/cli.php b/bin/cli old mode 100644 new mode 100755 similarity index 78% rename from cli/cli.php rename to bin/cli index b6dfbbda..76ec92ac --- a/cli/cli.php +++ b/bin/cli @@ -1,5 +1,5 @@ +#!/usr/bin/env php run(); diff --git a/composer.json b/composer.json index 3d8a9e24..5bdd7063 100644 --- a/composer.json +++ b/composer.json @@ -53,8 +53,8 @@ "test": "talon run", "test-coverage": "talon run unit -- --coverage-clover tests/_output/coverage.xml", "test-coverage-html": "talon run unit -- --coverage-html tests/_output/coverage", - "migrate": "phinx migrate -c phinx.php", - "seed": "phinx seed:run -c phinx.php", + "migrate": "phinx migrate -c resources/phinx.php", + "seed": "phinx seed:run -c resources/phinx.php", "post-create-project-cmd": [ "@php -r \"file_exists('.env') || copy('resources/.env.example', '.env');\"", "@php -r \"echo PHP_EOL . 'The Phalcon REST API is ready (.env created from resources/.env.example). Next steps:' . PHP_EOL . ' docker: docker compose up -d --build, then composer install && composer migrate' . PHP_EOL . ' tests: vendor/bin/talon run' . PHP_EOL;\"" diff --git a/public/index.php b/public/index.php index b569822b..9e94f009 100644 --- a/public/index.php +++ b/public/index.php @@ -12,6 +12,6 @@ use Phalcon\Api\Bootstrap\Api; -require_once dirname(__FILE__, 2) . '/library/Core/autoload.php'; +require_once dirname(__FILE__, 2) . '/src/Core/autoload.php'; (new Api())->run(); diff --git a/storage/db/migrations/20180508203845_add_users_table.php b/resources/migrations/20180508203845_add_users_table.php similarity index 100% rename from storage/db/migrations/20180508203845_add_users_table.php rename to resources/migrations/20180508203845_add_users_table.php diff --git a/storage/db/migrations/20180509232740_add_token_and_audience_fields_to_users.php b/resources/migrations/20180509232740_add_token_and_audience_fields_to_users.php similarity index 100% rename from storage/db/migrations/20180509232740_add_token_and_audience_fields_to_users.php rename to resources/migrations/20180509232740_add_token_and_audience_fields_to_users.php diff --git a/storage/db/migrations/20180510004748_add_token_id_in_users.php b/resources/migrations/20180510004748_add_token_id_in_users.php similarity index 100% rename from storage/db/migrations/20180510004748_add_token_id_in_users.php rename to resources/migrations/20180510004748_add_token_id_in_users.php diff --git a/storage/db/migrations/20180525210751_increase_token_size.php b/resources/migrations/20180525210751_increase_token_size.php similarity index 100% rename from storage/db/migrations/20180525210751_increase_token_size.php rename to resources/migrations/20180525210751_increase_token_size.php diff --git a/storage/db/migrations/20180528011826_split_token_field.php b/resources/migrations/20180528011826_split_token_field.php similarity index 100% rename from storage/db/migrations/20180528011826_split_token_field.php rename to resources/migrations/20180528011826_split_token_field.php diff --git a/storage/db/migrations/20180604160513_add_token_password_in_users.php b/resources/migrations/20180604160513_add_token_password_in_users.php similarity index 100% rename from storage/db/migrations/20180604160513_add_token_password_in_users.php rename to resources/migrations/20180604160513_add_token_password_in_users.php diff --git a/storage/db/migrations/20180607165746_remove_token_fields_from_users.php b/resources/migrations/20180607165746_remove_token_fields_from_users.php similarity index 100% rename from storage/db/migrations/20180607165746_remove_token_fields_from_users.php rename to resources/migrations/20180607165746_remove_token_fields_from_users.php diff --git a/storage/db/migrations/20180612191624_rename_domain_to_issuer.php b/resources/migrations/20180612191624_rename_domain_to_issuer.php similarity index 100% rename from storage/db/migrations/20180612191624_rename_domain_to_issuer.php rename to resources/migrations/20180612191624_rename_domain_to_issuer.php diff --git a/storage/db/migrations/20180715202028_add_companies_table.php b/resources/migrations/20180715202028_add_companies_table.php similarity index 100% rename from storage/db/migrations/20180715202028_add_companies_table.php rename to resources/migrations/20180715202028_add_companies_table.php diff --git a/storage/db/migrations/20180715221034_add_products_table.php b/resources/migrations/20180715221034_add_products_table.php similarity index 100% rename from storage/db/migrations/20180715221034_add_products_table.php rename to resources/migrations/20180715221034_add_products_table.php diff --git a/storage/db/migrations/20180717231009_add_product_types_table.php b/resources/migrations/20180717231009_add_product_types_table.php similarity index 100% rename from storage/db/migrations/20180717231009_add_product_types_table.php rename to resources/migrations/20180717231009_add_product_types_table.php diff --git a/storage/db/migrations/20180717231024_add_individuals_table.php b/resources/migrations/20180717231024_add_individuals_table.php similarity index 100% rename from storage/db/migrations/20180717231024_add_individuals_table.php rename to resources/migrations/20180717231024_add_individuals_table.php diff --git a/storage/db/migrations/20180717231029_add_individual_types_table.php b/resources/migrations/20180717231029_add_individual_types_table.php similarity index 100% rename from storage/db/migrations/20180717231029_add_individual_types_table.php rename to resources/migrations/20180717231029_add_individual_types_table.php diff --git a/storage/db/migrations/20180717231052_add_companies_to_products_table.php b/resources/migrations/20180717231052_add_companies_to_products_table.php similarity index 100% rename from storage/db/migrations/20180717231052_add_companies_to_products_table.php rename to resources/migrations/20180717231052_add_companies_to_products_table.php diff --git a/storage/db/migrations/20180717232102_add_product_type_to_products.php b/resources/migrations/20180717232102_add_product_type_to_products.php similarity index 100% rename from storage/db/migrations/20180717232102_add_product_type_to_products.php rename to resources/migrations/20180717232102_add_product_type_to_products.php diff --git a/storage/db/migrations/20180723152114_rename_table_fields.php b/resources/migrations/20180723152114_rename_table_fields.php similarity index 100% rename from storage/db/migrations/20180723152114_rename_table_fields.php rename to resources/migrations/20180723152114_rename_table_fields.php diff --git a/phinx.php b/resources/phinx.php similarity index 88% rename from phinx.php rename to resources/phinx.php index 5d021a69..91d54d5f 100644 --- a/phinx.php +++ b/resources/phinx.php @@ -3,13 +3,12 @@ use function Phalcon\Api\Core\appPath; use function Phalcon\Api\Core\envValue; -// This file lives in the root of the project -require_once dirname(__FILE__) . '/library/Core/autoload.php'; +require_once dirname(__FILE__, 2) . '/src/Core/autoload.php'; return [ 'paths' => [ - 'migrations' => appPath('/storage/db/migrations'), - 'seeds' => appPath('/storage/db/seeds'), + 'migrations' => appPath('/resources/migrations'), + 'seeds' => appPath('/resources/seeds'), ], 'environments' => [ 'default_migration_table' => 'ut_migrations', diff --git a/resources/php-cs-fixer.php b/resources/php-cs-fixer.php index 32dff070..a687f4d2 100644 --- a/resources/php-cs-fixer.php +++ b/resources/php-cs-fixer.php @@ -14,9 +14,7 @@ $finder = PhpCsFixer\Finder::create() ->in( [ - __DIR__ . '/../api', - __DIR__ . '/../cli', - __DIR__ . '/../library', + __DIR__ . '/../src', __DIR__ . '/../tests', ] ) diff --git a/resources/phpcs.xml b/resources/phpcs.xml index 987c155b..04305cd8 100644 --- a/resources/phpcs.xml +++ b/resources/phpcs.xml @@ -11,8 +11,7 @@ */public/* */_output/* - ../api - ../cli - ../library + ../bin + ../src ../tests diff --git a/resources/phpstan.neon b/resources/phpstan.neon index dd784e97..36b412cc 100644 --- a/resources/phpstan.neon +++ b/resources/phpstan.neon @@ -1,8 +1,6 @@ parameters: paths: - - ../api - - ../cli - - ../library + - ../src - ../public/index.php level: max reportUnmatchedIgnoredErrors: false diff --git a/resources/phpunit.xml.dist b/resources/phpunit.xml.dist index 6e2513d0..d8de0499 100644 --- a/resources/phpunit.xml.dist +++ b/resources/phpunit.xml.dist @@ -18,9 +18,7 @@ - ../api - ../cli - ../library + ../src diff --git a/runCli b/runCli deleted file mode 100755 index 0973eeb6..00000000 --- a/runCli +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -EXEC_PATH="`dirname \"$0\"`" - -PHP_BINARY="`which php`" -$PHP_BINARY $EXEC_PATH/cli/cli.php $* \ No newline at end of file diff --git a/api/controllers/BaseController.php b/src/Api/Controllers/BaseController.php similarity index 100% rename from api/controllers/BaseController.php rename to src/Api/Controllers/BaseController.php diff --git a/api/controllers/Companies/AddController.php b/src/Api/Controllers/Companies/AddController.php similarity index 100% rename from api/controllers/Companies/AddController.php rename to src/Api/Controllers/Companies/AddController.php diff --git a/api/controllers/Companies/GetController.php b/src/Api/Controllers/Companies/GetController.php similarity index 100% rename from api/controllers/Companies/GetController.php rename to src/Api/Controllers/Companies/GetController.php diff --git a/api/controllers/IndividualTypes/GetController.php b/src/Api/Controllers/IndividualTypes/GetController.php similarity index 100% rename from api/controllers/IndividualTypes/GetController.php rename to src/Api/Controllers/IndividualTypes/GetController.php diff --git a/api/controllers/Individuals/GetController.php b/src/Api/Controllers/Individuals/GetController.php similarity index 100% rename from api/controllers/Individuals/GetController.php rename to src/Api/Controllers/Individuals/GetController.php diff --git a/api/controllers/LoginController.php b/src/Api/Controllers/LoginController.php similarity index 100% rename from api/controllers/LoginController.php rename to src/Api/Controllers/LoginController.php diff --git a/api/controllers/ProductTypes/GetController.php b/src/Api/Controllers/ProductTypes/GetController.php similarity index 100% rename from api/controllers/ProductTypes/GetController.php rename to src/Api/Controllers/ProductTypes/GetController.php diff --git a/api/controllers/Products/GetController.php b/src/Api/Controllers/Products/GetController.php similarity index 100% rename from api/controllers/Products/GetController.php rename to src/Api/Controllers/Products/GetController.php diff --git a/api/controllers/Users/GetController.php b/src/Api/Controllers/Users/GetController.php similarity index 100% rename from api/controllers/Users/GetController.php rename to src/Api/Controllers/Users/GetController.php diff --git a/api/config/providers.php b/src/Api/providers.php similarity index 100% rename from api/config/providers.php rename to src/Api/providers.php diff --git a/library/Bootstrap/AbstractBootstrap.php b/src/Bootstrap/AbstractBootstrap.php similarity index 96% rename from library/Bootstrap/AbstractBootstrap.php rename to src/Bootstrap/AbstractBootstrap.php index d620287a..3126ad71 100644 --- a/library/Bootstrap/AbstractBootstrap.php +++ b/src/Bootstrap/AbstractBootstrap.php @@ -48,7 +48,7 @@ public function __construct() } if ([] === $this->providers) { - $this->providers = require appPath('api/config/providers.php'); + $this->providers = require appPath('src/Api/providers.php'); } $this diff --git a/library/Bootstrap/Api.php b/src/Bootstrap/Api.php similarity index 100% rename from library/Bootstrap/Api.php rename to src/Bootstrap/Api.php diff --git a/library/Bootstrap/Cli.php b/src/Bootstrap/Cli.php similarity index 96% rename from library/Bootstrap/Cli.php rename to src/Bootstrap/Cli.php index 5ac03fab..816c0b61 100644 --- a/library/Bootstrap/Cli.php +++ b/src/Bootstrap/Cli.php @@ -31,7 +31,7 @@ class Cli extends AbstractBootstrap public function __construct() { $this->container = new PhCli(); - $this->providers = require appPath('cli/config/providers.php'); + $this->providers = require appPath('src/Cli/providers.php'); $this->processArguments(); diff --git a/library/Bootstrap/Tests.php b/src/Bootstrap/Tests.php similarity index 100% rename from library/Bootstrap/Tests.php rename to src/Bootstrap/Tests.php diff --git a/cli/tasks/ClearcacheTask.php b/src/Cli/Tasks/ClearcacheTask.php similarity index 100% rename from cli/tasks/ClearcacheTask.php rename to src/Cli/Tasks/ClearcacheTask.php diff --git a/cli/tasks/MainTask.php b/src/Cli/Tasks/MainTask.php similarity index 95% rename from cli/tasks/MainTask.php rename to src/Cli/Tasks/MainTask.php index 330d82ad..08080dd9 100755 --- a/cli/tasks/MainTask.php +++ b/src/Cli/Tasks/MainTask.php @@ -31,7 +31,7 @@ public function mainAction() . " Phalcon Team | (C) {$year}" . PHP_EOL . "******************************************************" . PHP_EOL . "" . PHP_EOL - . "Usage: runCli " . PHP_EOL + . "Usage: bin/cli " . PHP_EOL . "" . PHP_EOL . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL diff --git a/cli/config/providers.php b/src/Cli/providers.php similarity index 100% rename from cli/config/providers.php rename to src/Cli/providers.php diff --git a/library/Constants/Flags.php b/src/Constants/Flags.php similarity index 100% rename from library/Constants/Flags.php rename to src/Constants/Flags.php diff --git a/library/Constants/Relationships.php b/src/Constants/Relationships.php similarity index 100% rename from library/Constants/Relationships.php rename to src/Constants/Relationships.php diff --git a/library/Core/autoload.php b/src/Core/autoload.php similarity index 65% rename from library/Core/autoload.php rename to src/Core/autoload.php index fc37988f..57f561ef 100644 --- a/library/Core/autoload.php +++ b/src/Core/autoload.php @@ -19,12 +19,15 @@ // Register the autoloader require __DIR__ . '/functions.php'; +/** + * The loader resolves by namespace prefix, so a single entry covers the whole + * source tree: Phalcon\Api\Models\Users -> src/Models/Users.php, and + * Phalcon\Api\Api\Controllers\LoginController -> src/Api/Controllers/LoginController.php. + */ $loader = new Loader(); $namespaces = [ - 'Phalcon\Api' => appPath('/library'), - 'Phalcon\Api\Api\Controllers' => appPath('/api/controllers'), - 'Phalcon\Api\Cli\Tasks' => appPath('/cli/tasks'), - 'Phalcon\Api\Tests' => appPath('/tests'), + 'Phalcon\Api' => appPath('/src'), + 'Phalcon\Api\Tests' => appPath('/tests'), ]; $loader->setNamespaces($namespaces); diff --git a/library/Core/config.php b/src/Core/config.php similarity index 100% rename from library/Core/config.php rename to src/Core/config.php diff --git a/library/Core/functions.php b/src/Core/functions.php similarity index 100% rename from library/Core/functions.php rename to src/Core/functions.php diff --git a/library/ErrorHandler.php b/src/ErrorHandler.php similarity index 100% rename from library/ErrorHandler.php rename to src/ErrorHandler.php diff --git a/library/Exception/Exception.php b/src/Exception/Exception.php similarity index 100% rename from library/Exception/Exception.php rename to src/Exception/Exception.php diff --git a/library/Exception/HttpException.php b/src/Exception/HttpException.php similarity index 100% rename from library/Exception/HttpException.php rename to src/Exception/HttpException.php diff --git a/library/Exception/ModelException.php b/src/Exception/ModelException.php similarity index 100% rename from library/Exception/ModelException.php rename to src/Exception/ModelException.php diff --git a/library/Http/Request.php b/src/Http/Request.php similarity index 100% rename from library/Http/Request.php rename to src/Http/Request.php diff --git a/library/Http/Response.php b/src/Http/Response.php similarity index 100% rename from library/Http/Response.php rename to src/Http/Response.php diff --git a/library/Middleware/AuthenticationMiddleware.php b/src/Middleware/AuthenticationMiddleware.php similarity index 100% rename from library/Middleware/AuthenticationMiddleware.php rename to src/Middleware/AuthenticationMiddleware.php diff --git a/library/Middleware/NotFoundMiddleware.php b/src/Middleware/NotFoundMiddleware.php similarity index 100% rename from library/Middleware/NotFoundMiddleware.php rename to src/Middleware/NotFoundMiddleware.php diff --git a/library/Middleware/ResponseMiddleware.php b/src/Middleware/ResponseMiddleware.php similarity index 100% rename from library/Middleware/ResponseMiddleware.php rename to src/Middleware/ResponseMiddleware.php diff --git a/library/Middleware/TokenBase.php b/src/Middleware/TokenBase.php similarity index 100% rename from library/Middleware/TokenBase.php rename to src/Middleware/TokenBase.php diff --git a/library/Middleware/TokenUserMiddleware.php b/src/Middleware/TokenUserMiddleware.php similarity index 100% rename from library/Middleware/TokenUserMiddleware.php rename to src/Middleware/TokenUserMiddleware.php diff --git a/library/Middleware/TokenValidationMiddleware.php b/src/Middleware/TokenValidationMiddleware.php similarity index 100% rename from library/Middleware/TokenValidationMiddleware.php rename to src/Middleware/TokenValidationMiddleware.php diff --git a/library/Middleware/TokenVerificationMiddleware.php b/src/Middleware/TokenVerificationMiddleware.php similarity index 100% rename from library/Middleware/TokenVerificationMiddleware.php rename to src/Middleware/TokenVerificationMiddleware.php diff --git a/library/Models/Companies.php b/src/Models/Companies.php similarity index 100% rename from library/Models/Companies.php rename to src/Models/Companies.php diff --git a/library/Models/CompaniesXProducts.php b/src/Models/CompaniesXProducts.php similarity index 100% rename from library/Models/CompaniesXProducts.php rename to src/Models/CompaniesXProducts.php diff --git a/library/Models/IndividualTypes.php b/src/Models/IndividualTypes.php similarity index 100% rename from library/Models/IndividualTypes.php rename to src/Models/IndividualTypes.php diff --git a/library/Models/Individuals.php b/src/Models/Individuals.php similarity index 100% rename from library/Models/Individuals.php rename to src/Models/Individuals.php diff --git a/library/Models/ProductTypes.php b/src/Models/ProductTypes.php similarity index 100% rename from library/Models/ProductTypes.php rename to src/Models/ProductTypes.php diff --git a/library/Models/Products.php b/src/Models/Products.php similarity index 100% rename from library/Models/Products.php rename to src/Models/Products.php diff --git a/library/Models/Users.php b/src/Models/Users.php similarity index 100% rename from library/Models/Users.php rename to src/Models/Users.php diff --git a/library/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php similarity index 100% rename from library/Mvc/Model/AbstractModel.php rename to src/Mvc/Model/AbstractModel.php diff --git a/library/Providers/CacheDataProvider.php b/src/Providers/CacheDataProvider.php similarity index 100% rename from library/Providers/CacheDataProvider.php rename to src/Providers/CacheDataProvider.php diff --git a/library/Providers/CliDispatcherProvider.php b/src/Providers/CliDispatcherProvider.php similarity index 100% rename from library/Providers/CliDispatcherProvider.php rename to src/Providers/CliDispatcherProvider.php diff --git a/library/Providers/ConfigProvider.php b/src/Providers/ConfigProvider.php similarity index 91% rename from library/Providers/ConfigProvider.php rename to src/Providers/ConfigProvider.php index 3b21acca..8d359006 100644 --- a/library/Providers/ConfigProvider.php +++ b/src/Providers/ConfigProvider.php @@ -29,7 +29,7 @@ public function register(DiInterface $container): void $container->setShared( 'config', function () { - $data = require appPath('library/Core/config.php'); + $data = require appPath('src/Core/config.php'); return new Config($data); } diff --git a/library/Providers/DatabaseProvider.php b/src/Providers/DatabaseProvider.php similarity index 100% rename from library/Providers/DatabaseProvider.php rename to src/Providers/DatabaseProvider.php diff --git a/library/Providers/ErrorHandlerProvider.php b/src/Providers/ErrorHandlerProvider.php similarity index 100% rename from library/Providers/ErrorHandlerProvider.php rename to src/Providers/ErrorHandlerProvider.php diff --git a/library/Providers/LoggerProvider.php b/src/Providers/LoggerProvider.php similarity index 100% rename from library/Providers/LoggerProvider.php rename to src/Providers/LoggerProvider.php diff --git a/library/Providers/ModelsMetadataProvider.php b/src/Providers/ModelsMetadataProvider.php similarity index 100% rename from library/Providers/ModelsMetadataProvider.php rename to src/Providers/ModelsMetadataProvider.php diff --git a/library/Providers/RequestProvider.php b/src/Providers/RequestProvider.php similarity index 100% rename from library/Providers/RequestProvider.php rename to src/Providers/RequestProvider.php diff --git a/library/Providers/ResponseProvider.php b/src/Providers/ResponseProvider.php similarity index 100% rename from library/Providers/ResponseProvider.php rename to src/Providers/ResponseProvider.php diff --git a/library/Providers/RouterProvider.php b/src/Providers/RouterProvider.php similarity index 100% rename from library/Providers/RouterProvider.php rename to src/Providers/RouterProvider.php diff --git a/library/Traits/FractalTrait.php b/src/Traits/FractalTrait.php similarity index 100% rename from library/Traits/FractalTrait.php rename to src/Traits/FractalTrait.php diff --git a/library/Traits/QueryTrait.php b/src/Traits/QueryTrait.php similarity index 100% rename from library/Traits/QueryTrait.php rename to src/Traits/QueryTrait.php diff --git a/library/Traits/ResponseTrait.php b/src/Traits/ResponseTrait.php similarity index 100% rename from library/Traits/ResponseTrait.php rename to src/Traits/ResponseTrait.php diff --git a/library/Traits/TokenTrait.php b/src/Traits/TokenTrait.php similarity index 100% rename from library/Traits/TokenTrait.php rename to src/Traits/TokenTrait.php diff --git a/library/Transformers/BaseTransformer.php b/src/Transformers/BaseTransformer.php similarity index 100% rename from library/Transformers/BaseTransformer.php rename to src/Transformers/BaseTransformer.php diff --git a/library/Transformers/CompaniesTransformer.php b/src/Transformers/CompaniesTransformer.php similarity index 100% rename from library/Transformers/CompaniesTransformer.php rename to src/Transformers/CompaniesTransformer.php diff --git a/library/Transformers/IndividualTypesTransformer.php b/src/Transformers/IndividualTypesTransformer.php similarity index 100% rename from library/Transformers/IndividualTypesTransformer.php rename to src/Transformers/IndividualTypesTransformer.php diff --git a/library/Transformers/IndividualsTransformer.php b/src/Transformers/IndividualsTransformer.php similarity index 100% rename from library/Transformers/IndividualsTransformer.php rename to src/Transformers/IndividualsTransformer.php diff --git a/library/Transformers/ProductTypesTransformer.php b/src/Transformers/ProductTypesTransformer.php similarity index 100% rename from library/Transformers/ProductTypesTransformer.php rename to src/Transformers/ProductTypesTransformer.php diff --git a/library/Transformers/ProductsTransformer.php b/src/Transformers/ProductsTransformer.php similarity index 100% rename from library/Transformers/ProductsTransformer.php rename to src/Transformers/ProductsTransformer.php diff --git a/library/Validation/CompaniesValidator.php b/src/Validation/CompaniesValidator.php similarity index 100% rename from library/Validation/CompaniesValidator.php rename to src/Validation/CompaniesValidator.php diff --git a/tests/Cli/CheckHelpTaskTest.php b/tests/Cli/CheckHelpTaskTest.php index 97de12bf..02fd0ca4 100644 --- a/tests/Cli/CheckHelpTaskTest.php +++ b/tests/Cli/CheckHelpTaskTest.php @@ -34,7 +34,7 @@ public function testCheckHelp(): void $output = []; $exitCode = 1; - exec(dirname(__FILE__, 3) . '/runCli 2>&1', $output, $exitCode); + exec(dirname(__FILE__, 3) . '/bin/cli 2>&1', $output, $exitCode); $shellOutput = implode(PHP_EOL, $output); diff --git a/tests/Integration/Library/Traits/QueryTest.php b/tests/Integration/Library/Traits/QueryTest.php index 18c91237..4236e73c 100644 --- a/tests/Integration/Library/Traits/QueryTest.php +++ b/tests/Integration/Library/Traits/QueryTest.php @@ -128,7 +128,7 @@ public function testGetUserByWrongTokenReturnsNull(): void public function testGetCompaniesCachedData(): void { - $configData = require appPath('./library/Core/config.php'); + $configData = require appPath('./src/Core/config.php'); $this->assertTrue($configData['app']['devMode']); $configData['app']['devMode'] = false; diff --git a/tests/Unit/Cli/BaseTest.php b/tests/Unit/Cli/BaseTest.php index 3cef5af5..8359c824 100644 --- a/tests/Unit/Cli/BaseTest.php +++ b/tests/Unit/Cli/BaseTest.php @@ -42,7 +42,7 @@ public function testOutput(): void . " Phalcon Team | (C) {$year}" . PHP_EOL . "******************************************************" . PHP_EOL . "" . PHP_EOL - . "Usage: runCli " . PHP_EOL + . "Usage: bin/cli " . PHP_EOL . "" . PHP_EOL . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL diff --git a/tests/Unit/Cli/BootstrapTest.php b/tests/Unit/Cli/BootstrapTest.php index 244ff2cf..e48f85dc 100644 --- a/tests/Unit/Cli/BootstrapTest.php +++ b/tests/Unit/Cli/BootstrapTest.php @@ -24,7 +24,7 @@ final class BootstrapTest extends AbstractUnitTestCase public function testBootstrap(): void { ob_start(); - require appPath('cli/cli.php'); + require appPath('bin/cli'); $actual = ob_get_contents(); ob_end_clean(); @@ -34,7 +34,7 @@ public function testBootstrap(): void . " Phalcon Team | (C) {$year}" . PHP_EOL . "******************************************************" . PHP_EOL . "" . PHP_EOL - . "Usage: runCli " . PHP_EOL + . "Usage: bin/cli " . PHP_EOL . "" . PHP_EOL . " --help \e[0;32m(safe)\e[0m shows the help screen/available commands" . PHP_EOL . " --clear-cache \e[0;32m(safe)\e[0m clears the cache folders" . PHP_EOL diff --git a/tests/Unit/Config/AutoloaderTest.php b/tests/Unit/Config/AutoloaderTest.php index a991e104..cb1829fe 100644 --- a/tests/Unit/Config/AutoloaderTest.php +++ b/tests/Unit/Config/AutoloaderTest.php @@ -23,7 +23,7 @@ final class AutoloaderTest extends AbstractUnitTestCase { public function testAutoloader(): void { - require appPath('library/Core/autoload.php'); + require appPath('src/Core/autoload.php'); $class = new Response(); $this->assertTrue($class instanceof Response); @@ -32,7 +32,7 @@ public function testAutoloader(): void public function testDotenvVariables(): void { - require appPath('library/Core/autoload.php'); + require appPath('src/Core/autoload.php'); $this->assertNotEquals(false, $_ENV['APP_DEBUG']); $this->assertNotEquals(false, $_ENV['APP_ENV']); diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php index ba6d62a1..0a6ad949 100644 --- a/tests/Unit/Config/ConfigTest.php +++ b/tests/Unit/Config/ConfigTest.php @@ -22,7 +22,7 @@ final class ConfigTest extends AbstractUnitTestCase { public function testConfig(): void { - $config = require(appPath('library/Core/config.php')); + $config = require(appPath('src/Core/config.php')); $this->assertTrue(is_array($config)); $this->assertTrue(isset($config['app'])); diff --git a/tests/Unit/Config/FunctionsTest.php b/tests/Unit/Config/FunctionsTest.php index 55609e10..a91dc144 100644 --- a/tests/Unit/Config/FunctionsTest.php +++ b/tests/Unit/Config/FunctionsTest.php @@ -47,9 +47,9 @@ public function testAppPath(): void public function testAppPathWithParameter(): void { - $path = dirname(__FILE__, 4) . '/library/Core/config.php'; + $path = dirname(__FILE__, 4) . '/src/Core/config.php'; - $this->assertSame($path, appPath('library/Core/config.php')); + $this->assertSame($path, appPath('src/Core/config.php')); } public function testEnvValueAsFalse(): void diff --git a/tests/Unit/Config/ProvidersTest.php b/tests/Unit/Config/ProvidersTest.php index bc7bd3cc..3429f0e4 100644 --- a/tests/Unit/Config/ProvidersTest.php +++ b/tests/Unit/Config/ProvidersTest.php @@ -30,7 +30,7 @@ final class ProvidersTest extends AbstractUnitTestCase { public function testApiProviders(): void { - $providers = require(appPath('api/config/providers.php')); + $providers = require(appPath('src/Api/providers.php')); $this->assertSame(ConfigProvider::class, $providers[0]); $this->assertSame(LoggerProvider::class, $providers[1]); @@ -44,7 +44,7 @@ public function testApiProviders(): void public function testCliProviders(): void { - $providers = require(appPath('cli/config/providers.php')); + $providers = require(appPath('src/Cli/providers.php')); $this->assertSame(ConfigProvider::class, $providers[0]); $this->assertSame(LoggerProvider::class, $providers[1]); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8b200f78..1891403f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,7 +26,7 @@ * Registers the application namespaces, pulls in the Composer autoloader and * loads the root .env. */ -require_once dirname(__FILE__, 2) . '/library/Core/autoload.php'; +require_once dirname(__FILE__, 2) . '/src/Core/autoload.php'; /** * Talon reads DATA_MYSQL_* / DATA_REDIS_*, while the application reads From 5e7b64c2535a41148185c25796f0ba8aaba8734f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:53:24 -0500 Subject: [PATCH 25/47] [#42] - apply phpcs and php-cs-fixer fixes --- composer.json | 2 + resources/php-cs-fixer.php | 20 ++- src/Api/Controllers/BaseController.php | 6 +- .../Controllers/Companies/GetController.php | 5 +- .../IndividualTypes/GetController.php | 5 +- .../Controllers/Individuals/GetController.php | 11 +- src/Api/Controllers/LoginController.php | 2 +- .../ProductTypes/GetController.php | 5 +- .../Controllers/Products/GetController.php | 5 +- src/Api/Controllers/Users/GetController.php | 6 +- src/Cli/Tasks/ClearcacheTask.php | 4 +- src/Cli/Tasks/MainTask.php | 3 +- src/Http/Response.php | 18 +-- src/Middleware/AuthenticationMiddleware.php | 2 +- src/Middleware/TokenBase.php | 2 +- src/Middleware/TokenValidationMiddleware.php | 3 - .../TokenVerificationMiddleware.php | 3 +- src/Models/Companies.php | 31 ++-- src/Models/CompaniesXProducts.php | 25 ++- src/Models/IndividualTypes.php | 27 ++-- src/Models/Individuals.php | 37 +++-- src/Models/ProductTypes.php | 27 ++-- src/Models/Products.php | 33 ++-- src/Models/Users.php | 22 +-- src/Mvc/Model/AbstractModel.php | 29 ++-- src/Providers/RouterProvider.php | 54 +++---- src/Traits/QueryTrait.php | 73 +++++---- src/Traits/TokenTrait.php | 18 +-- .../AbstractIntegrationTestCase.php | 54 +++---- tests/Integration/Library/ModelTest.php | 133 ++++++++-------- .../Library/Models/CompaniesTest.php | 25 ++- .../Library/Models/CompaniesXProductsTest.php | 19 ++- .../Library/Models/IndividualTypesTest.php | 21 ++- .../Library/Models/IndividualsTest.php | 31 ++-- .../Library/Models/ProductTypesTest.php | 21 ++- .../Library/Models/ProductsTest.php | 27 ++-- .../Integration/Library/Models/UsersTest.php | 62 ++++---- .../Integration/Library/Traits/QueryTest.php | 142 +++++++++--------- tests/Support/Data.php | 102 ++++++------- tests/Unit/Config/FunctionsTest.php | 16 +- tests/_data/dump.sql | 0 41 files changed, 559 insertions(+), 572 deletions(-) delete mode 100644 tests/_data/dump.sql diff --git a/composer.json b/composer.json index 5bdd7063..f9c1a082 100644 --- a/composer.json +++ b/composer.json @@ -50,6 +50,8 @@ "analyze": "phpstan analyse -c resources/phpstan.neon --memory-limit 1024M", "cs": "phpcs --standard=resources/phpcs.xml", "cs-fix": "phpcbf --standard=resources/phpcs.xml", + "cs-fixer": "php-cs-fixer fix --config=resources/php-cs-fixer.php --dry-run --diff", + "cs-fixer-fix": "php-cs-fixer fix --config=resources/php-cs-fixer.php", "test": "talon run", "test-coverage": "talon run unit -- --coverage-clover tests/_output/coverage.xml", "test-coverage-html": "talon run unit -- --coverage-html tests/_output/coverage", diff --git a/resources/php-cs-fixer.php b/resources/php-cs-fixer.php index a687f4d2..20c969d2 100644 --- a/resources/php-cs-fixer.php +++ b/resources/php-cs-fixer.php @@ -11,23 +11,31 @@ declare(strict_types=1); -$finder = PhpCsFixer\Finder::create() +use PhpCsFixer\Config; +use PhpCsFixer\Finder; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; + +use function dirname; + +$root = dirname(__FILE__, 2); + +$finder = Finder::create() ->in( [ - __DIR__ . '/../src', - __DIR__ . '/../tests', + $root . '/src', + $root . '/tests', ] ) // Build artifacts - phpstan's container cache and this fixer's own cache // file live here. phpcs excludes it for the same reason. ->exclude('_output'); -return (new PhpCsFixer\Config()) - ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) +return (new Config()) + ->setParallelConfig(ParallelConfigFactory::detect()) // declare_strict_types is a risky rule. ->setRiskyAllowed(true) ->setUsingCache(true) - ->setCacheFile(__DIR__ . '/../tests/_output/.php-cs-fixer.cache') + ->setCacheFile($root . '/tests/_output/.php-cs-fixer.cache') ->setRules( [ // The two rules below are a local addition on top of the ordering diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index ae095aa9..5b5d5f7a 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -47,15 +47,15 @@ class BaseController extends Controller use QueryTrait; use ResponseTrait; - /** @var string */ - protected string $model = ''; - /** @var array */ protected array $includes = []; /** @var string */ protected string $method = 'collection'; + /** @var string */ + protected string $model = ''; + /** @var string */ protected string $orderBy = 'name'; diff --git a/src/Api/Controllers/Companies/GetController.php b/src/Api/Controllers/Companies/GetController.php index 6ed401fc..8027a72b 100644 --- a/src/Api/Controllers/Companies/GetController.php +++ b/src/Api/Controllers/Companies/GetController.php @@ -23,14 +23,13 @@ */ class GetController extends BaseController { - /** @var string */ - protected string $model = Companies::class; - /** @var array */ protected array $includes = [ Relationships::INDIVIDUALS, Relationships::PRODUCTS, ]; + /** @var string */ + protected string $model = Companies::class; /** @var string */ protected string $resource = Relationships::COMPANIES; diff --git a/src/Api/Controllers/IndividualTypes/GetController.php b/src/Api/Controllers/IndividualTypes/GetController.php index 04cee0bb..fb032616 100644 --- a/src/Api/Controllers/IndividualTypes/GetController.php +++ b/src/Api/Controllers/IndividualTypes/GetController.php @@ -23,13 +23,12 @@ */ class GetController extends BaseController { - /** @var string */ - protected string $model = IndividualTypes::class; - /** @var array */ protected array $includes = [ Relationships::INDIVIDUALS, ]; + /** @var string */ + protected string $model = IndividualTypes::class; /** @var string */ protected string $resource = Relationships::INDIVIDUAL_TYPES; diff --git a/src/Api/Controllers/Individuals/GetController.php b/src/Api/Controllers/Individuals/GetController.php index abead545..6efa4a68 100644 --- a/src/Api/Controllers/Individuals/GetController.php +++ b/src/Api/Controllers/Individuals/GetController.php @@ -23,20 +23,19 @@ */ class GetController extends BaseController { - /** @var string */ - protected string $model = Individuals::class; - /** @var array */ protected array $includes = [ Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES, ]; + /** @var string */ + protected string $model = Individuals::class; /** @var string */ - protected string $resource = Relationships::INDIVIDUALS; + protected string $orderBy = 'last, first'; /** @var string */ - protected string $transformer = IndividualsTransformer::class; + protected string $resource = Relationships::INDIVIDUALS; /** @var array */ protected array $sortFields = [ @@ -51,5 +50,5 @@ class GetController extends BaseController ]; /** @var string */ - protected string $orderBy = 'last, first'; + protected string $transformer = IndividualsTransformer::class; } diff --git a/src/Api/Controllers/LoginController.php b/src/Api/Controllers/LoginController.php index 58c337b0..6e609ea8 100644 --- a/src/Api/Controllers/LoginController.php +++ b/src/Api/Controllers/LoginController.php @@ -34,8 +34,8 @@ */ class LoginController extends Controller { - use TokenTrait; use QueryTrait; + use TokenTrait; /** * Default action logging in diff --git a/src/Api/Controllers/ProductTypes/GetController.php b/src/Api/Controllers/ProductTypes/GetController.php index 72da8c6d..efa1c522 100644 --- a/src/Api/Controllers/ProductTypes/GetController.php +++ b/src/Api/Controllers/ProductTypes/GetController.php @@ -23,13 +23,12 @@ */ class GetController extends BaseController { - /** @var string */ - protected string $model = ProductTypes::class; - /** @var array */ protected array $includes = [ Relationships::PRODUCTS, ]; + /** @var string */ + protected string $model = ProductTypes::class; /** @var string */ protected string $resource = Relationships::PRODUCT_TYPES; diff --git a/src/Api/Controllers/Products/GetController.php b/src/Api/Controllers/Products/GetController.php index a10fe6e3..e2dd103a 100644 --- a/src/Api/Controllers/Products/GetController.php +++ b/src/Api/Controllers/Products/GetController.php @@ -23,14 +23,13 @@ */ class GetController extends BaseController { - /** @var string */ - protected string $model = Products::class; - /** @var array */ protected array $includes = [ Relationships::COMPANIES, Relationships::PRODUCT_TYPES, ]; + /** @var string */ + protected string $model = Products::class; /** @var string */ protected string $resource = Relationships::PRODUCTS; diff --git a/src/Api/Controllers/Users/GetController.php b/src/Api/Controllers/Users/GetController.php index efa7342f..e48c7501 100644 --- a/src/Api/Controllers/Users/GetController.php +++ b/src/Api/Controllers/Users/GetController.php @@ -27,10 +27,10 @@ class GetController extends BaseController protected string $model = Users::class; /** @var string */ - protected string $resource = Relationships::USERS; + protected string $orderBy = 'username'; /** @var string */ - protected string $transformer = BaseTransformer::class; + protected string $resource = Relationships::USERS; /** @var array */ protected array $sortFields = [ @@ -44,5 +44,5 @@ class GetController extends BaseController ]; /** @var string */ - protected string $orderBy = 'username'; + protected string $transformer = BaseTransformer::class; } diff --git a/src/Cli/Tasks/ClearcacheTask.php b/src/Cli/Tasks/ClearcacheTask.php index febd9656..dcfb0855 100755 --- a/src/Cli/Tasks/ClearcacheTask.php +++ b/src/Cli/Tasks/ClearcacheTask.php @@ -1,5 +1,4 @@ 'OK', diff --git a/src/Middleware/AuthenticationMiddleware.php b/src/Middleware/AuthenticationMiddleware.php index 793f0ce9..a2d1d8e4 100755 --- a/src/Middleware/AuthenticationMiddleware.php +++ b/src/Middleware/AuthenticationMiddleware.php @@ -25,8 +25,8 @@ */ class AuthenticationMiddleware implements MiddlewareInterface { - use ResponseTrait; use QueryTrait; + use ResponseTrait; /** * Call me diff --git a/src/Middleware/TokenBase.php b/src/Middleware/TokenBase.php index ff4d1a33..492fd405 100755 --- a/src/Middleware/TokenBase.php +++ b/src/Middleware/TokenBase.php @@ -24,9 +24,9 @@ */ abstract class TokenBase implements MiddlewareInterface { + use QueryTrait; use ResponseTrait; use TokenTrait; - use QueryTrait; /** * @param Request $request diff --git a/src/Middleware/TokenValidationMiddleware.php b/src/Middleware/TokenValidationMiddleware.php index 362dcf55..ba64eaae 100755 --- a/src/Middleware/TokenValidationMiddleware.php +++ b/src/Middleware/TokenValidationMiddleware.php @@ -20,9 +20,6 @@ use Phalcon\Cache\Cache; use Phalcon\Config\Config; use Phalcon\Mvc\Micro; -use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; -use Phalcon\Encryption\Security\JWT\Signer\Hmac; -use Phalcon\Encryption\Security\JWT\Validator; use function implode; diff --git a/src/Middleware/TokenVerificationMiddleware.php b/src/Middleware/TokenVerificationMiddleware.php index d28b2d13..07d9047f 100755 --- a/src/Middleware/TokenVerificationMiddleware.php +++ b/src/Middleware/TokenVerificationMiddleware.php @@ -19,9 +19,8 @@ use Phalcon\Api\Models\Users; use Phalcon\Cache\Cache; use Phalcon\Config\Config; -use Phalcon\Mvc\Micro; use Phalcon\Encryption\Security\JWT\Signer\Hmac; -use Phalcon\Encryption\Security\JWT\Validator; +use Phalcon\Mvc\Micro; /** * Class AuthenticationMiddleware diff --git a/src/Models/Companies.php b/src/Models/Companies.php index 68a9aff3..26081328 100644 --- a/src/Models/Companies.php +++ b/src/Models/Companies.php @@ -24,6 +24,21 @@ */ class Companies extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'address' => Filter::FILTER_STRING, + 'city' => Filter::FILTER_STRING, + 'phone' => Filter::FILTER_STRING, + ]; + } /** * Initialize relationships and model properties * @@ -59,22 +74,6 @@ public function initialize(): void parent::initialize(); } - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'address' => Filter::FILTER_STRING, - 'city' => Filter::FILTER_STRING, - 'phone' => Filter::FILTER_STRING, - ]; - } - /** * Validates the company name * diff --git a/src/Models/CompaniesXProducts.php b/src/Models/CompaniesXProducts.php index 190a738c..2f91dd66 100644 --- a/src/Models/CompaniesXProducts.php +++ b/src/Models/CompaniesXProducts.php @@ -22,6 +22,18 @@ */ class CompaniesXProducts extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'companyId' => Filter::FILTER_ABSINT, + 'productId' => Filter::FILTER_ABSINT, + ]; + } /** * Initialize relationships and model properties * @@ -53,17 +65,4 @@ public function initialize(): void parent::initialize(); } - - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'companyId' => Filter::FILTER_ABSINT, - 'productId' => Filter::FILTER_ABSINT, - ]; - } } diff --git a/src/Models/IndividualTypes.php b/src/Models/IndividualTypes.php index c93d626a..1684a5e1 100644 --- a/src/Models/IndividualTypes.php +++ b/src/Models/IndividualTypes.php @@ -22,6 +22,19 @@ */ class IndividualTypes extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + } /** * Initialize relationships and model properties * @@ -43,18 +56,4 @@ public function initialize(): void parent::initialize(); } - - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - } } diff --git a/src/Models/Individuals.php b/src/Models/Individuals.php index 3fd5eb6d..2e03e9b6 100644 --- a/src/Models/Individuals.php +++ b/src/Models/Individuals.php @@ -22,6 +22,24 @@ */ class Individuals extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'companyId' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'prefix' => Filter::FILTER_STRING, + 'first' => Filter::FILTER_STRING, + 'middle' => Filter::FILTER_STRING, + 'last' => Filter::FILTER_STRING, + 'suffix' => Filter::FILTER_STRING, + ]; + } /** * Initialize relationships and model properties * @@ -53,23 +71,4 @@ public function initialize(): void parent::initialize(); } - - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'id' => Filter::FILTER_ABSINT, - 'companyId' => Filter::FILTER_ABSINT, - 'typeId' => Filter::FILTER_ABSINT, - 'prefix' => Filter::FILTER_STRING, - 'first' => Filter::FILTER_STRING, - 'middle' => Filter::FILTER_STRING, - 'last' => Filter::FILTER_STRING, - 'suffix' => Filter::FILTER_STRING, - ]; - } } diff --git a/src/Models/ProductTypes.php b/src/Models/ProductTypes.php index ed53de19..5923e18b 100644 --- a/src/Models/ProductTypes.php +++ b/src/Models/ProductTypes.php @@ -22,6 +22,19 @@ */ class ProductTypes extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + } /** * Initialize relationships and model properties * @@ -43,18 +56,4 @@ public function initialize(): void parent::initialize(); } - - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - } } diff --git a/src/Models/Products.php b/src/Models/Products.php index ca8324cd..6f3fb8ec 100644 --- a/src/Models/Products.php +++ b/src/Models/Products.php @@ -22,6 +22,22 @@ */ class Products extends AbstractModel { + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + 'quantity' => Filter::FILTER_ABSINT, + 'price' => Filter::FILTER_FLOAT, + ]; + } /** * Initialize relationships and model properties * @@ -56,21 +72,4 @@ public function initialize(): void parent::initialize(); } - - /** - * Model filters - * - * @return array - */ - public function getModelFilters(): array - { - return [ - 'id' => Filter::FILTER_ABSINT, - 'typeId' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - 'quantity' => Filter::FILTER_ABSINT, - 'price' => Filter::FILTER_FLOAT, - ]; - } } diff --git a/src/Models/Users.php b/src/Models/Users.php index 3e0d3796..28f8d586 100644 --- a/src/Models/Users.php +++ b/src/Models/Users.php @@ -16,13 +16,13 @@ use Phalcon\Api\Exception\ModelException; use Phalcon\Api\Mvc\Model\AbstractModel; use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Filter\Filter; use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; use Phalcon\Encryption\Security\JWT\Signer\Hmac; use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Encryption\Security\JWT\Validator; +use Phalcon\Filter\Filter; /** * Class Users @@ -31,16 +31,6 @@ class Users extends AbstractModel { use TokenTrait; - /** - * Returns the source table from the database - * - * @return void - */ - public function initialize(): void - { - $this->setSource('co_users'); - } - /** * Model filters * @@ -92,6 +82,16 @@ public function getValidationData(Token $token): Validator ; } + /** + * Returns the source table from the database + * + * @return void + */ + public function initialize(): void + { + $this->setSource('co_users'); + } + /** * @return Builder * @throws ModelException diff --git a/src/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php index 632100be..003e876c 100644 --- a/src/Mvc/Model/AbstractModel.php +++ b/src/Mvc/Model/AbstractModel.php @@ -22,21 +22,6 @@ abstract class AbstractModel extends PhModel { - /** - * Master initializer - * - * @return void - */ - public function initialize(): void - { - $this->setup( - [ - 'phqlLiterals' => false, - 'notNullValidations' => false, - ] - ); - } - /** * Gets a field from this model * @@ -76,6 +61,20 @@ public function getModelMessages(Logger $logger = null): string return $error; } + /** + * Master initializer + * + * @return void + */ + public function initialize(): void + { + $this->setup( + [ + 'phqlLiterals' => false, + 'notNullValidations' => false, + ] + ); + } /** * Sets a field in the model sanitized diff --git a/src/Providers/RouterProvider.php b/src/Providers/RouterProvider.php index 48a6c386..bd57f103 100644 --- a/src/Providers/RouterProvider.php +++ b/src/Providers/RouterProvider.php @@ -117,6 +117,33 @@ private function getMiddleware(): array ]; } + /** + * Adds multiple routes for the same handler abiding by the JSONAPI standard + * + * @param array $routes + * @param string $class + * @param string $relationship + * + * @return array + */ + private function getMultiRoutes( + array $routes, + string $class, + string $relationship + ): array { + $routes[] = [$class, '/' . $relationship, 'get', '/']; + $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}']; + $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}']; + $routes[] = [ + $class, + '/' . $relationship, + 'get', + '/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}', + ]; + + return $routes; + } + /** * Returns the array for the routes * @@ -160,31 +187,4 @@ private function getRoutes(): array return $routes; } - - /** - * Adds multiple routes for the same handler abiding by the JSONAPI standard - * - * @param array $routes - * @param string $class - * @param string $relationship - * - * @return array - */ - private function getMultiRoutes( - array $routes, - string $class, - string $relationship - ): array { - $routes[] = [$class, '/' . $relationship, 'get', '/']; - $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}']; - $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}']; - $routes[] = [ - $class, - '/' . $relationship, - 'get', - '/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}', - ]; - - return $routes; - } } diff --git a/src/Traits/QueryTrait.php b/src/Traits/QueryTrait.php index 7043c43d..8562c8b5 100644 --- a/src/Traits/QueryTrait.php +++ b/src/Traits/QueryTrait.php @@ -17,10 +17,10 @@ use Phalcon\Api\Models\Users; use Phalcon\Cache\Cache; use Phalcon\Config\Config; -use Phalcon\Mvc\Model\Query\Builder; -use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Token\Token; +use Phalcon\Mvc\Model\Query\Builder; +use Phalcon\Mvc\Model\ResultsetInterface; use function json_encode; use function sha1; @@ -31,6 +31,40 @@ */ trait QueryTrait { + /** + * Runs a query using the builder + * + * @param Config $config + * @param Cache $cache + * @param string $class + * @param array $where + * @param string $orderBy + * + * @return ResultsetInterface + */ + protected function getRecords( + Config $config, + Cache $cache, + string $class, + array $where = [], + string $orderBy = '' + ): ResultsetInterface { + $builder = new Builder(); + $builder->addFrom($class, 't1'); + + foreach ($where as $field => $value) { + $builder->andWhere( + sprintf('%s = :%s:', $field, $field), + [$field => $value] + ); + } + + if (true !== empty($orderBy)) { + $builder->orderBy($orderBy); + } + + return $this->getResults($config, $cache, $builder, $where); + } /** * Gets a user from the database based on the JWT token * @@ -85,41 +119,6 @@ protected function getUserByUsernameAndPassword( return $result[0] ?? null; } - /** - * Runs a query using the builder - * - * @param Config $config - * @param Cache $cache - * @param string $class - * @param array $where - * @param string $orderBy - * - * @return ResultsetInterface - */ - protected function getRecords( - Config $config, - Cache $cache, - string $class, - array $where = [], - string $orderBy = '' - ): ResultsetInterface { - $builder = new Builder(); - $builder->addFrom($class, 't1'); - - foreach ($where as $field => $value) { - $builder->andWhere( - sprintf('%s = :%s:', $field, $field), - [$field => $value] - ); - } - - if (true !== empty($orderBy)) { - $builder->orderBy($orderBy); - } - - return $this->getResults($config, $cache, $builder, $where); - } - /** * Runs the builder query if there is no cached data * diff --git a/src/Traits/TokenTrait.php b/src/Traits/TokenTrait.php index 7b19269d..7c7ef87b 100644 --- a/src/Traits/TokenTrait.php +++ b/src/Traits/TokenTrait.php @@ -50,32 +50,32 @@ protected function getTokenAudience(): string } /** - * Returns the time the token is issued at + * Returns the expiry time for the token * * @return int */ - protected function getTokenTimeIssuedAt(): int + protected function getTokenTimeExpiration(): int { - return time(); + return (time() + envValue('TOKEN_EXPIRATION', 86400)); } /** - * Returns the time drift i.e. token will be valid not before + * Returns the time the token is issued at * * @return int */ - protected function getTokenTimeNotBefore(): int + protected function getTokenTimeIssuedAt(): int { - return (time() + envValue('TOKEN_NOT_BEFORE', 0)); + return time(); } /** - * Returns the expiry time for the token + * Returns the time drift i.e. token will be valid not before * * @return int */ - protected function getTokenTimeExpiration(): int + protected function getTokenTimeNotBefore(): int { - return (time() + envValue('TOKEN_EXPIRATION', 86400)); + return (time() + envValue('TOKEN_NOT_BEFORE', 0)); } } diff --git a/tests/Integration/AbstractIntegrationTestCase.php b/tests/Integration/AbstractIntegrationTestCase.php index 7a9bf163..4b362950 100644 --- a/tests/Integration/AbstractIntegrationTestCase.php +++ b/tests/Integration/AbstractIntegrationTestCase.php @@ -46,15 +46,15 @@ abstract class AbstractIntegrationTestCase extends AbstractUnitTestCase { protected ?DiInterface $diContainer = null; + /** @var array */ + protected array $options = ['rollback' => false]; + /** @var array> */ protected array $savedModels = []; /** @var array */ protected array $savedRecords = []; - /** @var array */ - protected array $options = ['rollback' => false]; - protected function setUp(): void { parent::setUp(); @@ -113,17 +113,6 @@ protected function addCompanyXProduct(int $companyId, int $productId): Companies ); } - protected function addIndividualTypeRecord(string $namePrefix = ''): IndividualTypes - { - return $this->haveRecordWithFields( - IndividualTypes::class, - [ - 'name' => uniqid($namePrefix), - 'description' => uniqid(), - ] - ); - } - protected function addIndividualRecord(string $namePrefix = '', int $comId = 0, int $typeId = 0): Individuals { return $this->haveRecordWithFields( @@ -140,6 +129,17 @@ protected function addIndividualRecord(string $namePrefix = '', int $comId = 0, ); } + protected function addIndividualTypeRecord(string $namePrefix = ''): IndividualTypes + { + return $this->haveRecordWithFields( + IndividualTypes::class, + [ + 'name' => uniqid($namePrefix), + 'description' => uniqid(), + ] + ); + } + protected function addProductRecord(string $namePrefix = '', int $typeId = 0): Products { return $this->haveRecordWithFields( @@ -165,19 +165,6 @@ protected function addProductTypeRecord(string $namePrefix = ''): ProductTypes ); } - protected function grabDi(): ?DiInterface - { - return $this->diContainer; - } - - /** - * @return mixed - */ - protected function grabFromDi(string $name) - { - return $this->diContainer->get($name); - } - /** * Returns the relationships that a model has. * @@ -240,6 +227,19 @@ protected function getRecordWithFields(string $modelName, array $fields = []) return $record; } + protected function grabDi(): ?DiInterface + { + return $this->diContainer; + } + + /** + * @return mixed + */ + protected function grabFromDi(string $name) + { + return $this->diContainer->get($name); + } + /** * @param array $configData */ diff --git a/tests/Integration/Library/ModelTest.php b/tests/Integration/Library/ModelTest.php index 0d7badc7..2c83e3ec 100644 --- a/tests/Integration/Library/ModelTest.php +++ b/tests/Integration/Library/ModelTest.php @@ -23,30 +23,57 @@ final class ModelTest extends AbstractIntegrationTestCase { - public function testModelGetSetFields(): void + public function testCheckModelMessages(): void { - $this->haveRecordWithFields( + $user = $this->mockWithConstructor( Users::class, + [], [ - 'username' => 'testusername', - 'password' => 'testpass', - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenPassword' => '12345', - 'tokenId' => '00110011', + 'save' => false, + 'getMessages' => [ + new Message('error 1'), + new Message('error 2'), + ], ] ); + + $result = $user + ->set('username', 'test') + ->save() + ; + $this->assertFalse($result); + + $this->assertSame('error 1
error 2
', $user->getModelMessages()); } - public function testModelSetNonExistingFields(): void + public function testCheckModelMessagesWithLogger(): void { - $this->expectException(ModelException::class); + /** @var Logger $logger */ + $logger = $this->grabFromDi('logger'); + $user = $this->mockWithConstructor( + Users::class, + [], + [ + 'save' => false, + 'getMessages' => [ + new Message('error 1'), + new Message('error 2'), + ], + ] + ); - $fixture = new Users(); - $fixture->set('id', 1000) - ->set('some_field', true) - ->save() + $fileName = appPath('storage/logs/api.log'); + $result = $user + ->set('username', 'test') + ->save() ; + $this->assertFalse($result); + $this->assertSame('error 1
error 2
', $user->getModelMessages()); + + $user->getModelMessages($logger); + + $this->assertFileContentsContains($fileName, "error 1\n"); + $this->assertFileContentsContains($fileName, "error 2\n"); } public function testModelGetNonExistingFields(): void @@ -68,6 +95,31 @@ public function testModelGetNonExistingFields(): void $user->get('some_field'); } + public function testModelGetSetFields(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', + ] + ); + } + + public function testModelSetNonExistingFields(): void + { + $this->expectException(ModelException::class); + + $fixture = new Users(); + $fixture->set('id', 1000) + ->set('some_field', true) + ->save() + ; + } public function testModelUpdateFields(): void { @@ -122,57 +174,4 @@ public function testModelUpdateFieldsNotSanitized(): void ; $this->assertSame($user->get('password'), 'abcde\nfg'); } - - public function testCheckModelMessages(): void - { - $user = $this->mockWithConstructor( - Users::class, - [], - [ - 'save' => false, - 'getMessages' => [ - new Message('error 1'), - new Message('error 2'), - ], - ] - ); - - $result = $user - ->set('username', 'test') - ->save() - ; - $this->assertFalse($result); - - $this->assertSame('error 1
error 2
', $user->getModelMessages()); - } - - public function testCheckModelMessagesWithLogger(): void - { - /** @var Logger $logger */ - $logger = $this->grabFromDi('logger'); - $user = $this->mockWithConstructor( - Users::class, - [], - [ - 'save' => false, - 'getMessages' => [ - new Message('error 1'), - new Message('error 2'), - ], - ] - ); - - $fileName = appPath('storage/logs/api.log'); - $result = $user - ->set('username', 'test') - ->save() - ; - $this->assertFalse($result); - $this->assertSame('error 1
error 2
', $user->getModelMessages()); - - $user->getModelMessages($logger); - - $this->assertFileContentsContains($fileName, "error 1\n"); - $this->assertFileContentsContains($fileName, "error 2\n"); - } } diff --git a/tests/Integration/Library/Models/CompaniesTest.php b/tests/Integration/Library/Models/CompaniesTest.php index 82fd77ca..0615c687 100644 --- a/tests/Integration/Library/Models/CompaniesTest.php +++ b/tests/Integration/Library/Models/CompaniesTest.php @@ -24,6 +24,18 @@ final class CompaniesTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new Companies(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'address' => Filter::FILTER_STRING, + 'city' => Filter::FILTER_STRING, + 'phone' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -38,19 +50,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new Companies(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'address' => Filter::FILTER_STRING, - 'city' => Filter::FILTER_STRING, - 'phone' => Filter::FILTER_STRING, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(Companies::class); diff --git a/tests/Integration/Library/Models/CompaniesXProductsTest.php b/tests/Integration/Library/Models/CompaniesXProductsTest.php index 03627675..1f59783b 100644 --- a/tests/Integration/Library/Models/CompaniesXProductsTest.php +++ b/tests/Integration/Library/Models/CompaniesXProductsTest.php @@ -22,6 +22,15 @@ final class CompaniesXProductsTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new CompaniesXProducts(); + $expected = [ + 'companyId' => Filter::FILTER_ABSINT, + 'productId' => Filter::FILTER_ABSINT, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -33,16 +42,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new CompaniesXProducts(); - $expected = [ - 'companyId' => Filter::FILTER_ABSINT, - 'productId' => Filter::FILTER_ABSINT, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(CompaniesXProducts::class); diff --git a/tests/Integration/Library/Models/IndividualTypesTest.php b/tests/Integration/Library/Models/IndividualTypesTest.php index 828878c1..1f034ff9 100644 --- a/tests/Integration/Library/Models/IndividualTypesTest.php +++ b/tests/Integration/Library/Models/IndividualTypesTest.php @@ -21,6 +21,16 @@ final class IndividualTypesTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new IndividualTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -33,17 +43,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new IndividualTypes(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(IndividualTypes::class); diff --git a/tests/Integration/Library/Models/IndividualsTest.php b/tests/Integration/Library/Models/IndividualsTest.php index 7eafb041..aadcffb8 100644 --- a/tests/Integration/Library/Models/IndividualsTest.php +++ b/tests/Integration/Library/Models/IndividualsTest.php @@ -22,6 +22,21 @@ final class IndividualsTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new Individuals(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'companyId' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'prefix' => Filter::FILTER_STRING, + 'first' => Filter::FILTER_STRING, + 'middle' => Filter::FILTER_STRING, + 'last' => Filter::FILTER_STRING, + 'suffix' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -39,22 +54,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new Individuals(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'companyId' => Filter::FILTER_ABSINT, - 'typeId' => Filter::FILTER_ABSINT, - 'prefix' => Filter::FILTER_STRING, - 'first' => Filter::FILTER_STRING, - 'middle' => Filter::FILTER_STRING, - 'last' => Filter::FILTER_STRING, - 'suffix' => Filter::FILTER_STRING, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(Individuals::class); diff --git a/tests/Integration/Library/Models/ProductTypesTest.php b/tests/Integration/Library/Models/ProductTypesTest.php index 11ce60a0..b65dacb7 100644 --- a/tests/Integration/Library/Models/ProductTypesTest.php +++ b/tests/Integration/Library/Models/ProductTypesTest.php @@ -21,6 +21,16 @@ final class ProductTypesTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new ProductTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -33,17 +43,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new ProductTypes(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(ProductTypes::class); diff --git a/tests/Integration/Library/Models/ProductsTest.php b/tests/Integration/Library/Models/ProductsTest.php index f39c19b8..8dd8ab69 100644 --- a/tests/Integration/Library/Models/ProductsTest.php +++ b/tests/Integration/Library/Models/ProductsTest.php @@ -22,6 +22,19 @@ final class ProductsTest extends AbstractIntegrationTestCase { + public function testValidateFilters(): void + { + $model = new Products(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + 'quantity' => Filter::FILTER_ABSINT, + 'price' => Filter::FILTER_FLOAT, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } public function testValidateModel(): void { $this->haveModelDefinition( @@ -37,20 +50,6 @@ public function testValidateModel(): void ); } - public function testValidateFilters(): void - { - $model = new Products(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'typeId' => Filter::FILTER_ABSINT, - 'name' => Filter::FILTER_STRING, - 'description' => Filter::FILTER_STRING, - 'quantity' => Filter::FILTER_ABSINT, - 'price' => Filter::FILTER_FLOAT, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testValidateRelationships(): void { $actual = $this->getModelRelationships(Products::class); diff --git a/tests/Integration/Library/Models/UsersTest.php b/tests/Integration/Library/Models/UsersTest.php index 2d49d1ef..db0c051e 100644 --- a/tests/Integration/Library/Models/UsersTest.php +++ b/tests/Integration/Library/Models/UsersTest.php @@ -29,37 +29,6 @@ final class UsersTest extends AbstractIntegrationTestCase { use TokenTrait; - public function testValidateModel(): void - { - $this->haveModelDefinition( - Users::class, - [ - 'id', - 'status', - 'username', - 'password', - 'issuer', - 'tokenPassword', - 'tokenId', - ] - ); - } - - public function testValidateFilters(): void - { - $model = new Users(); - $expected = [ - 'id' => Filter::FILTER_ABSINT, - 'status' => Filter::FILTER_ABSINT, - 'username' => Filter::FILTER_STRING, - 'password' => Filter::FILTER_STRING, - 'issuer' => Filter::FILTER_STRING, - 'tokenPassword' => Filter::FILTER_STRING, - 'tokenId' => Filter::FILTER_STRING, - ]; - $this->assertSame($expected, $model->getModelFilters()); - } - public function testCheckValidationData(): void { /** @var Users $user */ @@ -91,6 +60,37 @@ public function testCheckValidationData(): void $this->assertInstanceOf($class, $actual); } + public function testValidateFilters(): void + { + $model = new Users(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'status' => Filter::FILTER_ABSINT, + 'username' => Filter::FILTER_STRING, + 'password' => Filter::FILTER_STRING, + 'issuer' => Filter::FILTER_STRING, + 'tokenPassword' => Filter::FILTER_STRING, + 'tokenId' => Filter::FILTER_STRING, + ]; + $this->assertSame($expected, $model->getModelFilters()); + } + + public function testValidateModel(): void + { + $this->haveModelDefinition( + Users::class, + [ + 'id', + 'status', + 'username', + 'password', + 'issuer', + 'tokenPassword', + 'tokenId', + ] + ); + } + public function testValidateRelationships(): void { $actual = $this->getModelRelationships(Users::class); diff --git a/tests/Integration/Library/Traits/QueryTest.php b/tests/Integration/Library/Traits/QueryTest.php index 4236e73c..6849db93 100644 --- a/tests/Integration/Library/Traits/QueryTest.php +++ b/tests/Integration/Library/Traits/QueryTest.php @@ -31,38 +31,69 @@ final class QueryTest extends AbstractIntegrationTestCase { - use TokenTrait; use QueryTrait; + use TokenTrait; - public function testGetUserByUsernameAndPassword(): void + public function testGetCompaniesCachedData(): void { - /** @var Users $result */ - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); + $configData = require appPath('./src/Core/config.php'); + $this->assertTrue($configData['app']['devMode']); + + $configData['app']['devMode'] = false; + /** @var Config $config */ + $config = new Config($configData); + $container = $this->grabDi(); + $container->set('config', $config); + $this->assertFalse($config->path('app.devMode')); /** @var Cache $cache */ $cache = $this->grabFromDi('cache'); + $cache->clear(); /** @var Config $config */ $config = $this->grabFromDi('config'); - $dbUser = $this->getUserByUsernameAndPassword( - $config, - $cache, - Data::$testUsername, - Data::$testPassword + $this->assertFalse($config->path('app.devMode')); + + /** + * Company 1 + */ + $comName = uniqid('com-cached-'); + $comOne = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => $comName, + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] ); - $this->assertNotNull($dbUser); + $results = $this->getRecords($config, $cache, Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + + /** + * Get the record again but ensure the name has been changed + */ + $result = $comOne->set('name', 'com-cached-change') + ->save() + ; + $this->assertNotEquals(false, $result); + + /** + * This should return the cached result + */ + $results = $this->getRecords($config, $cache, Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); } - public function testGetUserByWrongUsernameAndPasswordReturnsNull(): void + public function testGetUserByUsernameAndPassword(): void { /** @var Users $result */ $this->haveRecordWithFields( @@ -84,10 +115,10 @@ public function testGetUserByWrongUsernameAndPasswordReturnsNull(): void $config, $cache, Data::$testUsername, - 'nothing' + Data::$testPassword ); - $this->assertNull($dbUser); + $this->assertNotNull($dbUser); } /** @@ -126,62 +157,31 @@ public function testGetUserByWrongTokenReturnsNull(): void $this->assertNull($actual); } - public function testGetCompaniesCachedData(): void + public function testGetUserByWrongUsernameAndPasswordReturnsNull(): void { - $configData = require appPath('./src/Core/config.php'); - $this->assertTrue($configData['app']['devMode']); - - $configData['app']['devMode'] = false; - /** @var Config $config */ - $config = new Config($configData); - $container = $this->grabDi(); - $container->set('config', $config); - $this->assertFalse($config->path('app.devMode')); + /** @var Users $result */ + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); /** @var Cache $cache */ $cache = $this->grabFromDi('cache'); - $cache->clear(); /** @var Config $config */ $config = $this->grabFromDi('config'); - $this->assertFalse($config->path('app.devMode')); - - /** - * Company 1 - */ - $comName = uniqid('com-cached-'); - $comOne = $this->haveRecordWithFields( - Companies::class, - [ - 'name' => $comName, - 'address' => uniqid(), - 'city' => uniqid(), - 'phone' => uniqid(), - ] + $dbUser = $this->getUserByUsernameAndPassword( + $config, + $cache, + Data::$testUsername, + 'nothing' ); - $results = $this->getRecords($config, $cache, Companies::class); - $this->assertSame(1, count($results)); - $this->assertSame($comName, $results[0]->get('name')); - $this->assertSame($comOne->get('address'), $results[0]->get('address')); - $this->assertSame($comOne->get('city'), $results[0]->get('city')); - $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); - - /** - * Get the record again but ensure the name has been changed - */ - $result = $comOne->set('name', 'com-cached-change') - ->save() - ; - $this->assertNotEquals(false, $result); - - /** - * This should return the cached result - */ - $results = $this->getRecords($config, $cache, Companies::class); - $this->assertSame(1, count($results)); - $this->assertSame($comName, $results[0]->get('name')); - $this->assertSame($comOne->get('address'), $results[0]->get('address')); - $this->assertSame($comOne->get('city'), $results[0]->get('city')); - $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + $this->assertNull($dbUser); } } diff --git a/tests/Support/Data.php b/tests/Support/Data.php index 497f27f5..60317ebe 100644 --- a/tests/Support/Data.php +++ b/tests/Support/Data.php @@ -27,25 +27,23 @@ class Data { + public static $companiesRecordIncludesUrl = '/companies/%s?includes=%s'; + public static $companiesRecordUrl = '/companies/%s'; public static $companiesSortUrl = '/companies?sort=%s'; public static $companiesUrl = '/companies'; - public static $companiesRecordUrl = '/companies/%s'; - public static $companiesRecordIncludesUrl = '/companies/%s?includes=%s'; - public static $loginUrl = '/login'; - public static $individualsUrl = '/individuals'; - public static $individualsRecordUrl = '/individuals/%s'; public static $individualsRecordIncludesUrl = '/individuals/%s?includes=%s'; - public static $individualTypesUrl = '/individual-types'; - public static $individualTypesRecordUrl = '/individual-types/%s'; + public static $individualsRecordUrl = '/individuals/%s'; + public static $individualsUrl = '/individuals'; public static $individualTypesRecordIncludesUrl = '/individual-types/%s?includes=%s'; - public static $productsUrl = '/products'; - public static $productsRecordUrl = '/products/%s'; + public static $individualTypesRecordUrl = '/individual-types/%s'; + public static $individualTypesUrl = '/individual-types'; + public static $loginUrl = '/login'; public static $productsRecordIncludesUrl = '/products/%s?includes=%s'; - public static $productTypesUrl = '/product-types'; - public static $productTypesRecordUrl = '/product-types/%s'; + public static $productsRecordUrl = '/products/%s'; + public static $productsUrl = '/products'; public static $productTypesRecordIncludesUrl = '/product-types/%s?includes=%s'; - public static $usersUrl = '/users'; - public static $wrongUrl = '/sommething'; + public static $productTypesRecordUrl = '/product-types/%s'; + public static $productTypesUrl = '/product-types'; public static $strongPassphrase = 'DR^3*ZwnAHKc9yP$YSpW98dsmHJBax5&'; public static $testIssuer = 'https://niden.net'; @@ -53,35 +51,8 @@ class Data public static $testTokenId = '110011'; public static $testTokenPassword = 'DR^4*ZwnAHKc0yP$YSpW09dsmHJBax6&'; public static $testUsername = 'testuser'; - - /** - * @return array - */ - public static function loginJson() - { - return [ - 'username' => self::$testUsername, - 'password' => self::$testPassword, - ]; - } - - /** - * @param $name - * @param string $address - * @param string $city - * @param string $phone - * - * @return array - */ - public static function companyAddJson($name, $address = '', $city = '', $phone = '') - { - return [ - 'name' => $name, - 'address' => $address, - 'city' => $city, - 'phone' => $phone, - ]; - } + public static $usersUrl = '/users'; + public static $wrongUrl = '/sommething'; /** * @param Companies $record @@ -173,6 +144,24 @@ public static function companiesResponse(Companies $record): array ]; } + /** + * @param $name + * @param string $address + * @param string $city + * @param string $phone + * + * @return array + */ + public static function companyAddJson($name, $address = '', $city = '', $phone = '') + { + return [ + 'name' => $name, + 'address' => $address, + 'city' => $city, + 'phone' => $phone, + ]; + } + /** * @param Individuals $record * @@ -230,23 +219,31 @@ public static function individualTypeResponse(IndividualTypes $record): array ]; } + /** + * @return array + */ + public static function loginJson() + { + return [ + 'username' => self::$testUsername, + 'password' => self::$testPassword, + ]; + } + /** * @param Products $record * * @return array * @throws ModelException */ - public static function productResponse(Products $record): array + public static function productFieldsResponse(Products $record): array { return [ 'type' => Relationships::PRODUCTS, 'id' => (string) $record->get('id'), 'attributes' => [ - 'typeId' => $record->get('typeId'), - 'name' => $record->get('name'), - 'description' => $record->get('description'), - 'quantity' => $record->get('quantity'), - 'price' => $record->get('price'), + 'name' => $record->get('name'), + 'price' => $record->get('price'), ], 'links' => [ 'self' => sprintf( @@ -265,14 +262,17 @@ public static function productResponse(Products $record): array * @return array * @throws ModelException */ - public static function productFieldsResponse(Products $record): array + public static function productResponse(Products $record): array { return [ 'type' => Relationships::PRODUCTS, 'id' => (string) $record->get('id'), 'attributes' => [ - 'name' => $record->get('name'), - 'price' => $record->get('price'), + 'typeId' => $record->get('typeId'), + 'name' => $record->get('name'), + 'description' => $record->get('description'), + 'quantity' => $record->get('quantity'), + 'price' => $record->get('price'), ], 'links' => [ 'self' => sprintf( diff --git a/tests/Unit/Config/FunctionsTest.php b/tests/Unit/Config/FunctionsTest.php index a91dc144..140778f6 100644 --- a/tests/Unit/Config/FunctionsTest.php +++ b/tests/Unit/Config/FunctionsTest.php @@ -52,6 +52,14 @@ public function testAppPathWithParameter(): void $this->assertSame($path, appPath('src/Core/config.php')); } + public function testAppUrlWithUrl(): void + { + $this->assertSame( + 'http://api.phalcon.ld/companies/1', + appUrl(Relationships::COMPANIES, 1) + ); + } + public function testEnvValueAsFalse(): void { $_ENV['SOMEVAL'] = false; @@ -72,12 +80,4 @@ public function testEnvValueWithValue(): void $this->assertSame('someval', envValue('SOMEVAL')); } - - public function testAppUrlWithUrl(): void - { - $this->assertSame( - 'http://api.phalcon.ld/companies/1', - appUrl(Relationships::COMPANIES, 1) - ); - } } diff --git a/tests/_data/dump.sql b/tests/_data/dump.sql deleted file mode 100644 index e69de29b..00000000 From 9361cf0b9b2227be4230fc82686ed7930f1eb774 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 20:55:05 -0500 Subject: [PATCH 26/47] [#42] - update stale test expectations for app url and log levels --- tests/Unit/Config/AutoloaderTest.php | 2 +- tests/Unit/Config/FunctionsTest.php | 2 +- tests/Unit/Library/ErrorHandlerTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Config/AutoloaderTest.php b/tests/Unit/Config/AutoloaderTest.php index cb1829fe..785d4f74 100644 --- a/tests/Unit/Config/AutoloaderTest.php +++ b/tests/Unit/Config/AutoloaderTest.php @@ -49,7 +49,7 @@ public function testDotenvVariables(): void $this->assertSame('true', $_ENV['APP_DEBUG']); $this->assertSame('development', $_ENV['APP_ENV']); - $this->assertSame('http://api.phalcon.ld', $_ENV['APP_URL']); + $this->assertSame('http://localhost:8080', $_ENV['APP_URL']); $this->assertSame('/', $_ENV['APP_BASE_URI']); $this->assertSame('team@phalcon.io', $_ENV['APP_SUPPORT_EMAIL']); $this->assertSame('UTC', $_ENV['APP_TIMEZONE']); diff --git a/tests/Unit/Config/FunctionsTest.php b/tests/Unit/Config/FunctionsTest.php index 140778f6..cce03983 100644 --- a/tests/Unit/Config/FunctionsTest.php +++ b/tests/Unit/Config/FunctionsTest.php @@ -55,7 +55,7 @@ public function testAppPathWithParameter(): void public function testAppUrlWithUrl(): void { $this->assertSame( - 'http://api.phalcon.ld/companies/1', + 'http://localhost:8080/companies/1', appUrl(Relationships::COMPANIES, 1) ); } diff --git a/tests/Unit/Library/ErrorHandlerTest.php b/tests/Unit/Library/ErrorHandlerTest.php index c7946195..696b29bb 100644 --- a/tests/Unit/Library/ErrorHandlerTest.php +++ b/tests/Unit/Library/ErrorHandlerTest.php @@ -40,7 +40,7 @@ public function testLogErrorOnError(): void $handler->handle(1, 'test error', 'file.php', 4); $fileName = appPath('storage/logs/api.log'); - $expected = '[ERROR] [#:1]-[L: 4] : test error (file.php)'; + $expected = '[error] [#:1]-[L: 4] : test error (file.php)'; $this->assertFileContentsContains($fileName, $expected); } @@ -61,7 +61,7 @@ public function testLogErrorOnShutdown(): void $handler->shutdown(); $fileName = appPath('storage/logs/api.log'); - $expected = '[INFO] Shutdown completed'; + $expected = '[info] Shutdown completed'; $this->assertFileContentsContains($fileName, $expected); } From 419ba9937737fdb76ed18545a5c1ecf3f14a5ea2 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 21:15:23 -0500 Subject: [PATCH 27/47] [#42] - merge the token middleware into one authentication middleware --- src/Middleware/AuthenticationMiddleware.php | 66 +++++++++++++++-- src/Middleware/TokenBase.php | 43 ----------- src/Middleware/TokenUserMiddleware.php | 65 ----------------- src/Middleware/TokenValidationMiddleware.php | 73 ------------------- .../TokenVerificationMiddleware.php | 69 ------------------ src/Providers/RouterProvider.php | 22 +++--- 6 files changed, 70 insertions(+), 268 deletions(-) delete mode 100755 src/Middleware/TokenBase.php delete mode 100755 src/Middleware/TokenUserMiddleware.php delete mode 100755 src/Middleware/TokenValidationMiddleware.php delete mode 100755 src/Middleware/TokenVerificationMiddleware.php diff --git a/src/Middleware/AuthenticationMiddleware.php b/src/Middleware/AuthenticationMiddleware.php index a2d1d8e4..2a8d4e89 100755 --- a/src/Middleware/AuthenticationMiddleware.php +++ b/src/Middleware/AuthenticationMiddleware.php @@ -15,18 +15,35 @@ use Phalcon\Api\Http\Request; use Phalcon\Api\Http\Response; +use Phalcon\Api\Models\Users; use Phalcon\Api\Traits\QueryTrait; use Phalcon\Api\Traits\ResponseTrait; +use Phalcon\Api\Traits\TokenTrait; +use Phalcon\Cache\Cache; +use Phalcon\Config\Config; +use Phalcon\Encryption\Security\JWT\Signer\Hmac; use Phalcon\Mvc\Micro; use Phalcon\Mvc\Micro\MiddlewareInterface; +use function implode; + /** - * Class AuthenticationMiddleware + * Authorizes the request. + * + * The bearer token is parsed once and the user resolved once, then the three + * phases of authorization run in order: + * + * 1. is the user carried by the token one that we know? + * 2. was the token signed with that user's passphrase? + * 3. do the token's claims match the ones we expect? + * + * The login route is exempt - it is what issues the token in the first place. */ class AuthenticationMiddleware implements MiddlewareInterface { use QueryTrait; use ResponseTrait; + use TokenTrait; /** * Call me @@ -37,19 +54,56 @@ class AuthenticationMiddleware implements MiddlewareInterface */ public function call(Micro $api): bool { + /** @var Cache $cache */ + $cache = $api->getService('cache'); + /** @var Config $config */ + $config = $api->getService('config'); /** @var Request $request */ $request = $api->getService('request'); /** @var Response $response */ $response = $api->getService('response'); - if ( - true !== $request->isLoginPage() && - true === $request->isEmptyBearerToken() - ) { + if (true === $request->isLoginPage()) { + return true; + } + + if (true === $request->isEmptyBearerToken()) { + $this->halt($api, $response::OK, 'Invalid Token'); + + return false; + } + + $token = $this->getToken($request->getBearerTokenFromHeader()); + + /** + * Phase 1 - is the user attached to this token in the database? + */ + /** @var Users|null $user */ + $user = $this->getUserByToken($config, $cache, $token); + if (null === $user) { + $this->halt($api, $response::OK, 'Invalid token (user)'); + + return false; + } + + /** + * Phase 2 - was the token signed with this user's passphrase? + */ + if (false === $token->verify(new Hmac(), $user->get('tokenPassword'))) { + $this->halt($api, $response::OK, 'Invalid Token (verification)'); + + return false; + } + + /** + * Phase 3 - do the claims the token carries match this user's? + */ + $errors = $token->validate($user->getValidationData($token)); + if (true !== empty($errors)) { $this->halt( $api, $response::OK, - 'Invalid Token' + 'Invalid Token [' . implode('; ', $errors) . ']' ); return false; diff --git a/src/Middleware/TokenBase.php b/src/Middleware/TokenBase.php deleted file mode 100755 index 492fd405..00000000 --- a/src/Middleware/TokenBase.php +++ /dev/null @@ -1,43 +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\Middleware; - -use Phalcon\Api\Http\Request; -use Phalcon\Api\Traits\QueryTrait; -use Phalcon\Api\Traits\ResponseTrait; -use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Mvc\Micro\MiddlewareInterface; - -/** - * Class AuthenticationMiddleware - */ -abstract class TokenBase implements MiddlewareInterface -{ - use QueryTrait; - use ResponseTrait; - use TokenTrait; - - /** - * @param Request $request - * - * @return bool - */ - protected function isValidCheck(Request $request): bool - { - return ( - true !== $request->isLoginPage() && - true !== $request->isEmptyBearerToken() - ); - } -} diff --git a/src/Middleware/TokenUserMiddleware.php b/src/Middleware/TokenUserMiddleware.php deleted file mode 100755 index e6846d80..00000000 --- a/src/Middleware/TokenUserMiddleware.php +++ /dev/null @@ -1,65 +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\Middleware; - -use Phalcon\Api\Http\Request; -use Phalcon\Api\Http\Response; -use Phalcon\Api\Models\Users; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; -use Phalcon\Mvc\Micro; - -/** - * Class TokenUserMiddleware - */ -class TokenUserMiddleware extends TokenBase -{ - /** - * @param Micro $api - * - * @return bool - */ - public function call(Micro $api): bool - { - /** @var Cache $cache */ - $cache = $api->getService('cache'); - /** @var Config $config */ - $config = $api->getService('config'); - /** @var Request $request */ - $request = $api->getService('request'); - /** @var Response $response */ - $response = $api->getService('response'); - if (true === $this->isValidCheck($request)) { - /** - * This is where we will find if the user exists based on - * the token passed using Bearer Authentication - */ - $token = $this->getToken($request->getBearerTokenFromHeader()); - - /** @var Users|null $user */ - $user = $this->getUserByToken($config, $cache, $token); - if (null === $user) { - $this->halt( - $api, - $response::OK, - 'Invalid token (user)' - ); - - return false; - } - } - - return true; - } -} diff --git a/src/Middleware/TokenValidationMiddleware.php b/src/Middleware/TokenValidationMiddleware.php deleted file mode 100755 index ba64eaae..00000000 --- a/src/Middleware/TokenValidationMiddleware.php +++ /dev/null @@ -1,73 +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\Middleware; - -use Phalcon\Api\Exception\ModelException; -use Phalcon\Api\Http\Request; -use Phalcon\Api\Http\Response; -use Phalcon\Api\Models\Users; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; -use Phalcon\Mvc\Micro; - -use function implode; - -/** - * Class TokenValidationMiddleware - */ -class TokenValidationMiddleware extends TokenBase -{ - /** - * @param Micro $api - * - * @return bool - * @throws ModelException - */ - public function call(Micro $api): bool - { - /** @var Cache $cache */ - $cache = $api->getService('cache'); - /** @var Config $config */ - $config = $api->getService('config'); - /** @var Request $request */ - $request = $api->getService('request'); - /** @var Response $response */ - $response = $api->getService('response'); - if (true === $this->isValidCheck($request)) { - /** - * This is where we will validate the token that was sent to us - * using Bearer Authentication - * - * Find the user attached to this token - */ - $token = $this->getToken($request->getBearerTokenFromHeader()); - - /** @var Users $user */ - $user = $this->getUserByToken($config, $cache, $token); - $errors = $token->validate($user->getValidationData($token)); - - if (true !== empty($errors)) { - $this->halt( - $api, - $response::OK, - 'Invalid Token [' . implode('; ', $errors) . ']' - ); - - return false; - } - } - - return true; - } -} diff --git a/src/Middleware/TokenVerificationMiddleware.php b/src/Middleware/TokenVerificationMiddleware.php deleted file mode 100755 index 07d9047f..00000000 --- a/src/Middleware/TokenVerificationMiddleware.php +++ /dev/null @@ -1,69 +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\Middleware; - -use Phalcon\Api\Exception\ModelException; -use Phalcon\Api\Http\Request; -use Phalcon\Api\Http\Response; -use Phalcon\Api\Models\Users; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; -use Phalcon\Encryption\Security\JWT\Signer\Hmac; -use Phalcon\Mvc\Micro; - -/** - * Class AuthenticationMiddleware - */ -class TokenVerificationMiddleware extends TokenBase -{ - /** - * @param Micro $api - * - * @return bool - * @throws ModelException - */ - public function call(Micro $api): bool - { - /** @var Cache $cache */ - $cache = $api->getService('cache'); - /** @var Config $config */ - $config = $api->getService('config'); - /** @var Request $request */ - $request = $api->getService('request'); - /** @var Response $response */ - $response = $api->getService('response'); - if (true === $this->isValidCheck($request)) { - /** - * This is where we will validate the token that was sent to us - * using Bearer Authentication - * - * Find the user attached to this token - */ - $token = $this->getToken($request->getBearerTokenFromHeader()); - $signer = new Hmac(); - - /** @var Users $user */ - $user = $this->getUserByToken($config, $cache, $token); - if (false === $token->verify($signer, $user->get('tokenPassword'))) { - $this->halt( - $api, - $response::OK, - 'Invalid Token (verification)' - ); - } - } - - return true; - } -} diff --git a/src/Providers/RouterProvider.php b/src/Providers/RouterProvider.php index bd57f103..e682661c 100644 --- a/src/Providers/RouterProvider.php +++ b/src/Providers/RouterProvider.php @@ -25,9 +25,6 @@ use Phalcon\Api\Middleware\AuthenticationMiddleware; use Phalcon\Api\Middleware\NotFoundMiddleware; use Phalcon\Api\Middleware\ResponseMiddleware; -use Phalcon\Api\Middleware\TokenUserMiddleware; -use Phalcon\Api\Middleware\TokenValidationMiddleware; -use Phalcon\Api\Middleware\TokenVerificationMiddleware; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Events\Manager; @@ -69,11 +66,15 @@ private function attachMiddleware( $middleware = $this->getMiddleware(); /** - * Get the events manager and attach the middleware to it + * Get the events manager and attach the middleware to it. One instance + * per middleware, shared by the events manager and the application - + * two instances would give each its own state. */ foreach ($middleware as $class => $function) { - $eventsManager->attach('micro', new $class()); - $application->{$function}(new $class()); + $object = new $class(); + + $eventsManager->attach('micro', $object); + $application->{$function}($object); } } @@ -108,12 +109,9 @@ private function attachRoutes(Micro $application): void private function getMiddleware(): array { return [ - NotFoundMiddleware::class => 'before', - AuthenticationMiddleware::class => 'before', - TokenUserMiddleware::class => 'before', - TokenVerificationMiddleware::class => 'before', - TokenValidationMiddleware::class => 'before', - ResponseMiddleware::class => 'after', + NotFoundMiddleware::class => 'before', + AuthenticationMiddleware::class => 'before', + ResponseMiddleware::class => 'after', ]; } From 7801b733b54ff0b811289b1079e910eb9b8568a1 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 21:18:57 -0500 Subject: [PATCH 28/47] [#42] - replace the query trait with an injected query service and users repository --- src/Api/Controllers/BaseController.php | 12 +- src/Api/Controllers/LoginController.php | 16 +- src/Api/providers.php | 4 + src/Middleware/AuthenticationMiddleware.php | 13 +- src/Providers/QueryServiceProvider.php | 42 ++++ src/Providers/UsersRepositoryProvider.php | 36 ++++ src/Repositories/UsersRepository.php | 79 ++++++++ src/Services/QueryService.php | 109 ++++++++++ src/Traits/QueryTrait.php | 159 --------------- .../Repositories/UsersRepositoryTest.php | 114 +++++++++++ .../Library/Services/QueryServiceTest.php | 88 +++++++++ .../Integration/Library/Traits/QueryTest.php | 187 ------------------ 12 files changed, 484 insertions(+), 375 deletions(-) create mode 100644 src/Providers/QueryServiceProvider.php create mode 100644 src/Providers/UsersRepositoryProvider.php create mode 100644 src/Repositories/UsersRepository.php create mode 100644 src/Services/QueryService.php delete mode 100644 src/Traits/QueryTrait.php create mode 100644 tests/Integration/Library/Repositories/UsersRepositoryTest.php create mode 100644 tests/Integration/Library/Services/QueryServiceTest.php delete mode 100644 tests/Integration/Library/Traits/QueryTest.php diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index 5b5d5f7a..60362a89 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -14,11 +14,9 @@ namespace Phalcon\Api\Api\Controllers; use Phalcon\Api\Http\Response; +use Phalcon\Api\Services\QueryService; use Phalcon\Api\Traits\FractalTrait; -use Phalcon\Api\Traits\QueryTrait; use Phalcon\Api\Traits\ResponseTrait; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; use Phalcon\Filter\Exception; use Phalcon\Filter\Filter; use Phalcon\Mvc\Controller; @@ -36,15 +34,13 @@ * Class BaseController * * @property Micro $application - * @property Cache $cache - * @property Config $config * @property ModelsMetadataCache $modelsMetadata + * @property QueryService $queryService * @property Response $response */ class BaseController extends Controller { use FractalTrait; - use QueryTrait; use ResponseTrait; /** @var array */ @@ -86,9 +82,7 @@ public function callAction(mixed $id = 0): void if (true !== $validSort) { $this->sendError($this->response::BAD_REQUEST); } else { - $results = $this->getRecords( - $this->config, - $this->cache, + $results = $this->queryService->getRecords( $this->model, $parameters, $this->orderBy diff --git a/src/Api/Controllers/LoginController.php b/src/Api/Controllers/LoginController.php index 6e609ea8..a93e77d9 100644 --- a/src/Api/Controllers/LoginController.php +++ b/src/Api/Controllers/LoginController.php @@ -17,24 +17,20 @@ use Phalcon\Api\Http\Request; use Phalcon\Api\Http\Response; use Phalcon\Api\Models\Users; -use Phalcon\Api\Traits\QueryTrait; +use Phalcon\Api\Repositories\UsersRepository; use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; use Phalcon\Filter\Filter; use Phalcon\Mvc\Controller; /** * Class LoginController * - * @property Cache $cache - * @property Config $config - * @property Request $request - * @property Response $response + * @property Request $request + * @property Response $response + * @property UsersRepository $usersRepository */ class LoginController extends Controller { - use QueryTrait; use TokenTrait; /** @@ -49,9 +45,7 @@ public function callAction() $password = $this->request->getPost('password', Filter::FILTER_STRING); /** @var Users|null $user */ - $user = $this->getUserByUsernameAndPassword( - $this->config, - $this->cache, + $user = $this->usersRepository->getByUsernameAndPassword( $username, $password ); diff --git a/src/Api/providers.php b/src/Api/providers.php index ce2f827a..e3d10d69 100644 --- a/src/Api/providers.php +++ b/src/Api/providers.php @@ -17,9 +17,11 @@ use Phalcon\Api\Providers\ErrorHandlerProvider; use Phalcon\Api\Providers\LoggerProvider; use Phalcon\Api\Providers\ModelsMetadataProvider; +use Phalcon\Api\Providers\QueryServiceProvider; use Phalcon\Api\Providers\RequestProvider; use Phalcon\Api\Providers\ResponseProvider; use Phalcon\Api\Providers\RouterProvider; +use Phalcon\Api\Providers\UsersRepositoryProvider; /** * Enabled providers. Order does matter @@ -34,4 +36,6 @@ ResponseProvider::class, RouterProvider::class, CacheDataProvider::class, + QueryServiceProvider::class, + UsersRepositoryProvider::class, ]; diff --git a/src/Middleware/AuthenticationMiddleware.php b/src/Middleware/AuthenticationMiddleware.php index 2a8d4e89..21e20037 100755 --- a/src/Middleware/AuthenticationMiddleware.php +++ b/src/Middleware/AuthenticationMiddleware.php @@ -16,11 +16,9 @@ use Phalcon\Api\Http\Request; use Phalcon\Api\Http\Response; use Phalcon\Api\Models\Users; -use Phalcon\Api\Traits\QueryTrait; +use Phalcon\Api\Repositories\UsersRepository; use Phalcon\Api\Traits\ResponseTrait; use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; use Phalcon\Encryption\Security\JWT\Signer\Hmac; use Phalcon\Mvc\Micro; use Phalcon\Mvc\Micro\MiddlewareInterface; @@ -41,7 +39,6 @@ */ class AuthenticationMiddleware implements MiddlewareInterface { - use QueryTrait; use ResponseTrait; use TokenTrait; @@ -54,14 +51,12 @@ class AuthenticationMiddleware implements MiddlewareInterface */ public function call(Micro $api): bool { - /** @var Cache $cache */ - $cache = $api->getService('cache'); - /** @var Config $config */ - $config = $api->getService('config'); /** @var Request $request */ $request = $api->getService('request'); /** @var Response $response */ $response = $api->getService('response'); + /** @var UsersRepository $usersRepository */ + $usersRepository = $api->getService('usersRepository'); if (true === $request->isLoginPage()) { return true; @@ -79,7 +74,7 @@ public function call(Micro $api): bool * Phase 1 - is the user attached to this token in the database? */ /** @var Users|null $user */ - $user = $this->getUserByToken($config, $cache, $token); + $user = $usersRepository->getByToken($token); if (null === $user) { $this->halt($api, $response::OK, 'Invalid token (user)'); diff --git a/src/Providers/QueryServiceProvider.php b/src/Providers/QueryServiceProvider.php new file mode 100644 index 00000000..b9dfcf72 --- /dev/null +++ b/src/Providers/QueryServiceProvider.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\Providers; + +use Phalcon\Api\Services\QueryService; +use Phalcon\Di\DiInterface; +use Phalcon\Di\ServiceProviderInterface; + +class QueryServiceProvider implements ServiceProviderInterface +{ + /** + * Registers the query service + * + * @param DiInterface $container + */ + public function register(DiInterface $container): void + { + $container->setShared( + 'queryService', + /** + * Resolved lazily - `cache` is registered after this provider runs. + */ + function () use ($container) { + return new QueryService( + $container->getShared('config'), + $container->getShared('cache') + ); + } + ); + } +} diff --git a/src/Providers/UsersRepositoryProvider.php b/src/Providers/UsersRepositoryProvider.php new file mode 100644 index 00000000..249aaaee --- /dev/null +++ b/src/Providers/UsersRepositoryProvider.php @@ -0,0 +1,36 @@ + + * + * 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\Providers; + +use Phalcon\Api\Repositories\UsersRepository; +use Phalcon\Di\DiInterface; +use Phalcon\Di\ServiceProviderInterface; + +class UsersRepositoryProvider implements ServiceProviderInterface +{ + /** + * Registers the users repository + * + * @param DiInterface $container + */ + public function register(DiInterface $container): void + { + $container->setShared( + 'usersRepository', + function () use ($container) { + return new UsersRepository($container->getShared('queryService')); + } + ); + } +} diff --git a/src/Repositories/UsersRepository.php b/src/Repositories/UsersRepository.php new file mode 100644 index 00000000..19ecde88 --- /dev/null +++ b/src/Repositories/UsersRepository.php @@ -0,0 +1,79 @@ + + * + * 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\Repositories; + +use Phalcon\Api\Constants\Flags; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Services\QueryService; +use Phalcon\Encryption\Security\JWT\Token\Enum; +use Phalcon\Encryption\Security\JWT\Token\Token; + +/** + * Looks users up. The knowledge of how a user is identified - by token claims + * or by credentials - lives here rather than in a general purpose query helper. + */ +class UsersRepository +{ + /** + * @param QueryService $queryService + */ + public function __construct(private readonly QueryService $queryService) + { + } + + /** + * Gets a user from the database based on the JWT token + * + * @param Token $token + * + * @return Users|null + */ + public function getByToken(Token $token): ?Users + { + $parameters = [ + 'issuer' => $token->getClaims() + ->get(Enum::ISSUER), + 'tokenId' => $token->getClaims() + ->get(Enum::ID), + 'status' => Flags::ACTIVE, + ]; + + $result = $this->queryService->getRecords(Users::class, $parameters); + + return $result[0] ?? null; + } + + /** + * Gets a user from the database based on the username and password + * + * @param string $username + * @param string $password + * + * @return Users|null + */ + public function getByUsernameAndPassword( + string $username, + string $password + ): ?Users { + $parameters = [ + 'username' => $username, + 'password' => $password, + 'status' => Flags::ACTIVE, + ]; + + $result = $this->queryService->getRecords(Users::class, $parameters); + + return $result[0] ?? null; + } +} diff --git a/src/Services/QueryService.php b/src/Services/QueryService.php new file mode 100644 index 00000000..66e0ec78 --- /dev/null +++ b/src/Services/QueryService.php @@ -0,0 +1,109 @@ + + * + * 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\Services; + +use Phalcon\Cache\Cache; +use Phalcon\Config\Config; +use Phalcon\Mvc\Model\Query\Builder; +use Phalcon\Mvc\Model\ResultsetInterface; + +use function json_encode; +use function sha1; +use function sprintf; + +/** + * Reads records through the query builder, serving them from the cache when + * one is warm and the application is not in dev mode. + * + * The config and the cache are collaborators, held once, rather than arguments + * threaded through every call. + */ +class QueryService +{ + /** + * @param Config $config + * @param Cache $cache + */ + public function __construct( + private readonly Config $config, + private readonly Cache $cache + ) { + } + + /** + * Runs a query using the builder + * + * @param string $class + * @param array $where + * @param string $orderBy + * + * @return ResultsetInterface + */ + public function getRecords( + string $class, + array $where = [], + string $orderBy = '' + ): ResultsetInterface { + $builder = new Builder(); + $builder->addFrom($class, 't1'); + + foreach ($where as $field => $value) { + $builder->andWhere( + sprintf('%s = :%s:', $field, $field), + [$field => $value] + ); + } + + if (true !== empty($orderBy)) { + $builder->orderBy($orderBy); + } + + return $this->getResults($builder, $where); + } + + /** + * Runs the builder query if there is no cached data + * + * @param Builder $builder + * @param array $where + * + * @return ResultsetInterface + */ + private function getResults( + Builder $builder, + array $where = [] + ): ResultsetInterface { + /** + * Calculate the cache key + */ + $phql = $builder->getPhql(); + $params = json_encode($where); + $cacheKey = sha1(sprintf('%s-%s.cache', $phql, $params)); + + if ( + true !== $this->config->path('app.devMode') && + true === $this->cache->has($cacheKey) + ) { + /** @var ResultsetInterface $data */ + $data = $this->cache->get($cacheKey); + } else { + $data = $builder->getQuery() + ->execute() + ; + $this->cache->set($cacheKey, $data); + } + + return $data; + } +} diff --git a/src/Traits/QueryTrait.php b/src/Traits/QueryTrait.php deleted file mode 100644 index 8562c8b5..00000000 --- a/src/Traits/QueryTrait.php +++ /dev/null @@ -1,159 +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\Traits; - -use Phalcon\Api\Constants\Flags; -use Phalcon\Api\Models\Users; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; -use Phalcon\Encryption\Security\JWT\Token\Enum; -use Phalcon\Encryption\Security\JWT\Token\Token; -use Phalcon\Mvc\Model\Query\Builder; -use Phalcon\Mvc\Model\ResultsetInterface; - -use function json_encode; -use function sha1; -use function sprintf; - -/** - * Trait QueryTrait - */ -trait QueryTrait -{ - /** - * Runs a query using the builder - * - * @param Config $config - * @param Cache $cache - * @param string $class - * @param array $where - * @param string $orderBy - * - * @return ResultsetInterface - */ - protected function getRecords( - Config $config, - Cache $cache, - string $class, - array $where = [], - string $orderBy = '' - ): ResultsetInterface { - $builder = new Builder(); - $builder->addFrom($class, 't1'); - - foreach ($where as $field => $value) { - $builder->andWhere( - sprintf('%s = :%s:', $field, $field), - [$field => $value] - ); - } - - if (true !== empty($orderBy)) { - $builder->orderBy($orderBy); - } - - return $this->getResults($config, $cache, $builder, $where); - } - /** - * Gets a user from the database based on the JWT token - * - * @param Config $config - * @param Cache $cache - * @param Token $token - * - * @return Users|null - */ - protected function getUserByToken( - Config $config, - Cache $cache, - Token $token - ): ?Users { - $parameters = [ - 'issuer' => $token->getClaims() - ->get(Enum::ISSUER), - 'tokenId' => $token->getClaims() - ->get(Enum::ID), - 'status' => Flags::ACTIVE, - ]; - - $result = $this->getRecords($config, $cache, Users::class, $parameters); - - return $result[0] ?? null; - } - - /** - * Gets a user from the database based on the username and password - * - * @param Config $config - * @param Cache $cache - * @param string $username - * @param string $password - * - * @return Users|null - */ - protected function getUserByUsernameAndPassword( - Config $config, - Cache $cache, - string $username, - string $password - ): ?Users { - $parameters = [ - 'username' => $username, - 'password' => $password, - 'status' => Flags::ACTIVE, - ]; - - $result = $this->getRecords($config, $cache, Users::class, $parameters); - - return $result[0] ?? null; - } - - /** - * Runs the builder query if there is no cached data - * - * @param Config $config - * @param Cache $cache - * @param Builder $builder - * @param array $where - * - * @return ResultsetInterface - */ - private function getResults( - Config $config, - Cache $cache, - Builder $builder, - array $where = [] - ): ResultsetInterface { - /** - * Calculate the cache key - */ - $phql = $builder->getPhql(); - $params = json_encode($where); - $cacheKey = sha1(sprintf('%s-%s.cache', $phql, $params)); - if ( - true !== $config->path('app.devMode') && - true === $cache->has($cacheKey) - ) { - /** @var ResultsetInterface $data */ - $data = $cache->get($cacheKey); - } else { - $data = $builder->getQuery() - ->execute() - ; - $cache->set($cacheKey, $data); - } - - return $data; - } -} diff --git a/tests/Integration/Library/Repositories/UsersRepositoryTest.php b/tests/Integration/Library/Repositories/UsersRepositoryTest.php new file mode 100644 index 00000000..2c995bc2 --- /dev/null +++ b/tests/Integration/Library/Repositories/UsersRepositoryTest.php @@ -0,0 +1,114 @@ + + * + * 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\Integration\Library\Repositories; + +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Models\Users; +use Phalcon\Api\Repositories\UsersRepository; +use Phalcon\Api\Services\QueryService; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\TokenTrait; +use Phalcon\Cache\Cache; +use Phalcon\Config\Config; +use Phalcon\Encryption\Security\JWT\Builder; +use Phalcon\Encryption\Security\JWT\Signer\Hmac; + +final class UsersRepositoryTest extends AbstractIntegrationTestCase +{ + use TokenTrait; + + public function testGetByUsernameAndPassword(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + $dbUser = $this->getRepository()->getByUsernameAndPassword( + Data::$testUsername, + Data::$testPassword + ); + + $this->assertNotNull($dbUser); + } + + /** + * @throws ModelException + */ + public function testGetByWrongTokenReturnsNull(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + $signer = new Hmac(); + $builder = new Builder($signer); + $token = $builder + ->setIssuer('https://somedomain.com') + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + $actual = $this->getRepository()->getByToken($token); + + $this->assertNull($actual); + } + + public function testGetByWrongUsernameAndPasswordReturnsNull(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => Data::$testUsername, + 'password' => Data::$testPassword, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + $dbUser = $this->getRepository()->getByUsernameAndPassword( + Data::$testUsername, + 'nothing' + ); + + $this->assertNull($dbUser); + } + + private function getRepository(): UsersRepository + { + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + + return new UsersRepository(new QueryService($config, $cache)); + } +} diff --git a/tests/Integration/Library/Services/QueryServiceTest.php b/tests/Integration/Library/Services/QueryServiceTest.php new file mode 100644 index 00000000..e0a7ea6e --- /dev/null +++ b/tests/Integration/Library/Services/QueryServiceTest.php @@ -0,0 +1,88 @@ + + * + * 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\Integration\Library\Services; + +use Phalcon\Api\Models\Companies; +use Phalcon\Api\Services\QueryService; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Cache\Cache; +use Phalcon\Config\Config; + +use function count; +use function Phalcon\Api\Core\appPath; +use function uniqid; + +final class QueryServiceTest extends AbstractIntegrationTestCase +{ + public function testGetCompaniesCachedData(): void + { + $configData = require appPath('./src/Core/config.php'); + $this->assertTrue($configData['app']['devMode']); + + $configData['app']['devMode'] = false; + /** @var Config $config */ + $config = new Config($configData); + $container = $this->grabDi(); + $container->set('config', $config); + $this->assertFalse($config->path('app.devMode')); + + /** @var Cache $cache */ + $cache = $this->grabFromDi('cache'); + $cache->clear(); + /** @var Config $config */ + $config = $this->grabFromDi('config'); + $this->assertFalse($config->path('app.devMode')); + + $queryService = new QueryService($config, $cache); + + /** + * Company 1 + */ + $comName = uniqid('com-cached-'); + $comOne = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => $comName, + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + $results = $queryService->getRecords(Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + + /** + * Get the record again but ensure the name has been changed + */ + $result = $comOne->set('name', 'com-cached-change') + ->save() + ; + $this->assertNotEquals(false, $result); + + /** + * This should return the cached result + */ + $results = $queryService->getRecords(Companies::class); + $this->assertSame(1, count($results)); + $this->assertSame($comName, $results[0]->get('name')); + $this->assertSame($comOne->get('address'), $results[0]->get('address')); + $this->assertSame($comOne->get('city'), $results[0]->get('city')); + $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); + } +} diff --git a/tests/Integration/Library/Traits/QueryTest.php b/tests/Integration/Library/Traits/QueryTest.php deleted file mode 100644 index 6849db93..00000000 --- a/tests/Integration/Library/Traits/QueryTest.php +++ /dev/null @@ -1,187 +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\Tests\Integration\Library\Traits; - -use Phalcon\Api\Exception\ModelException; -use Phalcon\Api\Models\Companies; -use Phalcon\Api\Models\Users; -use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; -use Phalcon\Api\Tests\Support\Data; -use Phalcon\Api\Traits\QueryTrait; -use Phalcon\Api\Traits\TokenTrait; -use Phalcon\Cache\Cache; -use Phalcon\Config\Config; -use Phalcon\Encryption\Security\JWT\Builder; -use Phalcon\Encryption\Security\JWT\Signer\Hmac; - -use function count; -use function Phalcon\Api\Core\appPath; -use function uniqid; - -final class QueryTest extends AbstractIntegrationTestCase -{ - use QueryTrait; - use TokenTrait; - - public function testGetCompaniesCachedData(): void - { - $configData = require appPath('./src/Core/config.php'); - $this->assertTrue($configData['app']['devMode']); - - $configData['app']['devMode'] = false; - /** @var Config $config */ - $config = new Config($configData); - $container = $this->grabDi(); - $container->set('config', $config); - $this->assertFalse($config->path('app.devMode')); - - /** @var Cache $cache */ - $cache = $this->grabFromDi('cache'); - $cache->clear(); - /** @var Config $config */ - $config = $this->grabFromDi('config'); - $this->assertFalse($config->path('app.devMode')); - - /** - * Company 1 - */ - $comName = uniqid('com-cached-'); - $comOne = $this->haveRecordWithFields( - Companies::class, - [ - 'name' => $comName, - 'address' => uniqid(), - 'city' => uniqid(), - 'phone' => uniqid(), - ] - ); - - $results = $this->getRecords($config, $cache, Companies::class); - $this->assertSame(1, count($results)); - $this->assertSame($comName, $results[0]->get('name')); - $this->assertSame($comOne->get('address'), $results[0]->get('address')); - $this->assertSame($comOne->get('city'), $results[0]->get('city')); - $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); - - /** - * Get the record again but ensure the name has been changed - */ - $result = $comOne->set('name', 'com-cached-change') - ->save() - ; - $this->assertNotEquals(false, $result); - - /** - * This should return the cached result - */ - $results = $this->getRecords($config, $cache, Companies::class); - $this->assertSame(1, count($results)); - $this->assertSame($comName, $results[0]->get('name')); - $this->assertSame($comOne->get('address'), $results[0]->get('address')); - $this->assertSame($comOne->get('city'), $results[0]->get('city')); - $this->assertSame($comOne->get('phone'), $results[0]->get('phone')); - } - - public function testGetUserByUsernameAndPassword(): void - { - /** @var Users $result */ - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - /** @var Cache $cache */ - $cache = $this->grabFromDi('cache'); - /** @var Config $config */ - $config = $this->grabFromDi('config'); - $dbUser = $this->getUserByUsernameAndPassword( - $config, - $cache, - Data::$testUsername, - Data::$testPassword - ); - - $this->assertNotNull($dbUser); - } - - /** - * @throws ModelException - */ - public function testGetUserByWrongTokenReturnsNull(): void - { - /** @var Users $result */ - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - $signer = new Hmac(); - $builder = new Builder($signer); - $token = $builder - ->setIssuer('https://somedomain.com') - ->setAudience($this->getTokenAudience()) - ->setId(Data::$testTokenId) - ->setPassphrase(Data::$strongPassphrase) - ->getToken() - ; - - /** @var Cache $cache */ - $cache = $this->grabFromDi('cache'); - /** @var Config $config */ - $config = $this->grabFromDi('config'); - $actual = $this->getUserByToken($config, $cache, $token); - - $this->assertNull($actual); - } - - public function testGetUserByWrongUsernameAndPasswordReturnsNull(): void - { - /** @var Users $result */ - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPassword, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); - - /** @var Cache $cache */ - $cache = $this->grabFromDi('cache'); - /** @var Config $config */ - $config = $this->grabFromDi('config'); - $dbUser = $this->getUserByUsernameAndPassword( - $config, - $cache, - Data::$testUsername, - 'nothing' - ); - - $this->assertNull($dbUser); - } -} From 0b0f117e892fd287d3dd9c2a8562ab83c30fb3be Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 21:23:17 -0500 Subject: [PATCH 29/47] [#42] - let each model declare the fields it publishes --- resources/php-cs-fixer.php | 2 -- src/Models/Companies.php | 14 ++++++++++++++ src/Models/CompaniesXProducts.php | 12 ++++++++++++ src/Models/IndividualTypes.php | 13 +++++++++++++ src/Models/Individuals.php | 18 ++++++++++++++++++ src/Models/ProductTypes.php | 13 +++++++++++++ src/Models/Products.php | 16 ++++++++++++++++ src/Models/Users.php | 18 ++++++++++++++++++ src/Mvc/Model/AbstractModel.php | 13 +++++++++++++ src/Transformers/BaseTransformer.php | 13 +++++++++---- tests/Api/Users/GetTest.php | 20 ++++++++++++++++++++ tests/Support/Data.php | 9 ++++----- 12 files changed, 150 insertions(+), 11 deletions(-) diff --git a/resources/php-cs-fixer.php b/resources/php-cs-fixer.php index 20c969d2..d6a32b91 100644 --- a/resources/php-cs-fixer.php +++ b/resources/php-cs-fixer.php @@ -15,8 +15,6 @@ use PhpCsFixer\Finder; use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; -use function dirname; - $root = dirname(__FILE__, 2); $finder = Finder::create() diff --git a/src/Models/Companies.php b/src/Models/Companies.php index 26081328..0cd42a70 100644 --- a/src/Models/Companies.php +++ b/src/Models/Companies.php @@ -39,6 +39,20 @@ public function getModelFilters(): array 'phone' => Filter::FILTER_STRING, ]; } + /** + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'name', + 'address', + 'city', + 'phone', + ]; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/CompaniesXProducts.php b/src/Models/CompaniesXProducts.php index 2f91dd66..2edb4913 100644 --- a/src/Models/CompaniesXProducts.php +++ b/src/Models/CompaniesXProducts.php @@ -34,6 +34,18 @@ public function getModelFilters(): array 'productId' => Filter::FILTER_ABSINT, ]; } + + /** + * Nothing. This is the companies/products junction; it is not an API + * resource and has no route, controller or transformer of its own. + * + * @return array + */ + public function getPublicFields(): array + { + return []; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/IndividualTypes.php b/src/Models/IndividualTypes.php index 1684a5e1..d926e751 100644 --- a/src/Models/IndividualTypes.php +++ b/src/Models/IndividualTypes.php @@ -35,6 +35,19 @@ public function getModelFilters(): array 'description' => Filter::FILTER_STRING, ]; } + + /** + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'name', + 'description', + ]; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/Individuals.php b/src/Models/Individuals.php index 2e03e9b6..4136fad4 100644 --- a/src/Models/Individuals.php +++ b/src/Models/Individuals.php @@ -40,6 +40,24 @@ public function getModelFilters(): array 'suffix' => Filter::FILTER_STRING, ]; } + + /** + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'companyId', + 'typeId', + 'prefix', + 'first', + 'middle', + 'last', + 'suffix', + ]; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/ProductTypes.php b/src/Models/ProductTypes.php index 5923e18b..aeb27238 100644 --- a/src/Models/ProductTypes.php +++ b/src/Models/ProductTypes.php @@ -35,6 +35,19 @@ public function getModelFilters(): array 'description' => Filter::FILTER_STRING, ]; } + + /** + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'name', + 'description', + ]; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/Products.php b/src/Models/Products.php index 6f3fb8ec..1f347918 100644 --- a/src/Models/Products.php +++ b/src/Models/Products.php @@ -38,6 +38,22 @@ public function getModelFilters(): array 'price' => Filter::FILTER_FLOAT, ]; } + + /** + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'typeId', + 'name', + 'description', + 'quantity', + 'price', + ]; + } + /** * Initialize relationships and model properties * diff --git a/src/Models/Users.php b/src/Models/Users.php index 28f8d586..6e22ab3e 100644 --- a/src/Models/Users.php +++ b/src/Models/Users.php @@ -49,6 +49,24 @@ public function getModelFilters(): array ]; } + /** + * `password` and `tokenPassword` are deliberately absent. `tokenPassword` + * is the passphrase the token signature is verified against - publishing it + * would let any caller forge a token for this user. + * + * @return array + */ + public function getPublicFields(): array + { + return [ + 'id', + 'status', + 'username', + 'issuer', + 'tokenId', + ]; + } + /** * Returns the string token * diff --git a/src/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php index 003e876c..8124ef9f 100644 --- a/src/Mvc/Model/AbstractModel.php +++ b/src/Mvc/Model/AbstractModel.php @@ -61,6 +61,19 @@ public function getModelMessages(Logger $logger = null): string return $error; } + + /** + * The fields this model is willing to publish through the API. + * + * Deliberately separate from getModelFilters(): that map answers how a + * field is sanitised, which is not the same question as whether the world + * may see it. Every model must answer this one for itself, so that adding + * a column never publishes it by accident. + * + * @return array + */ + abstract public function getPublicFields(): array; + /** * Master initializer * diff --git a/src/Transformers/BaseTransformer.php b/src/Transformers/BaseTransformer.php index eab50b48..fd97aa38 100644 --- a/src/Transformers/BaseTransformer.php +++ b/src/Transformers/BaseTransformer.php @@ -20,7 +20,6 @@ use Phalcon\Api\Mvc\Model\AbstractModel; use function array_intersect; -use function array_keys; /** * Class BaseTransformer @@ -53,9 +52,15 @@ public function __construct(array $fields = [], string $resource = '') */ public function transform(AbstractModel $model): array { - $modelFields = array_keys($model->getModelFilters()); - $requestedFields = $this->fields[$this->resource] ?? $modelFields; - $fields = array_intersect($modelFields, $requestedFields); + /** + * The model decides what may be published. A caller asking for a field + * outside that set - `?fields[users]=password` - gets nothing back for + * it, because the intersection is taken against the public set and not + * against every column the model happens to have. + */ + $publicFields = $model->getPublicFields(); + $requestedFields = $this->fields[$this->resource] ?? $publicFields; + $fields = array_intersect($publicFields, $requestedFields); $data = []; foreach ($fields as $field) { $data[$field] = $model->get($field); diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php index 93f2e3af..8a6eba5e 100644 --- a/tests/Api/Users/GetTest.php +++ b/tests/Api/Users/GetTest.php @@ -70,6 +70,26 @@ public function testGetManyUsers(): void ); } + /** + * The suite asserts JSON subsets, so extra attributes in a response pass + * silently - which is how `password` and `tokenPassword` were published for + * as long as they were. This asserts their absence explicitly. + */ + public function testGetManyUsersDoesNotPublishSecrets(): void + { + $record = $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + + $this->assertResponseNotContains('password'); + $this->assertResponseNotContains(Data::$testPassword); + $this->assertResponseNotContains(Data::$testTokenPassword); + } + public function testGetManyUsersWithNoData(): void { $this->addApiUserRecord(); diff --git a/tests/Support/Data.php b/tests/Support/Data.php index 60317ebe..ddf53a16 100644 --- a/tests/Support/Data.php +++ b/tests/Support/Data.php @@ -323,11 +323,10 @@ public static function userResponse(AbstractModel $record) 'type' => Relationships::USERS, 'id' => (string) $record->get('id'), 'attributes' => [ - 'status' => $record->get('status'), - 'username' => $record->get('username'), - 'issuer' => $record->get('issuer'), - 'tokenPassword' => $record->get('tokenPassword'), - 'tokenId' => $record->get('tokenId'), + 'status' => $record->get('status'), + 'username' => $record->get('username'), + 'issuer' => $record->get('issuer'), + 'tokenId' => $record->get('tokenId'), ], 'links' => [ 'self' => sprintf( From 468701582ee44637337a2944b9d15c19c53ab2b2 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 21:33:11 -0500 Subject: [PATCH 30/47] [#42] - hash user passwords instead of storing them in clear --- src/Providers/UsersRepositoryProvider.php | 5 +++- src/Repositories/UsersRepository.php | 27 +++++++++++++++---- tests/Api/AbstractApiTestCase.php | 2 +- tests/Api/LoginTest.php | 2 +- tests/Api/Users/GetTest.php | 2 +- .../Repositories/UsersRepositoryTest.php | 11 +++++--- tests/Support/Data.php | 18 ++++++++----- 7 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/Providers/UsersRepositoryProvider.php b/src/Providers/UsersRepositoryProvider.php index 249aaaee..1b285478 100644 --- a/src/Providers/UsersRepositoryProvider.php +++ b/src/Providers/UsersRepositoryProvider.php @@ -29,7 +29,10 @@ public function register(DiInterface $container): void $container->setShared( 'usersRepository', function () use ($container) { - return new UsersRepository($container->getShared('queryService')); + return new UsersRepository( + $container->getShared('queryService'), + $container->getShared('security') + ); } ); } diff --git a/src/Repositories/UsersRepository.php b/src/Repositories/UsersRepository.php index 19ecde88..b6be4be7 100644 --- a/src/Repositories/UsersRepository.php +++ b/src/Repositories/UsersRepository.php @@ -16,6 +16,7 @@ use Phalcon\Api\Constants\Flags; use Phalcon\Api\Models\Users; use Phalcon\Api\Services\QueryService; +use Phalcon\Encryption\Security; use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Token\Token; @@ -27,9 +28,12 @@ class UsersRepository { /** * @param QueryService $queryService + * @param Security $security */ - public function __construct(private readonly QueryService $queryService) - { + public function __construct( + private readonly QueryService $queryService, + private readonly Security $security + ) { } /** @@ -55,7 +59,13 @@ public function getByToken(Token $token): ?Users } /** - * Gets a user from the database based on the username and password + * Gets a user from the database based on the username and password. + * + * The password is not part of the query: hashes are salted, so the same + * password hashes differently every time and no `password = :password:` + * comparison could ever match. The record is fetched by username and the + * password checked against the stored hash afterwards - which also keeps + * the plain password out of the query cache key. * * @param string $username * @param string $password @@ -68,12 +78,19 @@ public function getByUsernameAndPassword( ): ?Users { $parameters = [ 'username' => $username, - 'password' => $password, 'status' => Flags::ACTIVE, ]; $result = $this->queryService->getRecords(Users::class, $parameters); + /** @var Users|null $user */ + $user = $result[0] ?? null; - return $result[0] ?? null; + if (null === $user) { + return null; + } + + return true === $this->security->checkHash($password, $user->get('password')) + ? $user + : null; } } diff --git a/tests/Api/AbstractApiTestCase.php b/tests/Api/AbstractApiTestCase.php index d9363840..00c9053b 100644 --- a/tests/Api/AbstractApiTestCase.php +++ b/tests/Api/AbstractApiTestCase.php @@ -47,7 +47,7 @@ protected function addApiUserRecord(): Users [ 'status' => Flags::ACTIVE, 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'issuer' => Data::$testIssuer, 'tokenPassword' => Data::$testTokenPassword, 'tokenId' => Data::$testTokenId, diff --git a/tests/Api/LoginTest.php b/tests/Api/LoginTest.php index 0d2459fc..85663f85 100644 --- a/tests/Api/LoginTest.php +++ b/tests/Api/LoginTest.php @@ -28,7 +28,7 @@ public function testLoginKnownUser(): void [ 'status' => Flags::ACTIVE, 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'issuer' => Data::$testIssuer, 'tokenPassword' => Data::$strongPassphrase, 'tokenId' => Data::$testTokenId, diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php index 8a6eba5e..03b928ab 100644 --- a/tests/Api/Users/GetTest.php +++ b/tests/Api/Users/GetTest.php @@ -36,7 +36,7 @@ public function testGetManyUsers(): void [ 'status' => 1, 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'issuer' => Data::$testIssuer, 'tokenPassword' => Data::$strongPassphrase, 'tokenId' => Data::$testTokenId, diff --git a/tests/Integration/Library/Repositories/UsersRepositoryTest.php b/tests/Integration/Library/Repositories/UsersRepositoryTest.php index 2c995bc2..24185e96 100644 --- a/tests/Integration/Library/Repositories/UsersRepositoryTest.php +++ b/tests/Integration/Library/Repositories/UsersRepositoryTest.php @@ -22,6 +22,7 @@ use Phalcon\Api\Traits\TokenTrait; use Phalcon\Cache\Cache; use Phalcon\Config\Config; +use Phalcon\Encryption\Security; use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Signer\Hmac; @@ -35,7 +36,7 @@ public function testGetByUsernameAndPassword(): void Users::class, [ 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'status' => 1, 'issuer' => 'phalcon.io', 'tokenId' => Data::$testTokenId, @@ -59,7 +60,7 @@ public function testGetByWrongTokenReturnsNull(): void Users::class, [ 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'status' => 1, 'issuer' => 'phalcon.io', 'tokenId' => Data::$testTokenId, @@ -87,7 +88,7 @@ public function testGetByWrongUsernameAndPasswordReturnsNull(): void Users::class, [ 'username' => Data::$testUsername, - 'password' => Data::$testPassword, + 'password' => Data::$testPasswordHash, 'status' => 1, 'issuer' => 'phalcon.io', 'tokenId' => Data::$testTokenId, @@ -108,7 +109,9 @@ private function getRepository(): UsersRepository $cache = $this->grabFromDi('cache'); /** @var Config $config */ $config = $this->grabFromDi('config'); + /** @var Security $security */ + $security = $this->grabFromDi('security'); - return new UsersRepository(new QueryService($config, $cache)); + return new UsersRepository(new QueryService($config, $cache), $security); } } diff --git a/tests/Support/Data.php b/tests/Support/Data.php index ddf53a16..4d8594ce 100644 --- a/tests/Support/Data.php +++ b/tests/Support/Data.php @@ -45,12 +45,18 @@ class Data public static $productTypesRecordUrl = '/product-types/%s'; public static $productTypesUrl = '/product-types'; - public static $strongPassphrase = 'DR^3*ZwnAHKc9yP$YSpW98dsmHJBax5&'; - public static $testIssuer = 'https://niden.net'; - public static $testPassword = 'testpass'; - public static $testTokenId = '110011'; - public static $testTokenPassword = 'DR^4*ZwnAHKc0yP$YSpW09dsmHJBax6&'; - public static $testUsername = 'testuser'; + public static $strongPassphrase = 'DR^3*ZwnAHKc9yP$YSpW98dsmHJBax5&'; + public static $testIssuer = 'https://niden.net'; + public static $testPassword = 'testpass'; + /** + * The bcrypt hash of $testPassword. Hardcoded on purpose: fixtures store + * the hash, requests send the plain password, and hashing on every insert + * would cost a bcrypt round per fixture for no benefit. + */ + public static $testPasswordHash = '$2y$10$DSCDlw9tZtmQikTY8cwbGuUZSMcPo64YfRYCTREygVUMJTDqjTHFu'; + public static $testTokenId = '110011'; + public static $testTokenPassword = 'DR^4*ZwnAHKc0yP$YSpW09dsmHJBax6&'; + public static $testUsername = 'testuser'; public static $usersUrl = '/users'; public static $wrongUrl = '/sommething'; From 1b1c942161e0de52954d9590a6337e305700c60e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 21:49:24 -0500 Subject: [PATCH 31/47] [#42] - address code review findings --- src/Api/Controllers/BaseController.php | 8 ++--- src/Api/Controllers/LoginController.php | 3 -- src/Providers/ModelsMetadataProvider.php | 21 ++++++++----- src/Repositories/UsersRepository.php | 21 ++++++++++++- src/Services/QueryService.php | 38 ++++++++++++++++++------ tests/Api/Users/GetTest.php | 29 ++++++++++-------- 6 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index 60362a89..b0ddaa00 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -21,7 +21,6 @@ use Phalcon\Filter\Filter; use Phalcon\Mvc\Controller; use Phalcon\Mvc\Micro; -use Phalcon\Mvc\Model\MetaData\Libmemcached as ModelsMetadataCache; use function explode; use function implode; @@ -33,10 +32,9 @@ /** * Class BaseController * - * @property Micro $application - * @property ModelsMetadataCache $modelsMetadata - * @property QueryService $queryService - * @property Response $response + * @property Micro $application + * @property QueryService $queryService + * @property Response $response */ class BaseController extends Controller { diff --git a/src/Api/Controllers/LoginController.php b/src/Api/Controllers/LoginController.php index a93e77d9..c3ef1bcb 100644 --- a/src/Api/Controllers/LoginController.php +++ b/src/Api/Controllers/LoginController.php @@ -18,7 +18,6 @@ use Phalcon\Api\Http\Response; use Phalcon\Api\Models\Users; use Phalcon\Api\Repositories\UsersRepository; -use Phalcon\Api\Traits\TokenTrait; use Phalcon\Filter\Filter; use Phalcon\Mvc\Controller; @@ -31,8 +30,6 @@ */ class LoginController extends Controller { - use TokenTrait; - /** * Default action logging in * diff --git a/src/Providers/ModelsMetadataProvider.php b/src/Providers/ModelsMetadataProvider.php index 6a2a5ad9..94c7dcfc 100644 --- a/src/Providers/ModelsMetadataProvider.php +++ b/src/Providers/ModelsMetadataProvider.php @@ -37,19 +37,26 @@ function () use ($config) { $metadata = $config->get('metadata'); $devMode = $config->path('app.devMode'); $key = (true === $devMode) ? 'dev' : 'prod'; - $options = $metadata->get($key, []) + $entry = $metadata->get($key, []) ->toArray() ; - $adapter = $options['adapter'] ?? Redis::class; + + /** + * The entry is ['adapter' => ..., 'options' => [...]]; the + * adapter wants the inner array. Handing it the outer one costs + * it the host and sends it to 127.0.0.1. + */ + $adapter = $entry['adapter'] ?? Redis::class; + $options = $entry['options'] ?? []; if ($adapter === Memory::class) { return new $adapter($options); - } else { - $serializer = new SerializerFactory(); - $adapterFactory = new AdapterFactory($serializer); - - return new $adapter($adapterFactory, $options); } + + $serializer = new SerializerFactory(); + $adapterFactory = new AdapterFactory($serializer); + + return new $adapter($adapterFactory, $options); } ); } diff --git a/src/Repositories/UsersRepository.php b/src/Repositories/UsersRepository.php index b6be4be7..7ba20c36 100644 --- a/src/Repositories/UsersRepository.php +++ b/src/Repositories/UsersRepository.php @@ -26,6 +26,13 @@ */ class UsersRepository { + /** + * A valid bcrypt hash of a value nobody knows. Checked against when no user + * matches, so that a miss costs the same bcrypt round as a hit and the + * response time stops telling an attacker which usernames exist. + */ + private const DUMMY_HASH = '$2y$10$jG0Drp7/ZBXXPPsW1zzG6uCNpNMMwTOucqTCnX5ikNmicIJ0kqcEq'; + /** * @param QueryService $queryService * @param Security $security @@ -67,6 +74,11 @@ public function getByToken(Token $token): ?Users * password checked against the stored hash afterwards - which also keeps * the plain password out of the query cache key. * + * The cache is bypassed: with the password out of the query, the entry is + * keyed on the username alone, so a cached row would keep answering with a + * stale hash - rejecting the new password and accepting the old one for as + * long as the entry lived. + * * @param string $username * @param string $password * @@ -81,11 +93,18 @@ public function getByUsernameAndPassword( 'status' => Flags::ACTIVE, ]; - $result = $this->queryService->getRecords(Users::class, $parameters); + $result = $this->queryService->getRecords( + Users::class, + $parameters, + '', + false + ); /** @var Users|null $user */ $user = $result[0] ?? null; if (null === $user) { + $this->security->checkHash($password, self::DUMMY_HASH); + return null; } diff --git a/src/Services/QueryService.php b/src/Services/QueryService.php index 66e0ec78..a4da2939 100644 --- a/src/Services/QueryService.php +++ b/src/Services/QueryService.php @@ -47,13 +47,17 @@ public function __construct( * @param string $class * @param array $where * @param string $orderBy + * @param bool $useCache Pass false for anything that must + * read the current row - see + * getResults() * * @return ResultsetInterface */ public function getRecords( string $class, array $where = [], - string $orderBy = '' + string $orderBy = '', + bool $useCache = true ): ResultsetInterface { $builder = new Builder(); $builder->addFrom($class, 't1'); @@ -69,20 +73,27 @@ public function getRecords( $builder->orderBy($orderBy); } - return $this->getResults($builder, $where); + return $this->getResults($builder, $where, $useCache); } /** - * Runs the builder query if there is no cached data + * Runs the builder query if there is no cached data. + * + * The cache key is derived from the query and its bound values, so a row + * whose other columns changed is still served from the entry keyed on the + * columns that did not. That is fine for the read endpoints and wrong for a + * credential check, which is why the caller can opt out. * * @param Builder $builder * @param array $where + * @param bool $useCache * * @return ResultsetInterface */ private function getResults( Builder $builder, - array $where = [] + array $where = [], + bool $useCache = true ): ResultsetInterface { /** * Calculate the cache key @@ -91,17 +102,26 @@ private function getResults( $params = json_encode($where); $cacheKey = sha1(sprintf('%s-%s.cache', $phql, $params)); - if ( - true !== $this->config->path('app.devMode') && - true === $this->cache->has($cacheKey) - ) { + /** + * One decision, used for both the read and the write. A query that is + * never read back is not written either: dev mode means no caching + * rather than write-only caching, and an opted-out query keeps the + * users' hashes out of the cache entirely. + */ + $isCacheable = true === $useCache && + true !== $this->config->path('app.devMode'); + + if (true === $isCacheable && true === $this->cache->has($cacheKey)) { /** @var ResultsetInterface $data */ $data = $this->cache->get($cacheKey); } else { $data = $builder->getQuery() ->execute() ; - $this->cache->set($cacheKey, $data); + + if (true === $isCacheable) { + $this->cache->set($cacheKey, $data); + } } return $data; diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php index 03b928ab..aa0fbf20 100644 --- a/tests/Api/Users/GetTest.php +++ b/tests/Api/Users/GetTest.php @@ -70,12 +70,26 @@ public function testGetManyUsers(): void ); } + public function testGetManyUsersWithNoData(): void + { + $this->addApiUserRecord(); + $token = $this->apiLogin(); + + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet(Data::$usersUrl); + $this->unsetHttpHeader('Authorization'); + $this->assertResponseIsSuccessful(); + } + /** * The suite asserts JSON subsets, so extra attributes in a response pass * silently - which is how `password` and `tokenPassword` were published for * as long as they were. This asserts their absence explicitly. + * + * The stored password is a hash, so it is the hash that would leak - not + * the plain password, which never reaches the database. */ - public function testGetManyUsersDoesNotPublishSecrets(): void + public function testGetUserDoesNotPublishSecrets(): void { $record = $this->addApiUserRecord(); $token = $this->apiLogin(); @@ -86,21 +100,10 @@ public function testGetManyUsersDoesNotPublishSecrets(): void $this->assertResponseIsSuccessful(); $this->assertResponseNotContains('password'); - $this->assertResponseNotContains(Data::$testPassword); + $this->assertResponseNotContains(Data::$testPasswordHash); $this->assertResponseNotContains(Data::$testTokenPassword); } - public function testGetManyUsersWithNoData(): void - { - $this->addApiUserRecord(); - $token = $this->apiLogin(); - - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl); - $this->unsetHttpHeader('Authorization'); - $this->assertResponseIsSuccessful(); - } - public function testLoginKnownUserCorrectToken(): void { $this->addApiUserRecord(); From e30f97b2c84595b73837fb9a80724a04b6468b03 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:06:35 -0500 Subject: [PATCH 32/47] [#42] - consolidate duplicated logic in config, transformers and tests --- src/Bootstrap/AbstractBootstrap.php | 21 +- src/Bootstrap/Api.php | 16 ++ src/Bootstrap/Cli.php | 20 +- src/Core/config.php | 16 ++ .../IndividualTypesTransformer.php | 3 - src/Transformers/IndividualsTransformer.php | 5 +- src/Transformers/ProductsTransformer.php | 2 +- tests/Api/AbstractApiTestCase.php | 16 ++ tests/Api/Companies/GetFieldsTest.php | 5 +- tests/Api/Companies/GetIncludesTest.php | 9 +- tests/Api/Companies/GetSortTest.php | 20 +- tests/Api/Companies/GetTest.php | 16 +- tests/Api/IndividualTypes/GetTest.php | 17 +- tests/Api/Individuals/GetTest.php | 21 +- tests/Api/ProductTypes/GetTest.php | 17 +- tests/Api/Products/GetTest.php | 21 +- tests/Api/Users/GetTest.php | 16 +- .../Repositories/UsersRepositoryTest.php | 43 ++-- .../IndividualsTransformerTest.php | 18 +- .../Transformers/ProductsTransformerTest.php | 18 +- tests/Support/Data.php | 237 ++++++++---------- 21 files changed, 274 insertions(+), 283 deletions(-) diff --git a/src/Bootstrap/AbstractBootstrap.php b/src/Bootstrap/AbstractBootstrap.php index 3126ad71..e37a2c7e 100644 --- a/src/Bootstrap/AbstractBootstrap.php +++ b/src/Bootstrap/AbstractBootstrap.php @@ -48,7 +48,7 @@ public function __construct() } if ([] === $this->providers) { - $this->providers = require appPath('src/Api/providers.php'); + $this->providers = require appPath($this->providersPath()); } $this @@ -86,6 +86,21 @@ public function getResponse(): Response */ abstract public function run(); + /** + * The application class this bootstrap builds - a Micro for HTTP, a Console + * for the command line. + * + * @return class-string + */ + abstract protected function applicationClass(): string; + + /** + * The provider list this application registers, relative to the app root. + * + * @return string + */ + abstract protected function providersPath(): string; + /** * Set up the application object in the container * @@ -93,7 +108,9 @@ abstract public function run(); */ protected function setupApplication(): AbstractBootstrap { - $this->application = new Micro($this->container); + $class = $this->applicationClass(); + $this->application = new $class($this->container); + $this->container->setShared('application', $this->application); return $this; diff --git a/src/Bootstrap/Api.php b/src/Bootstrap/Api.php index 39fa3006..f5f62838 100644 --- a/src/Bootstrap/Api.php +++ b/src/Bootstrap/Api.php @@ -33,4 +33,20 @@ public function run() return $this->application->handle($uri); } + + /** + * @return class-string + */ + protected function applicationClass(): string + { + return Micro::class; + } + + /** + * @return string + */ + protected function providersPath(): string + { + return 'src/Api/providers.php'; + } } diff --git a/src/Bootstrap/Cli.php b/src/Bootstrap/Cli.php index 816c0b61..de14272f 100644 --- a/src/Bootstrap/Cli.php +++ b/src/Bootstrap/Cli.php @@ -16,8 +16,6 @@ use Phalcon\Cli\Console; use Phalcon\Di\FactoryDefault\Cli as PhCli; -use function Phalcon\Api\Core\appPath; - /** * Class Cli * @@ -31,7 +29,6 @@ class Cli extends AbstractBootstrap public function __construct() { $this->container = new PhCli(); - $this->providers = require appPath('src/Cli/providers.php'); $this->processArguments(); @@ -51,16 +48,19 @@ public function run() } /** - * Set up the application object in the container - * - * @return Cli + * @return class-string */ - protected function setupApplication(): Cli + protected function applicationClass(): string { - $this->application = new Console($this->container); - $this->container->setShared('application', $this->application); + return Console::class; + } - return $this; + /** + * @return string + */ + protected function providersPath(): string + { + return 'src/Cli/providers.php'; } /** diff --git a/src/Core/config.php b/src/Core/config.php index 41d73835..6d4947e3 100644 --- a/src/Core/config.php +++ b/src/Core/config.php @@ -16,6 +16,22 @@ use function Phalcon\Api\Core\envValue; +/** + * The cache and the production metadata store are the same Redis, differing + * only in the prefix they namespace their keys with. Stated once: the last time + * these two blocks were maintained separately, a `lifetime` that arrived as a + * string had to be cast in both, and missing either one returns HTTP 500 from + * every endpoint that reads a record. + */ +$redisOptions = static fn (string $prefix): array => [ + 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), + 'port' => envValue('DATA_API_REDIS_PORT', 6379), + 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), + // Env values arrive as strings; AbstractAdapter::getTtl() returns int. + 'lifetime' => (int) envValue('CACHE_LIFETIME', 86400), + 'prefix' => $prefix, +]; + return [ 'app' => [ 'version' => envValue('VERSION', time()), diff --git a/src/Transformers/IndividualTypesTransformer.php b/src/Transformers/IndividualTypesTransformer.php index ee4c15a8..e365ec28 100644 --- a/src/Transformers/IndividualTypesTransformer.php +++ b/src/Transformers/IndividualTypesTransformer.php @@ -27,9 +27,6 @@ class IndividualTypesTransformer extends BaseTransformer Relationships::INDIVIDUALS, ]; - /** @var string */ - protected string $resource = Relationships::INDIVIDUAL_TYPES; - /** * @param IndividualTypes $type * diff --git a/src/Transformers/IndividualsTransformer.php b/src/Transformers/IndividualsTransformer.php index 607e882c..d32949a7 100644 --- a/src/Transformers/IndividualsTransformer.php +++ b/src/Transformers/IndividualsTransformer.php @@ -28,9 +28,6 @@ class IndividualsTransformer extends BaseTransformer Relationships::INDIVIDUAL_TYPES, ]; - /** @var string */ - protected string $resource = Relationships::INDIVIDUALS; - /** * Includes the companies * @@ -60,7 +57,7 @@ public function includeIndividualTypes(Individuals $individual): Item return $this->getRelatedData( 'item', $individual, - BaseTransformer::class, + IndividualTypesTransformer::class, Relationships::INDIVIDUAL_TYPES ); } diff --git a/src/Transformers/ProductsTransformer.php b/src/Transformers/ProductsTransformer.php index 495bdcfa..c9343155 100644 --- a/src/Transformers/ProductsTransformer.php +++ b/src/Transformers/ProductsTransformer.php @@ -57,7 +57,7 @@ public function includeProductTypes(Products $product): Item return $this->getRelatedData( 'item', $product, - BaseTransformer::class, + ProductTypesTransformer::class, Relationships::PRODUCT_TYPES ); } diff --git a/tests/Api/AbstractApiTestCase.php b/tests/Api/AbstractApiTestCase.php index 00c9053b..3be19be5 100644 --- a/tests/Api/AbstractApiTestCase.php +++ b/tests/Api/AbstractApiTestCase.php @@ -118,6 +118,22 @@ protected function assertSuccessJsonResponse(string $key = 'data', array $data = $this->assertResponseContainsJson([$key => $data]); } + /** + * Sends a GET carrying the bearer token, then drops the header again. + * + * The header is per-test rather than per-request state, so leaving it set + * would leak into whatever the test does next. + * + * @param string $token + * @param string $url + */ + protected function sendGetAs(string $token, string $url): void + { + $this->haveHttpHeader('Authorization', 'Bearer ' . $token); + $this->sendGet($url); + $this->unsetHttpHeader('Authorization'); + } + private function assertErrorResponse(int $code): void { $this->assertResponseIsJson(); diff --git a/tests/Api/Companies/GetFieldsTest.php b/tests/Api/Companies/GetFieldsTest.php index 12c900d6..dfdb211e 100644 --- a/tests/Api/Companies/GetFieldsTest.php +++ b/tests/Api/Companies/GetFieldsTest.php @@ -39,8 +39,8 @@ private function runCompaniesWithIncludesAndFields(string $fields = ''): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$companiesRecordIncludesUrl, $com->get('id'), @@ -49,7 +49,6 @@ private function runCompaniesWithIncludesAndFields(string $fields = ''): void '&fields[' . Relationships::COMPANIES . ']=id,name,city' . '&fields[' . Relationships::PRODUCTS . ']=id,name,price' . $fields ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $element = [ diff --git a/tests/Api/Companies/GetIncludesTest.php b/tests/Api/Companies/GetIncludesTest.php index c8b910a0..1274c072 100644 --- a/tests/Api/Companies/GetIncludesTest.php +++ b/tests/Api/Companies/GetIncludesTest.php @@ -45,9 +45,7 @@ public function testGetCompanyUnknownInclude(): void $company = $this->addCompanyRecord('com-a-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesRecordIncludesUrl, $company->get('id'), 'unknown')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesRecordIncludesUrl, $company->get('id'), 'unknown')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -67,15 +65,14 @@ private function checkIncludes(array $includes = []): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$companiesRecordIncludesUrl, $com->get('id'), implode(',', $includes) ) ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $element = [ diff --git a/tests/Api/Companies/GetSortTest.php b/tests/Api/Companies/GetSortTest.php index bb86aecc..b94fa36d 100644 --- a/tests/Api/Companies/GetSortTest.php +++ b/tests/Api/Companies/GetSortTest.php @@ -27,9 +27,7 @@ public function testGetCompaniesInvalidSort(): void $this->addCompanyRecord('com-a-', '', 'city-b'); $this->addCompanyRecord('com-b-', '', 'city-b'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesSortUrl, 'unknown')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesSortUrl, 'unknown')); $this->assertResponseIs400(); } @@ -41,9 +39,7 @@ public function testGetCompaniesMultipleSort(): void $comOne = $this->addCompanyRecord('com-a-', '', 'city-b'); $comTwo = $this->addCompanyRecord('com-b-', '', 'city-b'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesSortUrl, 'city,name')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesSortUrl, 'city,name')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -53,9 +49,7 @@ public function testGetCompaniesMultipleSort(): void ] ); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesSortUrl, 'city,-name')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesSortUrl, 'city,-name')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -74,9 +68,7 @@ public function testGetCompaniesSingleSort(): void $comOne = $this->addCompanyRecord('com-a-'); $comTwo = $this->addCompanyRecord('com-b-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesSortUrl, 'name')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesSortUrl, 'name')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -86,9 +78,7 @@ public function testGetCompaniesSingleSort(): void ] ); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesSortUrl, '-name')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesSortUrl, '-name')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', diff --git a/tests/Api/Companies/GetTest.php b/tests/Api/Companies/GetTest.php index 612ccf45..6655d91e 100644 --- a/tests/Api/Companies/GetTest.php +++ b/tests/Api/Companies/GetTest.php @@ -27,9 +27,7 @@ public function testGetCompanies(): void $comOne = $this->addCompanyRecord('com-a-'); $comTwo = $this->addCompanyRecord('com-b-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$companiesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$companiesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -45,9 +43,7 @@ public function testGetCompaniesNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$companiesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$companiesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse(); } @@ -59,9 +55,7 @@ public function testGetCompany(): void $company = $this->addCompanyRecord('com-a-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesRecordUrl, $company->get('id'))); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesRecordUrl, $company->get('id'))); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -76,9 +70,7 @@ public function testGetUnknownCompany(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$companiesRecordUrl, 1)); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$companiesRecordUrl, 1)); $this->assertResponseIs404(); } } diff --git a/tests/Api/IndividualTypes/GetTest.php b/tests/Api/IndividualTypes/GetTest.php index 876fbf23..d8d8c470 100644 --- a/tests/Api/IndividualTypes/GetTest.php +++ b/tests/Api/IndividualTypes/GetTest.php @@ -30,9 +30,7 @@ public function testGetIndividualTypes(): void $typeOne = $this->addIndividualTypeRecord('type-a-'); $typeTwo = $this->addIndividualTypeRecord('type-b-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$individualTypesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$individualTypesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -48,9 +46,7 @@ public function testGetIndividualTypesNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$individualTypesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$individualTypesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse(); } @@ -65,15 +61,14 @@ public function testGetIndividualTypesWithIncludesIndividuals(): void $individualOne = $this->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); $individualTwo = $this->addIndividualRecord('prd-b-', $company->get('id'), $individualType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$individualTypesRecordIncludesUrl, $individualType->get('id'), Relationships::INDIVIDUALS ) ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -141,9 +136,7 @@ public function testGetUnknownIndividualTypes(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$individualTypesRecordUrl, 1)); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$individualTypesRecordUrl, 1)); $this->assertResponseIs404(); } } diff --git a/tests/Api/Individuals/GetTest.php b/tests/Api/Individuals/GetTest.php index 1009dccd..cfbd4ed3 100644 --- a/tests/Api/Individuals/GetTest.php +++ b/tests/Api/Individuals/GetTest.php @@ -33,9 +33,7 @@ public function testGetIndividual(): void $individualType = $this->addIndividualTypeRecord('prt-a-'); $individual = $this->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$individualsRecordUrl, $individual->get('id'))); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$individualsRecordUrl, $individual->get('id'))); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -55,9 +53,7 @@ public function testGetIndividuals(): void $individualOne = $this->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); $individualTwo = $this->addIndividualRecord('ind-b-', $company->get('id'), $individualType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$individualsUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$individualsUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -73,9 +69,7 @@ public function testGetIndividualsNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$individualsUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$individualsUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse(); } @@ -100,9 +94,7 @@ public function testGetUnknownIndividual(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$individualsRecordUrl, 1)); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$individualsRecordUrl, 1)); $this->assertResponseIs404(); } @@ -130,15 +122,14 @@ private function checkIncludes(array $includes = []): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$individualsRecordIncludesUrl, $individual->get('id'), implode(',', $includes) ) ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $element = [ diff --git a/tests/Api/ProductTypes/GetTest.php b/tests/Api/ProductTypes/GetTest.php index 61b64786..52a13316 100644 --- a/tests/Api/ProductTypes/GetTest.php +++ b/tests/Api/ProductTypes/GetTest.php @@ -30,9 +30,7 @@ public function testGetProductTypes(): void $typeOne = $this->addProductTypeRecord('type-a-'); $typeTwo = $this->addProductTypeRecord('type-b-'); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$productTypesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$productTypesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -48,9 +46,7 @@ public function testGetProductTypesNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$productTypesUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$productTypesUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse(); } @@ -64,15 +60,14 @@ public function testGetProductTypesWithIncludesProducts(): void $productOne = $this->addProductRecord('prd-a-', $productType->get('id')); $productTwo = $this->addProductRecord('prd-b-', $productType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$productTypesRecordIncludesUrl, $productType->get('id'), Relationships::PRODUCTS ) ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -140,9 +135,7 @@ public function testGetUnknownProductTypes(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$productTypesRecordUrl, 1)); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$productTypesRecordUrl, 1)); $this->assertResponseIs404(); } } diff --git a/tests/Api/Products/GetTest.php b/tests/Api/Products/GetTest.php index f150c8fc..d9a24f40 100644 --- a/tests/Api/Products/GetTest.php +++ b/tests/Api/Products/GetTest.php @@ -32,9 +32,7 @@ public function testGetProduct(): void $productType = $this->addProductTypeRecord('prt-a-'); $product = $this->addProductRecord('prd-a-', $productType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$productsRecordUrl, $product->get('id'))); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$productsRecordUrl, $product->get('id'))); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -53,9 +51,7 @@ public function testGetProducts(): void $productOne = $this->addProductRecord('prd-a-', $productType->get('id')); $productTwo = $this->addProductRecord('prd-b-', $productType->get('id')); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$productsUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$productsUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -71,9 +67,7 @@ public function testGetProductsNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$productsUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$productsUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse(); } @@ -98,9 +92,7 @@ public function testGetUnknownProduct(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(sprintf(Data::$productsRecordUrl, 1)); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, sprintf(Data::$productsRecordUrl, 1)); $this->assertResponseIs404(); } @@ -132,15 +124,14 @@ private function checkIncludes(array $includes = []): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet( + $this->sendGetAs( + $token, sprintf( Data::$productsRecordIncludesUrl, $product->get('id'), implode(',', $includes) ) ); - $this->unsetHttpHeader('Authorization'); $this->assertResponseIsSuccessful(); $element = [ diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php index aa0fbf20..c6fc3bd7 100644 --- a/tests/Api/Users/GetTest.php +++ b/tests/Api/Users/GetTest.php @@ -57,9 +57,7 @@ public function testGetManyUsers(): void $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$usersUrl); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', @@ -75,9 +73,7 @@ public function testGetManyUsersWithNoData(): void $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$usersUrl); $this->assertResponseIsSuccessful(); } @@ -94,9 +90,7 @@ public function testGetUserDoesNotPublishSecrets(): void $record = $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$usersUrl . '/' . $record->get('id')); $this->assertResponseIsSuccessful(); $this->assertResponseNotContains('password'); @@ -257,9 +251,7 @@ public function testLoginKnownUserValidToken(): void $record = $this->addApiUserRecord(); $token = $this->apiLogin(); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl . '/' . $record->get('id')); - $this->unsetHttpHeader('Authorization'); + $this->sendGetAs($token, Data::$usersUrl . '/' . $record->get('id')); $this->assertResponseIsSuccessful(); $this->assertSuccessJsonResponse( 'data', diff --git a/tests/Integration/Library/Repositories/UsersRepositoryTest.php b/tests/Integration/Library/Repositories/UsersRepositoryTest.php index 24185e96..0b9b87dd 100644 --- a/tests/Integration/Library/Repositories/UsersRepositoryTest.php +++ b/tests/Integration/Library/Repositories/UsersRepositoryTest.php @@ -32,16 +32,7 @@ final class UsersRepositoryTest extends AbstractIntegrationTestCase public function testGetByUsernameAndPassword(): void { - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPasswordHash, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); + $this->addUserRecord(); $dbUser = $this->getRepository()->getByUsernameAndPassword( Data::$testUsername, @@ -56,16 +47,7 @@ public function testGetByUsernameAndPassword(): void */ public function testGetByWrongTokenReturnsNull(): void { - $this->haveRecordWithFields( - Users::class, - [ - 'username' => Data::$testUsername, - 'password' => Data::$testPasswordHash, - 'status' => 1, - 'issuer' => 'phalcon.io', - 'tokenId' => Data::$testTokenId, - ] - ); + $this->addUserRecord(); $signer = new Hmac(); $builder = new Builder($signer); @@ -84,7 +66,19 @@ public function testGetByWrongTokenReturnsNull(): void public function testGetByWrongUsernameAndPasswordReturnsNull(): void { - $this->haveRecordWithFields( + $this->addUserRecord(); + + $dbUser = $this->getRepository()->getByUsernameAndPassword( + Data::$testUsername, + 'nothing' + ); + + $this->assertNull($dbUser); + } + + private function addUserRecord(): Users + { + return $this->haveRecordWithFields( Users::class, [ 'username' => Data::$testUsername, @@ -94,13 +88,6 @@ public function testGetByWrongUsernameAndPasswordReturnsNull(): void 'tokenId' => Data::$testTokenId, ] ); - - $dbUser = $this->getRepository()->getByUsernameAndPassword( - Data::$testUsername, - 'nothing' - ); - - $this->assertNull($dbUser); } private function getRepository(): UsersRepository diff --git a/tests/Integration/Library/Transformers/IndividualsTransformerTest.php b/tests/Integration/Library/Transformers/IndividualsTransformerTest.php index df5a6fd1..06e3a2e8 100644 --- a/tests/Integration/Library/Transformers/IndividualsTransformerTest.php +++ b/tests/Integration/Library/Transformers/IndividualsTransformerTest.php @@ -150,7 +150,23 @@ public function testTransformer(): void ], 'included' => [ Data::companiesResponse($company), - Data::individualTypeResponse($individualType), + /** + * The included type carries its own relationships now that the + * include uses IndividualTypesTransformer rather than the base + * one - which is what makes `?includes=individual-types.individuals` + * resolvable. + */ + Data::individualTypeResponse($individualType) + [ + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id'), + Relationships::INDIVIDUALS + ), + ], + ], + ], ], ]; diff --git a/tests/Integration/Library/Transformers/ProductsTransformerTest.php b/tests/Integration/Library/Transformers/ProductsTransformerTest.php index 4960b9b1..58190b21 100644 --- a/tests/Integration/Library/Transformers/ProductsTransformerTest.php +++ b/tests/Integration/Library/Transformers/ProductsTransformerTest.php @@ -158,7 +158,23 @@ public function testTransformer(): void ], 'included' => [ Data::companiesResponse($company), - Data::productTypeResponse($productType), + /** + * The included type carries its own relationships now that the + * include uses ProductTypesTransformer rather than the base one + * - which is what makes `?includes=product-types.products` + * resolvable. + */ + Data::productTypeResponse($productType) + [ + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCT_TYPES, + $productType->get('id'), + Relationships::PRODUCTS + ), + ], + ], + ], ], ]; diff --git a/tests/Support/Data.php b/tests/Support/Data.php index 4d8594ce..cfb5e400 100644 --- a/tests/Support/Data.php +++ b/tests/Support/Data.php @@ -68,24 +68,16 @@ class Data */ public static function companiesAddResponse(Companies $record): array { - return [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::COMPANIES, + $record, + [ 'name' => $record->get('name'), 'address' => $record->get('address'), 'city' => $record->get('city'), 'phone' => $record->get('phone'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], - ]; + ] + ); } /** @@ -96,55 +88,32 @@ public static function companiesAddResponse(Companies $record): array */ public static function companiesResponse(Companies $record): array { - return [ - 'type' => Relationships::COMPANIES, - 'id' => (string) $record->get('id'), - 'attributes' => [ + $recordId = $record->get('id'); + + return self::resource( + Relationships::COMPANIES, + $record, + [ 'name' => $record->get('name'), 'address' => $record->get('address'), 'city' => $record->get('city'), 'phone' => $record->get('phone'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], + ] + ) + [ 'relationships' => [ Relationships::PRODUCTS => [ - 'links' => [ - 'self' => sprintf( - '%s/%s/%s/relationships/products', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - 'related' => sprintf( - '%s/%s/%s/products', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ] + 'links' => self::relationshipLinks( + Relationships::COMPANIES, + $recordId, + Relationships::PRODUCTS + ), ], Relationships::INDIVIDUALS => [ - "links" => [ - 'self' => sprintf( - '%s/%s/%s/relationships/individuals', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - 'related' => sprintf( - '%s/%s/%s/individuals', - envValue('APP_URL'), - Relationships::COMPANIES, - $record->get('id') - ), - ], + 'links' => self::relationshipLinks( + Relationships::COMPANIES, + $recordId, + Relationships::INDIVIDUALS + ), ], ], ]; @@ -176,10 +145,10 @@ public static function companyAddJson($name, $address = '', $city = '', $phone = */ public static function individualResponse(Individuals $record): array { - return [ - 'type' => Relationships::INDIVIDUALS, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::INDIVIDUALS, + $record, + [ 'companyId' => $record->get('companyId'), 'typeId' => $record->get('typeId'), 'prefix' => $record->get('prefix'), @@ -187,16 +156,8 @@ public static function individualResponse(Individuals $record): array 'middle' => $record->get('middle'), 'last' => $record->get('last'), 'suffix' => $record->get('suffix'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUALS, - $record->get('id') - ), - ], - ]; + ] + ); } /** @@ -207,22 +168,14 @@ public static function individualResponse(Individuals $record): array */ public static function individualTypeResponse(IndividualTypes $record): array { - return [ - 'type' => Relationships::INDIVIDUAL_TYPES, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::INDIVIDUAL_TYPES, + $record, + [ 'name' => $record->get('name'), 'description' => $record->get('description'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::INDIVIDUAL_TYPES, - $record->get('id') - ), - ], - ]; + ] + ); } /** @@ -244,22 +197,14 @@ public static function loginJson() */ public static function productFieldsResponse(Products $record): array { - return [ - 'type' => Relationships::PRODUCTS, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::PRODUCTS, + $record, + [ 'name' => $record->get('name'), 'price' => $record->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCTS, - $record->get('id') - ), - ], - ]; + ] + ); } /** @@ -270,25 +215,17 @@ public static function productFieldsResponse(Products $record): array */ public static function productResponse(Products $record): array { - return [ - 'type' => Relationships::PRODUCTS, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::PRODUCTS, + $record, + [ 'typeId' => $record->get('typeId'), 'name' => $record->get('name'), 'description' => $record->get('description'), 'quantity' => $record->get('quantity'), 'price' => $record->get('price'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::PRODUCTS, - $record->get('id') - ), - ], - ]; + ] + ); } /** @@ -299,18 +236,64 @@ public static function productResponse(Products $record): array */ public static function productTypeResponse(ProductTypes $record): array { - return [ - 'type' => Relationships::PRODUCT_TYPES, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::PRODUCT_TYPES, + $record, + [ 'name' => $record->get('name'), 'description' => $record->get('description'), - ], + ] + ); + } + + /** + * The self/related pair a relationship carries. + * + * @param string $type + * @param mixed $recordId + * @param string $relationship + * + * @return array + */ + public static function relationshipLinks( + string $type, + $recordId, + string $relationship + ): array { + $base = sprintf('%s/%s/%s', envValue('APP_URL'), $type, $recordId); + + return [ + 'self' => $base . '/relationships/' . $relationship, + 'related' => $base . '/' . $relationship, + ]; + } + + /** + * The JSON:API envelope every resource shares - type, id, attributes and a + * self link. Only the attributes differ between resources, so only the + * attributes are worth stating at each call site. + * + * @param string $type + * @param AbstractModel $record + * @param array $attributes + * + * @return array + * @throws ModelException + */ + public static function resource( + string $type, + AbstractModel $record, + array $attributes + ): array { + return [ + 'type' => $type, + 'id' => (string) $record->get('id'), + 'attributes' => $attributes, 'links' => [ 'self' => sprintf( '%s/%s/%s', envValue('APP_URL'), - Relationships::PRODUCT_TYPES, + $type, $record->get('id') ), ], @@ -325,23 +308,15 @@ public static function productTypeResponse(ProductTypes $record): array */ public static function userResponse(AbstractModel $record) { - return [ - 'type' => Relationships::USERS, - 'id' => (string) $record->get('id'), - 'attributes' => [ + return self::resource( + Relationships::USERS, + $record, + [ 'status' => $record->get('status'), 'username' => $record->get('username'), 'issuer' => $record->get('issuer'), 'tokenId' => $record->get('tokenId'), - ], - 'links' => [ - 'self' => sprintf( - '%s/%s/%s', - envValue('APP_URL'), - Relationships::USERS, - $record->get('id') - ), - ], - ]; + ] + ); } } From 92aea9134248d0867f2ace4c8ddb36d05fa644a8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:20:34 -0500 Subject: [PATCH 33/47] [#42] - replace the stale workflow with quality, tests and coverage jobs --- .github/dependabot.yml | 20 +++ .github/workflows/main.yml | 323 ++++++++++++++++++++++++++++++------- composer.json | 4 +- resources/octocov.pr.yml | 32 ++++ resources/octocov.yml | 39 +++++ 5 files changed, 354 insertions(+), 64 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 resources/octocov.pr.yml create mode 100644 resources/octocov.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..fa2cbe2b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "weekly" + + # Keep the SHA-pinned GitHub Actions current. Dependabot opens reviewable + # pull requests that bump both the commit SHA and the trailing "# version" + # comment; it never merges on its own. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + + - package-ecosystem: "docker" + directory: "/resources/docker" + schedule: + interval: "weekly" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index df05078a..40e54cac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,96 +1,295 @@ -name: Testing Suite +name: "REST API CI" -on: [push, pull_request] +on: + push: + paths-ignore: + - '**.md' + - '**.txt' + pull_request: + workflow_dispatch: env: - DATA_API_MYSQL_HOST: '127.0.0.1' - DATA_API_REDIS_HOST: '127.0.0.1' + # Phalcon v5 C extension constraint (installed from source via PIE) + PHALCON_CONSTRAINT: "^5.0" + EXTENSIONS: "mbstring, intl, json, openssl, redis, pdo_mysql" + # The application reads DATA_API_*; Talon reads DATA_* for its own settings. + # Both are overridden here because tests/.env.test points at the compose + # service names, and Settings::fromEnv() checks getenv() before $_ENV - so a + # real environment variable wins over the file. + DATA_API_MYSQL_HOST: "127.0.0.1" + DATA_API_REDIS_HOST: "127.0.0.1" + DATA_MYSQL_HOST: "127.0.0.1" + DATA_REDIS_HOST: "127.0.0.1" + # The api suite crosses a real HTTP boundary; this is the built-in server + # started below. APP_URL stays http://localhost:8080 - it is what the app + # echoes back in `links`, and the expectations must match the app. + TALON_REST_URL: "http://127.0.0.1:8080" + +permissions: { } + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - run: + quality: + name: "Code quality" + permissions: + contents: read runs-on: ubuntu-latest - name: Workflow - PHP-${{ matrix.php }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 1 + - name: "Setup PHP" + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 + with: + php-version: '8.3' + extensions: ${{ env.EXTENSIONS }} + tools: composer:v2 + + # The quality tools do not need the Phalcon extension: phpstan analyses the + # phalcon/phalcon (v6) source pulled in by Composer. + - name: "Validate composer" + run: composer validate --no-check-all --no-check-publish + + - name: "Install dependencies" + uses: ramsey/composer-install@26d8a556604053a9612623447203a691f406fbe6 # v4 + with: + composer-options: "--prefer-dist" + + - name: "PHPCS" + run: composer cs + + - name: "PHP CS Fixer (dry-run)" + run: composer cs-fixer + + # PHPStan is deliberately not gated yet: `composer analyze` reports 154 + # errors at level max. Uncomment once those are cleared - that work is + # tracked separately and is the next task after this workflow lands. + # - name: "PHPStan" + # run: composer analyze + + tests: + name: "Tests (PHP ${{ matrix.php }}, Phalcon ${{ matrix.variant }})" + permissions: + contents: read + runs-on: ubuntu-latest + needs: + - quality + strategy: + fail-fast: false + matrix: + php: + - '8.1' + - '8.2' + - '8.3' + - '8.4' + variant: + # v5 = cphalcon C extension; v6 = phalcon/phalcon composer package + - 'v5' + - 'v6' services: mysql: - image: mysql:5.7 + image: mysql:8.0 ports: - "3306:3306" env: MYSQL_ROOT_PASSWORD: secret - MYSQL_USER: phalcon MYSQL_DATABASE: phalcon_api - MYSQL_PASSWORD: secret + options: >- + --health-cmd "mysqladmin ping --silent" + --health-interval 10s + --health-timeout 5s + --health-retries 5 redis: - image: redis:5-alpine + image: redis:alpine ports: - "6379:6379" - - strategy: - fail-fast: false - matrix: - php: ['8.0', '8.1' ] - + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - - uses: actions/checkout@v1 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 1 + + - name: "Setup PHP" + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 with: php-version: ${{ matrix.php }} - tools: pecl - extensions: mbstring, intl, json, phalcon-5.0.0RC4 - coverage: xdebug + extensions: ${{ env.EXTENSIONS }} + tools: composer:v2 + + # v5 = cphalcon C extension, built from source via PIE (pecl caps at PHP 8.4). + # v6 needs no extension: it runs on the phalcon/phalcon composer package (a require-dev dep). + - name: "Install Phalcon v5 (cphalcon) via PIE" + if: matrix.variant == 'v5' + run: | + curl -fsSL https://github.com/php/pie/releases/latest/download/pie.phar -o pie.phar + sudo "$(which php)" pie.phar install --no-interaction "phalcon/cphalcon:${{ env.PHALCON_CONSTRAINT }}" + php -m | grep -i phalcon + + - name: "Install dependencies" + uses: ramsey/composer-install@26d8a556604053a9612623447203a691f406fbe6 # v4 + with: + composer-options: "--prefer-dist" + + - name: "Prepare environment" + run: | + cp resources/.env.example .env + sed -i 's/^DATA_API_MYSQL_HOST=.*/DATA_API_MYSQL_HOST=127.0.0.1/' .env + sed -i 's/^DATA_API_REDIS_HOST=.*/DATA_API_REDIS_HOST=127.0.0.1/' .env + # storage/logs is not tracked (only the cache dirs carry .gitignore + # placeholders), so a fresh checkout has nowhere to write the log. + mkdir -p storage/logs storage/cache/data storage/cache/metadata tests/_output - - name: Init Database + - name: "Migrate" + run: composer migrate + + # The api suite talks to this over the network. .htrouter.php is a router + # script: it serves anything that exists under public/ and hands the rest + # to public/index.php. + - name: "Start the application" run: | - mysql -uroot -h127.0.0.1 -psecret -e 'CREATE DATABASE IF NOT EXISTS `phalcon_api`;' + php -S 127.0.0.1:8080 .htrouter.php > storage/logs/server.log 2>&1 & + for i in $(seq 1 30); do + if curl -fsS -o /dev/null "${TALON_REST_URL}/sommething" 2>/dev/null; then break; fi + # A 404 is a healthy answer here - the route does not exist, but the + # app answered. Only a connection failure means it is not up yet. + if curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null | grep -q '^[0-9]'; then break; fi + sleep 1 + done + curl -sS -o /dev/null -w 'application answered with HTTP %{http_code}\n' "${TALON_REST_URL}/sommething" - - name: Validate composer.json and composer.lock - run: composer validate + - name: "Tests" + run: vendor/bin/talon run all - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: "Server log" + if: failure() + run: cat storage/logs/server.log || true - - name: Cache dependencies - uses: actions/cache@v2 + coverage: + name: "Coverage" + permissions: + contents: read + pull-requests: write + actions: read + runs-on: ubuntu-latest + needs: + - quality + services: + mysql: + image: mysql:8.0 + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: secret + MYSQL_DATABASE: phalcon_api + options: >- + --health-cmd "mysqladmin ping --silent" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:alpine + ports: + - "6379:6379" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ matrix.php }}-composer- + ref: ${{ github.event.pull_request.head.sha || github.sha }} + # SonarQube needs full history for blame/new-code detection; + # octocov uses it for the base-branch diff. + fetch-depth: 0 - - name: Install Composer dependencies - run: composer install --prefer-dist --no-progress --no-suggest + - name: "Setup PHP" + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 + with: + php-version: '8.3' + extensions: ${{ env.EXTENSIONS }} + coverage: pcov + tools: composer:v2 - - name: Run PHPCS - if: always() - run: vendor/bin/phpcs + # pecl's phalcon release metadata caps at PHP 8.4; PIE builds it from source. + - name: "Install Phalcon (v5) via PIE" + run: | + curl -fsSL https://github.com/php/pie/releases/latest/download/pie.phar -o pie.phar + sudo "$(which php)" pie.phar install --no-interaction "phalcon/cphalcon:${{ env.PHALCON_CONSTRAINT }}" + php -m | grep -i phalcon - - name: Env file - if: always() - run: cp -v ./storage/config/.env.ci ./.env + - name: "Install dependencies" + uses: ramsey/composer-install@26d8a556604053a9612623447203a691f406fbe6 # v4 + with: + composer-options: "--prefer-dist" - - name: Run migrations - if: always() + - name: "Prepare environment" run: | - vendor/bin/phinx migrate + cp resources/.env.example .env + sed -i 's/^DATA_API_MYSQL_HOST=.*/DATA_API_MYSQL_HOST=127.0.0.1/' .env + sed -i 's/^DATA_API_REDIS_HOST=.*/DATA_API_REDIS_HOST=127.0.0.1/' .env + mkdir -p storage/logs storage/cache/data storage/cache/metadata tests/_output - - name: Run tests - if: always() + - name: "Migrate" + run: composer migrate + + - name: "Start the application" run: | - sudo php -S 0.0.0.0 -t ./.htrouter.php & - vendor/bin/codecept build - vendor/bin/codecept run unit --coverage-xml=unit-coverage.xml - vendor/bin/codecept run integration --coverage-xml=integration-coverage.xml - vendor/bin/codecept run cli --coverage-xml=cli-coverage.xml -# vendor/bin/codecept run api --coverage-xml=api-coverage.xml - - - name: Upload to codecov - uses: codecov/codecov-action@v3 + php -S 127.0.0.1:8080 .htrouter.php > storage/logs/server.log 2>&1 & + for i in $(seq 1 30); do + if curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null | grep -q '^[0-9]'; then break; fi + sleep 1 + done + curl -sS -o /dev/null -w 'application answered with HTTP %{http_code}\n' "${TALON_REST_URL}/sommething" + + # Runs phpunit over resources/phpunit.xml.dist directly rather than + # through talon: `talon run all` starts one process per suite, and each + # would overwrite the others' coverage.xml. One process, all four suites, + # one merged report. + # + # NOTE: the api suite exercises the application in the php -S process + # above, which pcov does not instrument - so the controllers read as 0% + # covered here despite being tested. See resources/octocov.yml. + - name: "Tests with coverage" + run: composer test-coverage + + # SonarQube first (push only) so a failing octocov gate can't skip the upload. + - name: "SonarQube Scan" + if: github.event_name == 'push' + uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.organization=phalcon + -Dsonar.projectKey=phalcon_rest-api + -Dsonar.sources=src + -Dsonar.tests=tests + -Dsonar.exclusions=resources/** + -Dsonar.php.coverage.reportPaths=tests/_output/coverage.xml + + # octocov last: PR comment + coverage gate (strict on PRs via + # octocov.pr.yml, floor only on pushes via octocov.yml), stores the + # baseline report on the default branch, and writes the badge SVG. + - name: "octocov" + uses: k1LoW/octocov-action@b3b6ee60482a667950f87553abf1df63217235d9 # v1.5.1 + with: + config: ${{ github.event_name == 'pull_request' && 'resources/octocov.pr.yml' || 'resources/octocov.yml' }} + + - name: "Upload coverage badge" + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - directory: ./tests/_output/ - files: unit-coverage.xml,integration-coverage.xml,cli-coverage.xml - name: codecov-umbrella # optional - fail_ci_if_error: false - verbose: true # optional (default = false) + name: coverage-badge + path: tests/_output/coverage.svg + if-no-files-found: ignore diff --git a/composer.json b/composer.json index f9c1a082..8ba3414a 100644 --- a/composer.json +++ b/composer.json @@ -53,8 +53,8 @@ "cs-fixer": "php-cs-fixer fix --config=resources/php-cs-fixer.php --dry-run --diff", "cs-fixer-fix": "php-cs-fixer fix --config=resources/php-cs-fixer.php", "test": "talon run", - "test-coverage": "talon run unit -- --coverage-clover tests/_output/coverage.xml", - "test-coverage-html": "talon run unit -- --coverage-html tests/_output/coverage", + "test-coverage": "phpunit -c resources/phpunit.xml.dist --coverage-clover tests/_output/coverage.xml", + "test-coverage-html": "phpunit -c resources/phpunit.xml.dist --coverage-html tests/_output/coverage", "migrate": "phinx migrate -c resources/phinx.php", "seed": "phinx seed:run -c resources/phinx.php", "post-create-project-cmd": [ diff --git a/resources/octocov.pr.yml b/resources/octocov.pr.yml new file mode 100644 index 00000000..09850bca --- /dev/null +++ b/resources/octocov.pr.yml @@ -0,0 +1,32 @@ +# octocov configuration - https://github.com/k1LoW/octocov +# +# Pull-request config: identical to octocov.yml except the gate adds the +# no-regression check (diff >= 0), so a PR cannot lower coverage versus the +# base branch. The default branch deliberately omits this check (floor only) +# to avoid a coverage blip red-Xing master; keep the rest of this file in sync +# with octocov.yml. +coverage: + paths: + - tests/_output/coverage.xml + acceptable: "current >= 75% && diff >= 0" + badge: + path: tests/_output/coverage.svg +codeToTestRatio: + code: + - src/**/*.php + test: + - tests/**/*.php +testExecutionTime: + steps: + - Tests with coverage +comment: + if: is_pull_request +summary: + if: true +diff: + datastores: + - artifact://${GITHUB_REPOSITORY} +report: + if: is_default_branch + datastores: + - artifact://${GITHUB_REPOSITORY} diff --git a/resources/octocov.yml b/resources/octocov.yml new file mode 100644 index 00000000..8dd2570b --- /dev/null +++ b/resources/octocov.yml @@ -0,0 +1,39 @@ +# octocov configuration - https://github.com/k1LoW/octocov +# +# Default config: used on pushes (including the default branch). +# The gate here is a stable FLOOR only, so a coverage measurement blip on the +# default branch never red-Xs the build. Pull requests use octocov.pr.yml, +# which adds the no-regression (diff >= 0) ratchet on top of this floor. +# (octocov's `acceptable` cannot branch on is_pull_request, so the two gates +# live in separate files selected by the workflow.) +# +# The floor is 75% rather than vokuro's 97% because the api suite drives a real +# HTTP server in a separate process, which pcov cannot instrument: every +# controller reads as 0% covered even though 52 api tests exercise it. Raising +# this means either measuring the server process or moving those tests +# in-process - a deliberate open question, not an oversight. +coverage: + paths: + - tests/_output/coverage.xml + acceptable: "current >= 75%" + badge: + path: tests/_output/coverage.svg +codeToTestRatio: + code: + - src/**/*.php + test: + - tests/**/*.php +testExecutionTime: + steps: + - Tests with coverage +comment: + if: is_pull_request +summary: + if: true +diff: + datastores: + - artifact://${GITHUB_REPOSITORY} +report: + if: is_default_branch + datastores: + - artifact://${GITHUB_REPOSITORY} From 8c98e9e94262d056a0833c8a316c252453affb50 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:25:55 -0500 Subject: [PATCH 34/47] [#42] - wait for the application to answer before running the tests --- .github/workflows/main.yml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 40e54cac..e37f9944 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -158,14 +158,20 @@ jobs: - name: "Start the application" run: | php -S 127.0.0.1:8080 .htrouter.php > storage/logs/server.log 2>&1 & + # Any HTTP status means the application answered - /sommething is + # expected to 404. curl reports "000" when it could not connect at + # all, which is the only case worth waiting on. for i in $(seq 1 30); do - if curl -fsS -o /dev/null "${TALON_REST_URL}/sommething" 2>/dev/null; then break; fi - # A 404 is a healthy answer here - the route does not exist, but the - # app answered. Only a connection failure means it is not up yet. - if curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null | grep -q '^[0-9]'; then break; fi + code=$(curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null || true) + if [ "$code" != "000" ]; then + echo "application answered with HTTP ${code} after ${i}s" + exit 0 + fi sleep 1 done - curl -sS -o /dev/null -w 'application answered with HTTP %{http_code}\n' "${TALON_REST_URL}/sommething" + echo "the application did not answer within 30s" + cat storage/logs/server.log || true + exit 1 - name: "Tests" run: vendor/bin/talon run all @@ -247,10 +253,16 @@ jobs: run: | php -S 127.0.0.1:8080 .htrouter.php > storage/logs/server.log 2>&1 & for i in $(seq 1 30); do - if curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null | grep -q '^[0-9]'; then break; fi + code=$(curl -sS -o /dev/null -w '%{http_code}' "${TALON_REST_URL}/sommething" 2>/dev/null || true) + if [ "$code" != "000" ]; then + echo "application answered with HTTP ${code} after ${i}s" + exit 0 + fi sleep 1 done - curl -sS -o /dev/null -w 'application answered with HTTP %{http_code}\n' "${TALON_REST_URL}/sommething" + echo "the application did not answer within 30s" + cat storage/logs/server.log || true + exit 1 # Runs phpunit over resources/phpunit.xml.dist directly rather than # through talon: `talon run all` starts one process per suite, and each From 428b27b4b0266bd3c0944a28b6f8a86a35004ec5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:31:01 -0500 Subject: [PATCH 35/47] [#42] - let dotenv supply the application database host in ci --- .github/workflows/main.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e37f9944..b425b887 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,14 +12,19 @@ env: # Phalcon v5 C extension constraint (installed from source via PIE) PHALCON_CONSTRAINT: "^5.0" EXTENSIONS: "mbstring, intl, json, openssl, redis, pdo_mysql" - # The application reads DATA_API_*; Talon reads DATA_* for its own settings. - # Both are overridden here because tests/.env.test points at the compose - # service names, and Settings::fromEnv() checks getenv() before $_ENV - so a - # real environment variable wins over the file. - DATA_API_MYSQL_HOST: "127.0.0.1" - DATA_API_REDIS_HOST: "127.0.0.1" + # Talon's own settings. Settings::fromEnv() reads getenv() before $_ENV, so + # these beat tests/.env.test, which points at the compose service names. DATA_MYSQL_HOST: "127.0.0.1" DATA_REDIS_HOST: "127.0.0.1" + # + # The application's DATA_API_* are deliberately NOT set here. envValue() reads + # $_ENV only - never getenv() - and phpdotenv is immutable: if a real + # environment variable already exists it declines to write it into $_ENV. So + # exporting DATA_API_MYSQL_HOST here does not configure the application, it + # SILENCES the .env value and envValue() falls back to its default + # ('localhost' in DatabaseProvider, i.e. a unix socket, i.e. SQLSTATE[HY000] + # [2002]). The sed below sets them in .env instead, which is the only channel + # the application actually reads. # The api suite crosses a real HTTP boundary; this is the built-in server # started below. APP_URL stays http://localhost:8080 - it is what the app # echoes back in `links`, and the expectations must match the app. From cb1ea26643741cfe20e564d9d320880179a2d6b7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:36:14 -0500 Subject: [PATCH 36/47] [#42] - read the real environment before dotenv in envValue --- .github/workflows/main.yml | 16 +++++----------- src/Core/functions.php | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b425b887..c487f5ea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,19 +12,13 @@ env: # Phalcon v5 C extension constraint (installed from source via PIE) PHALCON_CONSTRAINT: "^5.0" EXTENSIONS: "mbstring, intl, json, openssl, redis, pdo_mysql" - # Talon's own settings. Settings::fromEnv() reads getenv() before $_ENV, so - # these beat tests/.env.test, which points at the compose service names. + # The application reads DATA_API_*; Talon reads DATA_* for its own settings. + # Both resolve getenv() before $_ENV, so these beat the files that point at + # the compose service names (.env for the app, tests/.env.test for Talon). + DATA_API_MYSQL_HOST: "127.0.0.1" + DATA_API_REDIS_HOST: "127.0.0.1" DATA_MYSQL_HOST: "127.0.0.1" DATA_REDIS_HOST: "127.0.0.1" - # - # The application's DATA_API_* are deliberately NOT set here. envValue() reads - # $_ENV only - never getenv() - and phpdotenv is immutable: if a real - # environment variable already exists it declines to write it into $_ENV. So - # exporting DATA_API_MYSQL_HOST here does not configure the application, it - # SILENCES the .env value and envValue() falls back to its default - # ('localhost' in DatabaseProvider, i.e. a unix socket, i.e. SQLSTATE[HY000] - # [2002]). The sed below sets them in .env instead, which is the only channel - # the application actually reads. # The api suite crosses a real HTTP boundary; this is the built-in server # started below. APP_URL stays http://localhost:8080 - it is what the app # echoes back in `links`, and the expectations must match the app. diff --git a/src/Core/functions.php b/src/Core/functions.php index 7aa04347..308607f5 100644 --- a/src/Core/functions.php +++ b/src/Core/functions.php @@ -32,7 +32,16 @@ function appPath(string $path = ''): string if (true !== function_exists('Phalcon\Api\Core\envValue')) { /** * Gets a variable from the environment, returns it properly formatted or the - * default if it does not exist + * default if it does not exist. + * + * The real environment is consulted first, then $_ENV, then the default. + * Reading $_ENV alone is not enough: $_ENV is only populated from the + * environment when php's `variables_order` contains `E` (the container sets + * EGPCS; a stock CI runner does not), and phpdotenv is immutable - when a + * variable already exists in the environment it declines to copy it into + * $_ENV. Between the two, an exported variable would otherwise be invisible + * here AND suppress the .env entry of the same name, leaving only the + * default. * * @param string $variable * @param mixed|null $default @@ -41,7 +50,12 @@ function appPath(string $path = ''): string */ function envValue(string $variable, mixed $default = null): mixed { - $value = $_ENV[$variable] ?? $default; + $value = getenv($variable); + + if (false === $value) { + $value = $_ENV[$variable] ?? $default; + } + $values = [ 'false' => false, 'true' => true, From e7cd1fcbecacafccc1c54089a39b1bb7d4a8b4a5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:42:36 -0500 Subject: [PATCH 37/47] [#42] - add phql and register composer before the phalcon loader --- composer.json | 1 + composer.lock | 82 +++++++++++++++++++++++++++++++++++++++++-- src/Core/autoload.php | 16 +++++---- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8ba3414a..194d4685 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "pds/skeleton": "^1.0", "phalcon/ide-stubs": "^5.16", "phalcon/phalcon": "v6.0.x-dev", + "phalcon/phql": "1.0.x-dev", "phalcon/talon": "^0.8.0", "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^10.5", diff --git a/composer.lock b/composer.lock index b40f9a5c..ddb55438 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "11d8b2f30c0ab9dab5f6f0f3ae4c1354", + "content-hash": "1af45bf0ac93076694409e074fe499ac", "packages": [ { "name": "cakephp/chronos", @@ -3074,6 +3074,83 @@ ], "time": "2026-07-15T23:27:22+00:00" }, + { + "name": "phalcon/phql", + "version": "1.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phalcon/phql.git", + "reference": "eb0500b5ca39245576ce6608cf6efcc2ca6df058" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phalcon/phql/zipball/eb0500b5ca39245576ce6608cf6efcc2ca6df058", + "reference": "eb0500b5ca39245576ce6608cf6efcc2ca6df058", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1 <9.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.95", + "pds/composer-script-names": "^1.0", + "pds/skeleton": "^1.0", + "phalcon/talon": "^0.6.0 || ^0.7.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^4.0" + }, + "suggest": { + "phalcon/phalcon": "The Phalcon framework this module integrates with" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "resources/files/parser.php" + ], + "psr-4": { + "Phalcon\\Phql\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalcon.io", + "homepage": "https://phalcon.io" + } + ], + "description": "Phalcon Query Language (PHQL)", + "homepage": "https://phalcon.io", + "keywords": [ + "framework", + "phalcon", + "php", + "phql", + "query", + "sql" + ], + "support": { + "issues": "https://github.com/phalcon/phql/issues", + "source": "https://github.com/phalcon/phql" + }, + "funding": [ + { + "url": "https://github.com/phalcon", + "type": "github" + }, + { + "url": "https://opencollective.com/phalcon", + "type": "open_collective" + } + ], + "time": "2026-07-12T21:11:02+00:00" + }, { "name": "phalcon/talon", "version": "v0.8.0", @@ -6664,7 +6741,8 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phalcon/phalcon": 20 + "phalcon/phalcon": 20, + "phalcon/phql": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/src/Core/autoload.php b/src/Core/autoload.php index 57f561ef..3758b02e 100644 --- a/src/Core/autoload.php +++ b/src/Core/autoload.php @@ -16,9 +16,18 @@ use function Phalcon\Api\Core\appPath; -// Register the autoloader +// appPath() and friends; needed before anything can be located require __DIR__ . '/functions.php'; +/** + * Composer first. On the v5 variant Phalcon is a C extension and its classes + * exist unconditionally, but on v6 the framework IS a Composer package - so + * `new Loader()` below cannot be reached without Composer's autoloader already + * registered. Loading it second worked only because the extension happened to + * be there. + */ +require appPath('/vendor/autoload.php'); + /** * The loader resolves by namespace prefix, so a single entry covers the whole * source tree: Phalcon\Api\Models\Users -> src/Models/Users.php, and @@ -33,10 +42,5 @@ $loader->setNamespaces($namespaces); $loader->register(); -/** - * Composer Autoloader - */ -require appPath('/vendor/autoload.php'); - // Load environment (Dotenv::createImmutable(appPath()))->load(); From 93ae556f6216b326b9cbddb67132920eabc1c875 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 22:53:19 -0500 Subject: [PATCH 38/47] [#42] - resolve octocov paths from the resources directory --- resources/octocov.pr.yml | 11 +++++++---- resources/octocov.yml | 15 +++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/resources/octocov.pr.yml b/resources/octocov.pr.yml index 09850bca..bf06948a 100644 --- a/resources/octocov.pr.yml +++ b/resources/octocov.pr.yml @@ -5,17 +5,20 @@ # base branch. The default branch deliberately omits this check (floor only) # to avoid a coverage blip red-Xing master; keep the rest of this file in sync # with octocov.yml. +# +# Paths below are relative to THIS file's directory (resources/), not the +# repo root - octocov resolves them that way, so everything is prefixed `../`. coverage: paths: - - tests/_output/coverage.xml + - ../tests/_output/coverage.xml acceptable: "current >= 75% && diff >= 0" badge: - path: tests/_output/coverage.svg + path: ../tests/_output/coverage.svg codeToTestRatio: code: - - src/**/*.php + - ../src/**/*.php test: - - tests/**/*.php + - ../tests/**/*.php testExecutionTime: steps: - Tests with coverage diff --git a/resources/octocov.yml b/resources/octocov.yml index 8dd2570b..3c6800d5 100644 --- a/resources/octocov.yml +++ b/resources/octocov.yml @@ -7,6 +7,13 @@ # (octocov's `acceptable` cannot branch on is_pull_request, so the two gates # live in separate files selected by the workflow.) # +# Paths below are relative to THIS file's directory (resources/), not the +# repo root - octocov resolves them that way, so everything is prefixed `../`. +# +# `../tests/_output/coverage.xml` is generated by `composer test-coverage` in +# the "Tests with coverage" workflow step - the same file the SonarQube scan +# reads, so the two dashboards never disagree with each other. +# # The floor is 75% rather than vokuro's 97% because the api suite drives a real # HTTP server in a separate process, which pcov cannot instrument: every # controller reads as 0% covered even though 52 api tests exercise it. Raising @@ -14,15 +21,15 @@ # in-process - a deliberate open question, not an oversight. coverage: paths: - - tests/_output/coverage.xml + - ../tests/_output/coverage.xml acceptable: "current >= 75%" badge: - path: tests/_output/coverage.svg + path: ../tests/_output/coverage.svg codeToTestRatio: code: - - src/**/*.php + - ../src/**/*.php test: - - tests/**/*.php + - ../tests/**/*.php testExecutionTime: steps: - Tests with coverage From 00cc62737cefee2988a8182c61307cd284ad3f83 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:02:02 -0500 Subject: [PATCH 39/47] [#42] - address sonarqube findings on the new code --- src/Api/Controllers/BaseController.php | 2 +- src/Core/config.php | 20 ++------------------ src/Http/Request.php | 2 +- src/Traits/FractalTrait.php | 2 +- src/Traits/TokenTrait.php | 4 ++-- 5 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index b0ddaa00..67818fc4 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -194,7 +194,7 @@ private function checkSort(): bool /** * Check the results. If we have something update the $orderBy */ - if (count($sortArray) > 0) { + if (true !== empty($sortArray)) { $this->orderBy = implode(',', $sortArray); } diff --git a/src/Core/config.php b/src/Core/config.php index 6d4947e3..226b9fcf 100644 --- a/src/Core/config.php +++ b/src/Core/config.php @@ -45,15 +45,7 @@ ], 'cache' => [ 'adapter' => 'redis', - 'options' => [ - 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), - 'port' => envValue('DATA_API_REDIS_PORT', 6379), - 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), - // Cast: env values arrive as strings, and AbstractAdapter::getTtl() - // is typed to return int. - 'lifetime' => (int) envValue('CACHE_LIFETIME', 86400), - 'prefix' => 'data-', - ], + 'options' => $redisOptions('data-'), ], 'metadata' => [ 'dev' => [ @@ -62,15 +54,7 @@ ], 'prod' => [ 'adapter' => Redis::class, - 'options' => [ - 'host' => envValue('DATA_API_REDIS_HOST', '127.0.0.1'), - 'port' => envValue('DATA_API_REDIS_PORT', 6379), - 'index' => envValue('DATA_API_REDIS_WEIGHT', 0), - // Cast: env values arrive as strings, and AbstractAdapter::getTtl() - // is typed to return int. - 'lifetime' => (int) envValue('CACHE_LIFETIME', 86400), - 'prefix' => 'metadata-', - ], + 'options' => $redisOptions('metadata-'), ], ], ]; diff --git a/src/Http/Request.php b/src/Http/Request.php index 3d7bed81..727e55a7 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -40,6 +40,6 @@ public function isEmptyBearerToken(): bool */ public function isLoginPage(): bool { - return ('/login' === $this->getURI()); + return '/login' === $this->getURI(); } } diff --git a/src/Traits/FractalTrait.php b/src/Traits/FractalTrait.php index d2932e97..cd6b4465 100644 --- a/src/Traits/FractalTrait.php +++ b/src/Traits/FractalTrait.php @@ -52,7 +52,7 @@ protected function format( /** * Process relationships */ - if (count($relationships) > 0) { + if (true !== empty($relationships)) { $manager->parseIncludes($relationships); } diff --git a/src/Traits/TokenTrait.php b/src/Traits/TokenTrait.php index 7c7ef87b..adce3cf7 100644 --- a/src/Traits/TokenTrait.php +++ b/src/Traits/TokenTrait.php @@ -56,7 +56,7 @@ protected function getTokenAudience(): string */ protected function getTokenTimeExpiration(): int { - return (time() + envValue('TOKEN_EXPIRATION', 86400)); + return time() + envValue('TOKEN_EXPIRATION', 86400); } /** @@ -76,6 +76,6 @@ protected function getTokenTimeIssuedAt(): int */ protected function getTokenTimeNotBefore(): int { - return (time() + envValue('TOKEN_NOT_BEFORE', 0)); + return time() + envValue('TOKEN_NOT_BEFORE', 0); } } From 9b1b089d66e45beda0f14926299a2707a577843b Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:13:08 -0500 Subject: [PATCH 40/47] [#42] - cover the transformers and the user token; sanitize on read only --- src/Mvc/Model/AbstractModel.php | 15 ++- .../Integration/Library/Models/UsersTest.php | 72 +++++++++++ .../Transformers/CompaniesTransformerTest.php | 120 ++++++++++++++++++ .../IndividualTypesTransformerTest.php | 92 ++++++++++++++ .../ProductTypesTransformerTest.php | 91 +++++++++++++ 5 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Library/Transformers/CompaniesTransformerTest.php create mode 100644 tests/Integration/Library/Transformers/IndividualTypesTransformerTest.php create mode 100644 tests/Integration/Library/Transformers/ProductTypesTransformerTest.php diff --git a/src/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php index 8124ef9f..eb272916 100644 --- a/src/Mvc/Model/AbstractModel.php +++ b/src/Mvc/Model/AbstractModel.php @@ -90,7 +90,11 @@ public function initialize(): void } /** - * Sets a field in the model sanitized + * Sets a field in the model. + * + * The value is stored as it was given. Sanitising is get()'s job - doing it + * here as well escaped everything twice, so a stored '&' came back out as + * '&amp;'. * * @param string $field The name of the field * @param mixed $value The value of the field @@ -106,7 +110,12 @@ public function set($field, $value): AbstractModel } /** - * Gets or sets a field and sanitizes it if necessary + * Gets or sets a field, sanitising on the way out only. + * + * Both directions used to sanitise, which escaped every value twice: set() + * turned '&' into '&' and get() then turned that into '&amp;'. The + * filter map is still consulted for both, so an unknown field is rejected + * either way. * * @param string $type * @param string $field @@ -133,7 +142,7 @@ private function getSetFields(string $type, string $field, $value = ''): mixed if ('get' === $type) { $return = $this->sanitize($this->$field, $filter); } else { - $this->$field = $this->sanitize($value, $filter); + $this->$field = $value; } return $return; diff --git a/tests/Integration/Library/Models/UsersTest.php b/tests/Integration/Library/Models/UsersTest.php index db0c051e..8adc785a 100644 --- a/tests/Integration/Library/Models/UsersTest.php +++ b/tests/Integration/Library/Models/UsersTest.php @@ -19,6 +19,7 @@ use Phalcon\Api\Traits\TokenTrait; use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Signer\Hmac; +use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Validator; use Phalcon\Filter\Filter; @@ -60,6 +61,77 @@ public function testCheckValidationData(): void $this->assertInstanceOf($class, $actual); } + /** + * Guards the field whitelist at the model, where it is declared. The api + * suite asserts JSON subsets, so it cannot prove absence on its own. + */ + public function testGetPublicFields(): void + { + $expected = [ + 'id', + 'status', + 'username', + 'issuer', + 'tokenId', + ]; + + $actual = (new Users())->getPublicFields(); + + $this->assertSame($expected, $actual); + $this->assertNotContains('password', $actual); + $this->assertNotContains('tokenPassword', $actual); + } + + /** + * getToken() builds and signs the token from the record, which is what the + * login endpoint hands back. + */ + public function testGetToken(): void + { + /** @var Users $user */ + $user = $this->haveRecordWithFields( + Users::class, + [ + 'status' => 1, + 'username' => Data::$testUsername, + 'password' => Data::$testPasswordHash, + 'issuer' => Data::$testIssuer, + 'tokenPassword' => Data::$strongPassphrase, + 'tokenId' => Data::$testTokenId, + ] + ); + + $token = $user->getToken(); + $parsed = $this->getToken($token); + $claims = $parsed->getClaims(); + + $this->assertSame(Data::$testIssuer, $claims->get(Enum::ISSUER)); + $this->assertSame(Data::$testTokenId, $claims->get(Enum::ID)); + $this->assertSame([$this->getTokenAudience()], $claims->get(Enum::AUDIENCE)); + + /** + * The signature must verify against the record's passphrase - that is + * the whole point of the token. + * + * Asserted against get('tokenPassword') rather than the raw constant: + * get() sanitises, so a passphrase containing '&' comes back as + * '&'. Signing and verification both read through get(), so the key + * in use is the sanitised form, not the string handed to set(). + */ + $passphrase = $user->get('tokenPassword'); + + $this->assertTrue($parsed->verify(new Hmac(), $passphrase)); + $this->assertFalse($parsed->verify(new Hmac(), 'not-the-passphrase')); + + /** + * Issued in the past, expiring in the future. + */ + $now = time(); + $this->assertLessThanOrEqual($now, $claims->get(Enum::ISSUED_AT)); + $this->assertLessThanOrEqual($now, $claims->get(Enum::NOT_BEFORE)); + $this->assertGreaterThan($now, $claims->get(Enum::EXPIRATION_TIME)); + } + public function testValidateFilters(): void { $model = new Users(); diff --git a/tests/Integration/Library/Transformers/CompaniesTransformerTest.php b/tests/Integration/Library/Transformers/CompaniesTransformerTest.php new file mode 100644 index 00000000..c0d21b4b --- /dev/null +++ b/tests/Integration/Library/Transformers/CompaniesTransformerTest.php @@ -0,0 +1,120 @@ + + * + * 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\Integration\Library\Transformers; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\FractalTrait; +use Phalcon\Api\Transformers\CompaniesTransformer; + +/** + * Goes through FractalTrait::format() rather than building the Manager by hand, + * because that is the path the controllers actually take. + */ +final class CompaniesTransformerTest extends AbstractIntegrationTestCase +{ + use FractalTrait; + + /** + * @throws ModelException + */ + public function testTransformer(): void + { + $company = $this->addCompanyRecord('com-a-'); + $indType = $this->addIndividualTypeRecord('type-a-'); + $individual = $this->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + $prdType = $this->addProductTypeRecord('prt-a-'); + $product = $this->addProductRecord('prd-a-', $prdType->get('id')); + + $this->addCompanyXProduct($company->get('id'), $product->get('id')); + + $results = $this->format( + 'collection', + [$company], + CompaniesTransformer::class, + Relationships::COMPANIES, + [Relationships::PRODUCTS, Relationships::INDIVIDUALS] + ); + + /** + * companiesResponse() carries the relationship links; asking for the + * includes is what adds the `data` identifiers alongside them. + */ + $element = Data::companiesResponse($company); + + $element['relationships'][Relationships::PRODUCTS]['data'] = [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => (string) $product->get('id'), + ], + ]; + $element['relationships'][Relationships::INDIVIDUALS]['data'] = [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => (string) $individual->get('id'), + ], + ]; + + /** + * Each included resource advertises its own relationships, because its + * transformer declares availableIncludes of its own. + */ + $includedProduct = Data::productResponse($product) + [ + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + Relationships::PRODUCT_TYPES => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + ], + ]; + + $includedIndividual = Data::individualResponse($individual) + [ + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + Relationships::INDIVIDUAL_TYPES => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + ], + ]; + + $expected = [ + 'data' => [$element], + 'included' => [$includedProduct, $includedIndividual], + ]; + + $this->assertSame($expected, $results); + } +} diff --git a/tests/Integration/Library/Transformers/IndividualTypesTransformerTest.php b/tests/Integration/Library/Transformers/IndividualTypesTransformerTest.php new file mode 100644 index 00000000..93b95e05 --- /dev/null +++ b/tests/Integration/Library/Transformers/IndividualTypesTransformerTest.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\Integration\Library\Transformers; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\FractalTrait; +use Phalcon\Api\Transformers\IndividualTypesTransformer; + +/** + * Goes through FractalTrait::format() rather than building the Manager by hand, + * because that is the path the controllers actually take. + */ +final class IndividualTypesTransformerTest extends AbstractIntegrationTestCase +{ + use FractalTrait; + + /** + * @throws ModelException + */ + public function testTransformer(): void + { + $company = $this->addCompanyRecord('com-a-'); + $indType = $this->addIndividualTypeRecord('type-a-'); + $individual = $this->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + + $results = $this->format( + 'collection', + [$indType], + IndividualTypesTransformer::class, + Relationships::INDIVIDUAL_TYPES, + [Relationships::INDIVIDUALS] + ); + + $element = Data::individualTypeResponse($indType) + [ + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUAL_TYPES, + $indType->get('id'), + Relationships::INDIVIDUALS + ), + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => (string) $individual->get('id'), + ], + ], + ], + ], + ]; + + $includedIndividual = Data::individualResponse($individual) + [ + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + Relationships::INDIVIDUAL_TYPES => [ + 'links' => Data::relationshipLinks( + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + ], + ]; + + $expected = [ + 'data' => [$element], + 'included' => [$includedIndividual], + ]; + + $this->assertSame($expected, $results); + } +} diff --git a/tests/Integration/Library/Transformers/ProductTypesTransformerTest.php b/tests/Integration/Library/Transformers/ProductTypesTransformerTest.php new file mode 100644 index 00000000..8a942461 --- /dev/null +++ b/tests/Integration/Library/Transformers/ProductTypesTransformerTest.php @@ -0,0 +1,91 @@ + + * + * 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\Integration\Library\Transformers; + +use Phalcon\Api\Constants\Relationships; +use Phalcon\Api\Exception\ModelException; +use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Api\Tests\Support\Data; +use Phalcon\Api\Traits\FractalTrait; +use Phalcon\Api\Transformers\ProductTypesTransformer; + +/** + * Goes through FractalTrait::format() rather than building the Manager by hand, + * because that is the path the controllers actually take. + */ +final class ProductTypesTransformerTest extends AbstractIntegrationTestCase +{ + use FractalTrait; + + /** + * @throws ModelException + */ + public function testTransformer(): void + { + $prdType = $this->addProductTypeRecord('prt-a-'); + $product = $this->addProductRecord('prd-a-', $prdType->get('id')); + + $results = $this->format( + 'collection', + [$prdType], + ProductTypesTransformer::class, + Relationships::PRODUCT_TYPES, + [Relationships::PRODUCTS] + ); + + $element = Data::productTypeResponse($prdType) + [ + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCT_TYPES, + $prdType->get('id'), + Relationships::PRODUCTS + ), + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => (string) $product->get('id'), + ], + ], + ], + ], + ]; + + $includedProduct = Data::productResponse($product) + [ + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + Relationships::PRODUCT_TYPES => [ + 'links' => Data::relationshipLinks( + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + ], + ]; + + $expected = [ + 'data' => [$element], + 'included' => [$includedProduct], + ]; + + $this->assertSame($expected, $results); + } +} From 0a47c65906e728efdd56b7e04665d3bed81271f8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:25:46 -0500 Subject: [PATCH 41/47] [#42] - type the transformers, controllers and response for static analysis --- resources/phpstan.neon | 4 +- src/Api/Controllers/BaseController.php | 25 +++-- .../Controllers/Companies/AddController.php | 97 +++++++++++-------- .../Controllers/Companies/GetController.php | 4 +- .../IndividualTypes/GetController.php | 2 +- .../Controllers/Individuals/GetController.php | 2 +- .../ProductTypes/GetController.php | 2 +- .../Controllers/Products/GetController.php | 2 +- src/Bootstrap/AbstractBootstrap.php | 4 +- src/Http/Response.php | 15 ++- src/Traits/FractalTrait.php | 28 +++--- src/Transformers/BaseTransformer.php | 54 ++++++++--- src/Transformers/CompaniesTransformer.php | 10 +- .../IndividualTypesTransformer.php | 5 +- src/Transformers/IndividualsTransformer.php | 8 +- src/Transformers/ProductTypesTransformer.php | 4 +- src/Transformers/ProductsTransformer.php | 7 +- 17 files changed, 159 insertions(+), 114 deletions(-) diff --git a/resources/phpstan.neon b/resources/phpstan.neon index 36b412cc..159dee85 100644 --- a/resources/phpstan.neon +++ b/resources/phpstan.neon @@ -2,7 +2,9 @@ parameters: paths: - ../src - ../public/index.php - level: max + # Climbing towards max. Raise one level at a time, clearing the errors each + # step introduces before moving up. + level: 7 reportUnmatchedIgnoredErrors: false tmpDir: ../tests/_output # The application registers its own namespaces via library/Core/autoload.php, diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index 67818fc4..a981e49c 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -41,7 +41,7 @@ class BaseController extends Controller use FractalTrait; use ResponseTrait; - /** @var array */ + /** @var array */ protected array $includes = []; /** @var string */ @@ -56,10 +56,10 @@ class BaseController extends Controller /** @var string */ protected string $resource = ''; - /** @var array */ + /** @var array */ protected array $sortFields = []; - /** @var string */ + /** @var class-string|string */ protected string $transformer = ''; /** @@ -86,7 +86,12 @@ public function callAction(mixed $id = 0): void $this->orderBy ); - if (count($parameters) > 0 && 0 === count($results)) { + /** + * A record was asked for by id and nothing came back. `getFirst()` + * rather than `count()`: ResultsetInterface declares the former and + * not the latter, however countable the concrete Resultset is. + */ + if (true !== empty($parameters) && null === $results->getFirst()) { $this->sendError($this->response::NOT_FOUND); } else { $data = $this->format( @@ -106,7 +111,7 @@ public function callAction(mixed $id = 0): void } /** - * @return array + * @return array> */ private function checkFields(): array { @@ -128,7 +133,7 @@ private function checkFields(): array * * @param mixed $recordId * - * @return array + * @return array * @throws Exception */ private function checkIdParameter(mixed $recordId = 0): array @@ -148,7 +153,7 @@ private function checkIdParameter(mixed $recordId = 0): array /** * Processes the includes requested; Unknown includes are ignored * - * @return array + * @return array */ private function checkIncludes(): array { @@ -207,7 +212,7 @@ private function checkSort(): bool * * @param string $field * - * @return array + * @return array{0: string, 1: string} The field name and its direction */ private function getFieldAndDirection(string $field): array { @@ -229,8 +234,10 @@ private function getFieldAndDirection(string $field): array * Sets the response with an error code * * @param int $code + * + * @return void */ - private function sendError(int $code) + private function sendError(int $code): void { $this ->response diff --git a/src/Api/Controllers/Companies/AddController.php b/src/Api/Controllers/Companies/AddController.php index 69231577..0d49ff3c 100644 --- a/src/Api/Controllers/Companies/AddController.php +++ b/src/Api/Controllers/Companies/AddController.php @@ -37,62 +37,77 @@ class AddController extends Controller /** * Adds a record in the database * + * @return void * @throws ModelException */ - public function callAction() + public function callAction(): void { $validator = new CompaniesValidator(); $messages = $validator->validate($this->request->getPost()); /** - * If no messages are returned, go ahead with the query + * validate() answers false when a beforeValidation handler cancels the + * run, which is not the same as "nothing was wrong" - so it must not + * fall through to the insert. CompaniesValidator installs no such + * handler today, but the signature allows it and the old + * `count($messages)` would have been fatal on false rather than caught. */ - if (0 === count($messages)) { - $name = $this->request->getPost('name', Filter::FILTER_STRING); - $address = $this->request->getPost('address', Filter::FILTER_STRING, ''); - $city = $this->request->getPost('city', Filter::FILTER_STRING, ''); - $phone = $this->request->getPost('phone', Filter::FILTER_STRING, ''); - - $company = new Companies(); - $result = $company - ->set('name', $name) - ->set('address', $address) - ->set('city', $city) - ->set('phone', $phone) - ->save() + if (false === $messages) { + $this + ->response + ->setPayloadError('The company could not be validated') + ; + + return; + } + + if (0 !== $messages->count()) { + $this + ->response + ->setPayloadErrors($messages) ; - if (false !== $result) { - $data = $this->format( - 'item', - $company, - BaseTransformer::class, - 'companies' - ); - - $this - ->response - ->setHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))) - ->setJsonContent($data) - ->setStatusCode($this->response::CREATED) - ; - } else { - /** - * Errors happened store them - */ - $this - ->response - ->setPayloadErrors($company->getMessages()) - ; - } - } else { + return; + } + + $name = $this->request->getPost('name', Filter::FILTER_STRING); + $address = $this->request->getPost('address', Filter::FILTER_STRING, ''); + $city = $this->request->getPost('city', Filter::FILTER_STRING, ''); + $phone = $this->request->getPost('phone', Filter::FILTER_STRING, ''); + + $company = new Companies(); + $result = $company + ->set('name', $name) + ->set('address', $address) + ->set('city', $city) + ->set('phone', $phone) + ->save() + ; + + if (false === $result) { /** - * Set the errors in the payload + * Errors happened store them */ $this ->response - ->setPayloadErrors($messages) + ->setPayloadErrors($company->getMessages()) ; + + return; } + + $data = $this->format( + 'item', + $company, + BaseTransformer::class, + 'companies' + ); + + $this + ->response + ->setHeader('Location', appUrl(Relationships::COMPANIES, $company->get('id'))) + ->setJsonContent($data) + ->setStatusCode($this->response::CREATED) + ; } } diff --git a/src/Api/Controllers/Companies/GetController.php b/src/Api/Controllers/Companies/GetController.php index 8027a72b..06327a04 100644 --- a/src/Api/Controllers/Companies/GetController.php +++ b/src/Api/Controllers/Companies/GetController.php @@ -23,7 +23,7 @@ */ class GetController extends BaseController { - /** @var array */ + /** @var array */ protected array $includes = [ Relationships::INDIVIDUALS, Relationships::PRODUCTS, @@ -34,7 +34,7 @@ class GetController extends BaseController /** @var string */ protected string $resource = Relationships::COMPANIES; - /** @var array */ + /** @var array */ protected array $sortFields = [ 'id' => true, 'name' => true, diff --git a/src/Api/Controllers/IndividualTypes/GetController.php b/src/Api/Controllers/IndividualTypes/GetController.php index fb032616..e9b1ea2b 100644 --- a/src/Api/Controllers/IndividualTypes/GetController.php +++ b/src/Api/Controllers/IndividualTypes/GetController.php @@ -23,7 +23,7 @@ */ class GetController extends BaseController { - /** @var array */ + /** @var array */ protected array $includes = [ Relationships::INDIVIDUALS, ]; diff --git a/src/Api/Controllers/Individuals/GetController.php b/src/Api/Controllers/Individuals/GetController.php index 6efa4a68..c0dcd100 100644 --- a/src/Api/Controllers/Individuals/GetController.php +++ b/src/Api/Controllers/Individuals/GetController.php @@ -23,7 +23,7 @@ */ class GetController extends BaseController { - /** @var array */ + /** @var array */ protected array $includes = [ Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES, diff --git a/src/Api/Controllers/ProductTypes/GetController.php b/src/Api/Controllers/ProductTypes/GetController.php index efa1c522..60152d25 100644 --- a/src/Api/Controllers/ProductTypes/GetController.php +++ b/src/Api/Controllers/ProductTypes/GetController.php @@ -23,7 +23,7 @@ */ class GetController extends BaseController { - /** @var array */ + /** @var array */ protected array $includes = [ Relationships::PRODUCTS, ]; diff --git a/src/Api/Controllers/Products/GetController.php b/src/Api/Controllers/Products/GetController.php index e2dd103a..65b7338f 100644 --- a/src/Api/Controllers/Products/GetController.php +++ b/src/Api/Controllers/Products/GetController.php @@ -23,7 +23,7 @@ */ class GetController extends BaseController { - /** @var array */ + /** @var array */ protected array $includes = [ Relationships::COMPANIES, Relationships::PRODUCT_TYPES, diff --git a/src/Bootstrap/AbstractBootstrap.php b/src/Bootstrap/AbstractBootstrap.php index e37a2c7e..3ee5227d 100644 --- a/src/Bootstrap/AbstractBootstrap.php +++ b/src/Bootstrap/AbstractBootstrap.php @@ -32,10 +32,10 @@ abstract class AbstractBootstrap /** @var FactoryDefault|PhCli|null */ protected FactoryDefault|PhCli|null $container = null; - /** @var array */ + /** @var array */ protected array $options = []; - /** @var array */ + /** @var array> */ protected array $providers = []; /** diff --git a/src/Http/Response.php b/src/Http/Response.php index 78bbc2a3..3374fb13 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -15,7 +15,7 @@ use Phalcon\Http\Response as PhResponse; use Phalcon\Http\ResponseInterface; -use Phalcon\Messages\Messages; +use Phalcon\Messages\MessageInterface; use function date; use function is_array; @@ -40,6 +40,7 @@ class Response extends PhResponse public const TEMPORARY_REDIRECT = 307; public const UNAUTHORIZED = 401; + /** @var array */ private array $codes = [ 200 => 'OK', 301 => 'Moved Permanently', @@ -83,7 +84,7 @@ public function send(): ResponseInterface $hash = sha1($timestamp . $content); $eTag = sha1($content); - /** @var array $content */ + /** @var array $content */ $content = json_decode($this->getContent(), true); $jsonapi = [ 'jsonapi' => [ @@ -125,9 +126,13 @@ public function setPayloadError(string $detail = ''): Response } /** - * Traverses the errors collection and sets the errors in the payload + * Traverses the errors collection and sets the errors in the payload. * - * @param Messages $errors + * Documented as Messages for years while every caller passed something + * else: Model::getMessages() answers a plain array. All this needs is + * something iterable whose items carry getMessage(). + * + * @param iterable $errors * * @return Response */ @@ -146,7 +151,7 @@ public function setPayloadErrors($errors): Response /** * Sets the payload code as Success * - * @param null|string|array $content The content + * @param array|string|null $content The content * * @return Response */ diff --git a/src/Traits/FractalTrait.php b/src/Traits/FractalTrait.php index cd6b4465..fc68ecea 100644 --- a/src/Traits/FractalTrait.php +++ b/src/Traits/FractalTrait.php @@ -14,7 +14,9 @@ namespace Phalcon\Api\Traits; use League\Fractal\Manager; +use League\Fractal\Resource\ResourceInterface; use League\Fractal\Serializer\JsonApiSerializer; +use Phalcon\Api\Transformers\BaseTransformer; use function Phalcon\Api\Core\envValue; use function sprintf; @@ -28,14 +30,14 @@ trait FractalTrait /** * Format results based on a transformer * - * @param string $method - * @param mixed $results - * @param string $transformer - * @param string $resource - * @param array $relationships - * @param array $fields + * @param string $method 'collection' or 'item' + * @param mixed $results + * @param class-string $transformer + * @param string $resource + * @param array $relationships + * @param array> $fields * - * @return array + * @return array */ protected function format( string $method, @@ -56,12 +58,12 @@ protected function format( $manager->parseIncludes($relationships); } - $class = sprintf('League\Fractal\Resource\%s', ucfirst($method)); - $resource = new $class($results, new $transformer($fields, $resource), $resource); - $results = $manager->createData($resource) - ->toArray() - ; + /** @var class-string $class */ + $class = sprintf('League\Fractal\Resource\%s', ucfirst($method)); - return $results; + return $manager + ->createData(new $class($results, new $transformer($fields, $resource), $resource)) + ->toArray() + ; } } diff --git a/src/Transformers/BaseTransformer.php b/src/Transformers/BaseTransformer.php index fd97aa38..8f00cdd3 100644 --- a/src/Transformers/BaseTransformer.php +++ b/src/Transformers/BaseTransformer.php @@ -26,7 +26,7 @@ */ class BaseTransformer extends TransformerAbstract { - /** @var array */ + /** @var array> */ private array $fields = []; /** @var string */ @@ -35,8 +35,8 @@ class BaseTransformer extends TransformerAbstract /** * BaseTransformer constructor. * - * @param array $fields - * @param string $resource + * @param array> $fields + * @param string $resource */ public function __construct(array $fields = [], string $resource = '') { @@ -47,7 +47,7 @@ public function __construct(array $fields = [], string $resource = '') /** * @param AbstractModel $model * - * @return array + * @return array * @throws ModelException */ public function transform(AbstractModel $model): array @@ -70,24 +70,46 @@ public function transform(AbstractModel $model): array } /** - * @param string $method - * @param AbstractModel $model - * @param string $transformer - * @param string $resource + * A related resource holding many records. + * + * Split from getRelatedItem() rather than switched on a method-name string: + * one method returning Collection|Item cannot tell a caller which it got, + * so every includeXxx() declaring `: Collection` was lying by half. * - * @return Collection|Item + * @param AbstractModel $model + * @param class-string $transformer + * @param string $resource + * + * @return Collection */ - protected function getRelatedData( - string $method, + protected function getRelatedCollection( AbstractModel $model, string $transformer, string $resource - ): Collection|Item { - /** @var AbstractModel $data */ - $data = $model->getRelated($resource); + ): Collection { + return $this->collection( + $model->getRelated($resource), + new $transformer($this->fields, $resource), + $resource + ); + } - return $this->$method( - $data, + /** + * A related resource holding a single record. + * + * @param AbstractModel $model + * @param class-string $transformer + * @param string $resource + * + * @return Item + */ + protected function getRelatedItem( + AbstractModel $model, + string $transformer, + string $resource + ): Item { + return $this->item( + $model->getRelated($resource), new $transformer($this->fields, $resource), $resource ); diff --git a/src/Transformers/CompaniesTransformer.php b/src/Transformers/CompaniesTransformer.php index a44679ee..cdad7c79 100644 --- a/src/Transformers/CompaniesTransformer.php +++ b/src/Transformers/CompaniesTransformer.php @@ -22,9 +22,7 @@ */ class CompaniesTransformer extends BaseTransformer { - /** - * @var array - */ + /** @var array */ protected array $availableIncludes = [ Relationships::PRODUCTS, Relationships::INDIVIDUALS, @@ -37,8 +35,7 @@ class CompaniesTransformer extends BaseTransformer */ public function includeIndividuals(Companies $company): Collection { - return $this->getRelatedData( - 'collection', + return $this->getRelatedCollection( $company, IndividualsTransformer::class, Relationships::INDIVIDUALS @@ -52,8 +49,7 @@ public function includeIndividuals(Companies $company): Collection */ public function includeProducts(Companies $company): Collection { - return $this->getRelatedData( - 'collection', + return $this->getRelatedCollection( $company, ProductsTransformer::class, Relationships::PRODUCTS diff --git a/src/Transformers/IndividualTypesTransformer.php b/src/Transformers/IndividualTypesTransformer.php index e365ec28..2c9126e1 100644 --- a/src/Transformers/IndividualTypesTransformer.php +++ b/src/Transformers/IndividualTypesTransformer.php @@ -22,7 +22,7 @@ */ class IndividualTypesTransformer extends BaseTransformer { - /** @var array */ + /** @var array */ protected array $availableIncludes = [ Relationships::INDIVIDUALS, ]; @@ -34,8 +34,7 @@ class IndividualTypesTransformer extends BaseTransformer */ public function includeIndividuals(IndividualTypes $type): Collection { - return $this->getRelatedData( - 'collection', + return $this->getRelatedCollection( $type, IndividualsTransformer::class, Relationships::INDIVIDUALS diff --git a/src/Transformers/IndividualsTransformer.php b/src/Transformers/IndividualsTransformer.php index d32949a7..3e9b0d2d 100644 --- a/src/Transformers/IndividualsTransformer.php +++ b/src/Transformers/IndividualsTransformer.php @@ -22,7 +22,7 @@ */ class IndividualsTransformer extends BaseTransformer { - /** @var array */ + /** @var array */ protected array $availableIncludes = [ Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES, @@ -37,8 +37,7 @@ class IndividualsTransformer extends BaseTransformer */ public function includeCompanies(Individuals $individual): Item { - return $this->getRelatedData( - 'item', + return $this->getRelatedItem( $individual, CompaniesTransformer::class, Relationships::COMPANIES @@ -54,8 +53,7 @@ public function includeCompanies(Individuals $individual): Item */ public function includeIndividualTypes(Individuals $individual): Item { - return $this->getRelatedData( - 'item', + return $this->getRelatedItem( $individual, IndividualTypesTransformer::class, Relationships::INDIVIDUAL_TYPES diff --git a/src/Transformers/ProductTypesTransformer.php b/src/Transformers/ProductTypesTransformer.php index ae216c20..5198d80a 100644 --- a/src/Transformers/ProductTypesTransformer.php +++ b/src/Transformers/ProductTypesTransformer.php @@ -22,6 +22,7 @@ */ class ProductTypesTransformer extends BaseTransformer { + /** @var array */ protected array $availableIncludes = [ Relationships::PRODUCTS, ]; @@ -33,8 +34,7 @@ class ProductTypesTransformer extends BaseTransformer */ public function includeProducts(ProductTypes $type): Collection { - return $this->getRelatedData( - 'collection', + return $this->getRelatedCollection( $type, ProductsTransformer::class, Relationships::PRODUCTS diff --git a/src/Transformers/ProductsTransformer.php b/src/Transformers/ProductsTransformer.php index c9343155..890bae1b 100644 --- a/src/Transformers/ProductsTransformer.php +++ b/src/Transformers/ProductsTransformer.php @@ -23,6 +23,7 @@ */ class ProductsTransformer extends BaseTransformer { + /** @var array */ protected array $availableIncludes = [ Relationships::COMPANIES, Relationships::PRODUCT_TYPES, @@ -37,8 +38,7 @@ class ProductsTransformer extends BaseTransformer */ public function includeCompanies(Products $product): Collection { - return $this->getRelatedData( - 'collection', + return $this->getRelatedCollection( $product, CompaniesTransformer::class, Relationships::COMPANIES @@ -54,8 +54,7 @@ public function includeCompanies(Products $product): Collection */ public function includeProductTypes(Products $product): Item { - return $this->getRelatedData( - 'item', + return $this->getRelatedItem( $product, ProductTypesTransformer::class, Relationships::PRODUCT_TYPES From f7449db874ecf92a1fdd730296fad4687d9f4e5f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:34:10 -0500 Subject: [PATCH 42/47] [#42] - clear static analysis at level 7 and gate it in ci --- .github/workflows/main.yml | 7 ++----- resources/phpstan.neon | 9 ++++++++ src/Api/Controllers/BaseController.php | 10 +++++++-- .../Controllers/Companies/GetController.php | 3 ++- .../IndividualTypes/GetController.php | 3 ++- .../Controllers/Individuals/GetController.php | 3 ++- .../ProductTypes/GetController.php | 3 ++- .../Controllers/Products/GetController.php | 3 ++- src/Api/Controllers/Users/GetController.php | 2 +- src/Bootstrap/Cli.php | 2 -- src/Cli/Tasks/ClearcacheTask.php | 21 ++++++++++++++----- src/Cli/Tasks/MainTask.php | 4 +++- src/ErrorHandler.php | 11 ++++++++-- src/Models/Users.php | 2 +- src/Mvc/Model/AbstractModel.php | 4 ++-- src/Providers/ErrorHandlerProvider.php | 2 +- src/Providers/ModelsMetadataProvider.php | 3 ++- src/Providers/RouterProvider.php | 12 +++++------ src/Repositories/UsersRepository.php | 14 ++++++++++--- 19 files changed, 81 insertions(+), 37 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c487f5ea..e366ac98 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,11 +65,8 @@ jobs: - name: "PHP CS Fixer (dry-run)" run: composer cs-fixer - # PHPStan is deliberately not gated yet: `composer analyze` reports 154 - # errors at level max. Uncomment once those are cleared - that work is - # tracked separately and is the next task after this workflow lands. - # - name: "PHPStan" - # run: composer analyze + - name: "PHPStan" + run: composer analyze tests: name: "Tests (PHP ${{ matrix.php }}, Phalcon ${{ matrix.variant }})" diff --git a/resources/phpstan.neon b/resources/phpstan.neon index 159dee85..bbb82ff9 100644 --- a/resources/phpstan.neon +++ b/resources/phpstan.neon @@ -12,3 +12,12 @@ parameters: # from Composer only; the analysed paths above cover the application's classes. bootstrapFiles: - ../vendor/autoload.php + ignoreErrors: + # Phalcon\Mvc\Model is generic (@template T) in the v6 source; the v5 C + # extension's model is not, so AbstractModel cannot parametrize it and + # still run on both variants. + - '#extends generic class Phalcon\\Mvc\\Model but does not specify its types#' + # Loader::setNamespaces() is typed array>, + # but it documents and accepts a plain directory string per namespace - + # which is what src/Core/autoload.php passes, on both variants. + - '#Parameter \#1 \$namespaces of method Phalcon\\Autoload\\Loader::setNamespaces\(\) expects#' diff --git a/src/Api/Controllers/BaseController.php b/src/Api/Controllers/BaseController.php index a981e49c..906abdfe 100644 --- a/src/Api/Controllers/BaseController.php +++ b/src/Api/Controllers/BaseController.php @@ -17,6 +17,7 @@ use Phalcon\Api\Services\QueryService; use Phalcon\Api\Traits\FractalTrait; use Phalcon\Api\Traits\ResponseTrait; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Filter\Exception; use Phalcon\Filter\Filter; use Phalcon\Mvc\Controller; @@ -59,8 +60,13 @@ class BaseController extends Controller /** @var array */ protected array $sortFields = []; - /** @var class-string|string */ - protected string $transformer = ''; + /** + * Defaults to the base transformer rather than an empty string: every + * concrete controller names its own, and '' was never a usable value. + * + * @var class-string + */ + protected string $transformer = BaseTransformer::class; /** * Get the company/companies diff --git a/src/Api/Controllers/Companies/GetController.php b/src/Api/Controllers/Companies/GetController.php index 06327a04..c313e6e8 100644 --- a/src/Api/Controllers/Companies/GetController.php +++ b/src/Api/Controllers/Companies/GetController.php @@ -16,6 +16,7 @@ use Phalcon\Api\Api\Controllers\BaseController; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\Companies; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Api\Transformers\CompaniesTransformer; /** @@ -43,6 +44,6 @@ class GetController extends BaseController 'phone' => true, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = CompaniesTransformer::class; } diff --git a/src/Api/Controllers/IndividualTypes/GetController.php b/src/Api/Controllers/IndividualTypes/GetController.php index e9b1ea2b..79f5450b 100644 --- a/src/Api/Controllers/IndividualTypes/GetController.php +++ b/src/Api/Controllers/IndividualTypes/GetController.php @@ -16,6 +16,7 @@ use Phalcon\Api\Api\Controllers\BaseController; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\IndividualTypes; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Api\Transformers\IndividualTypesTransformer; /** @@ -40,6 +41,6 @@ class GetController extends BaseController 'description' => false, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = IndividualTypesTransformer::class; } diff --git a/src/Api/Controllers/Individuals/GetController.php b/src/Api/Controllers/Individuals/GetController.php index c0dcd100..3af3354f 100644 --- a/src/Api/Controllers/Individuals/GetController.php +++ b/src/Api/Controllers/Individuals/GetController.php @@ -16,6 +16,7 @@ use Phalcon\Api\Api\Controllers\BaseController; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\Individuals; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Api\Transformers\IndividualsTransformer; /** @@ -49,6 +50,6 @@ class GetController extends BaseController 'suffix' => true, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = IndividualsTransformer::class; } diff --git a/src/Api/Controllers/ProductTypes/GetController.php b/src/Api/Controllers/ProductTypes/GetController.php index 60152d25..854c1ba8 100644 --- a/src/Api/Controllers/ProductTypes/GetController.php +++ b/src/Api/Controllers/ProductTypes/GetController.php @@ -16,6 +16,7 @@ use Phalcon\Api\Api\Controllers\BaseController; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\ProductTypes; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Api\Transformers\ProductTypesTransformer; /** @@ -40,6 +41,6 @@ class GetController extends BaseController 'description' => false, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = ProductTypesTransformer::class; } diff --git a/src/Api/Controllers/Products/GetController.php b/src/Api/Controllers/Products/GetController.php index 65b7338f..0e41ab4f 100644 --- a/src/Api/Controllers/Products/GetController.php +++ b/src/Api/Controllers/Products/GetController.php @@ -16,6 +16,7 @@ use Phalcon\Api\Api\Controllers\BaseController; use Phalcon\Api\Constants\Relationships; use Phalcon\Api\Models\Products; +use Phalcon\Api\Transformers\BaseTransformer; use Phalcon\Api\Transformers\ProductsTransformer; /** @@ -44,6 +45,6 @@ class GetController extends BaseController 'price' => true, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = ProductsTransformer::class; } diff --git a/src/Api/Controllers/Users/GetController.php b/src/Api/Controllers/Users/GetController.php index e48c7501..36ecdcfb 100644 --- a/src/Api/Controllers/Users/GetController.php +++ b/src/Api/Controllers/Users/GetController.php @@ -43,6 +43,6 @@ class GetController extends BaseController 'tokenId' => false, ]; - /** @var string */ + /** @var class-string */ protected string $transformer = BaseTransformer::class; } diff --git a/src/Bootstrap/Cli.php b/src/Bootstrap/Cli.php index de14272f..83a71a03 100644 --- a/src/Bootstrap/Cli.php +++ b/src/Bootstrap/Cli.php @@ -33,8 +33,6 @@ public function __construct() $this->processArguments(); parent::__construct(); - - return $this; } /** diff --git a/src/Cli/Tasks/ClearcacheTask.php b/src/Cli/Tasks/ClearcacheTask.php index dcfb0855..ca22713c 100755 --- a/src/Cli/Tasks/ClearcacheTask.php +++ b/src/Cli/Tasks/ClearcacheTask.php @@ -21,6 +21,7 @@ use Redis; use function in_array; +use function is_array; use function Phalcon\Api\Core\appPath; use const PHP_EOL; @@ -35,8 +36,10 @@ class ClearcacheTask extends PhTask { /** * Clears the data cache from the application + * + * @return void */ - public function mainAction() + public function mainAction(): void { $this->clearFileCache(); $this->clearRedis(); @@ -44,8 +47,10 @@ public function mainAction() /** * Clears file based cache + * + * @return void */ - private function clearFileCache() + private function clearFileCache(): void { echo 'Clearing Cache folders' . PHP_EOL; @@ -78,8 +83,10 @@ private function clearFileCache() /** * Clears redis data cache + * + * @return void */ - private function clearRedis() + private function clearRedis(): void { echo 'Clearing data cache' . PHP_EOL; @@ -92,8 +99,12 @@ private function clearRedis() $options['options']['port'] ); - $keys = $redis->keys("*"); - $keys = $keys ?: []; + /** + * keys() is typed array|Redis because phpredis answers itself when the + * connection is in pipeline mode. It is not, here. + */ + $keys = $redis->keys('*'); + $keys = is_array($keys) ? $keys : []; echo sprintf('Found %s keys', count($keys)) . PHP_EOL; foreach ($keys as $key) { if ('api-data' === substr($key, 0, 8)) { diff --git a/src/Cli/Tasks/MainTask.php b/src/Cli/Tasks/MainTask.php index 7999df2b..c071fc30 100755 --- a/src/Cli/Tasks/MainTask.php +++ b/src/Cli/Tasks/MainTask.php @@ -21,8 +21,10 @@ class MainTask extends PhTask { /** * Executes the main action of the cli mapping passed parameters to tasks + * + * @return void */ - public function mainAction() + public function mainAction(): void { // 'green' => "\033[0;32m(%s)\033[0m", // 'red' => "\033[0;31m(%s)\033[0m", diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 41445d30..0789def9 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -52,7 +52,7 @@ public function __construct(Logger $logger, Config $config) * @param string $file * @param int $line * - * @return void + * @return bool * @throws Exception */ public function handle( @@ -60,7 +60,7 @@ public function handle( string $message, string $file = '', int $line = 0 - ): void { + ): bool { $this ->logger ->error( @@ -73,6 +73,13 @@ public function handle( ) ) ; + + /** + * The error is handled: it has been logged. Returning nothing would + * hand it on to PHP's internal handler as well, which is the contract + * set_error_handler() documents for a falsy return. + */ + return true; } /** diff --git a/src/Models/Users.php b/src/Models/Users.php index 6e22ab3e..77d67928 100644 --- a/src/Models/Users.php +++ b/src/Models/Users.php @@ -111,7 +111,7 @@ public function initialize(): void } /** - * @return Builder + * @return Token * @throws ModelException * @throws ValidatorException */ diff --git a/src/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php index eb272916..9539a2e2 100644 --- a/src/Mvc/Model/AbstractModel.php +++ b/src/Mvc/Model/AbstractModel.php @@ -151,8 +151,8 @@ private function getSetFields(string $type, string $field, $value = ''): mixed /** * Uses the Phalcon Filter to sanitize the variable passed * - * @param mixed $value The value to sanitize - * @param string|array $filter The filter to apply + * @param mixed $value The value to sanitize + * @param string|array $filter The filter, or a chain of them * * @return mixed */ diff --git a/src/Providers/ErrorHandlerProvider.php b/src/Providers/ErrorHandlerProvider.php index e5f64d5e..d236c54a 100644 --- a/src/Providers/ErrorHandlerProvider.php +++ b/src/Providers/ErrorHandlerProvider.php @@ -38,7 +38,7 @@ public function register(DiInterface $container): void { /** @var Logger $logger */ $logger = $container->getShared('logger'); - /** @var Config $registry */ + /** @var Config $config */ $config = $container->getShared('config'); date_default_timezone_set($config->path('app.timezone')); diff --git a/src/Providers/ModelsMetadataProvider.php b/src/Providers/ModelsMetadataProvider.php index 94c7dcfc..d54d165a 100644 --- a/src/Providers/ModelsMetadataProvider.php +++ b/src/Providers/ModelsMetadataProvider.php @@ -50,7 +50,8 @@ function () use ($config) { $options = $entry['options'] ?? []; if ($adapter === Memory::class) { - return new $adapter($options); + // Memory declares no constructor; it takes no options. + return new Memory(); } $serializer = new SerializerFactory(); diff --git a/src/Providers/RouterProvider.php b/src/Providers/RouterProvider.php index e682661c..94c5ba7d 100644 --- a/src/Providers/RouterProvider.php +++ b/src/Providers/RouterProvider.php @@ -104,7 +104,7 @@ private function attachRoutes(Micro $application): void /** * Returns the array for the middleware with the action to attach * - * @return array + * @return array Middleware class => the Micro hook it attaches to */ private function getMiddleware(): array { @@ -118,11 +118,11 @@ private function getMiddleware(): array /** * Adds multiple routes for the same handler abiding by the JSONAPI standard * - * @param array $routes - * @param string $class - * @param string $relationship + * @param array $routes + * @param class-string $class + * @param string $relationship * - * @return array + * @return array */ private function getMultiRoutes( array $routes, @@ -145,7 +145,7 @@ private function getMultiRoutes( /** * Returns the array for the routes * - * @return array + * @return array Handler, prefix, verb, route */ private function getRoutes(): array { diff --git a/src/Repositories/UsersRepository.php b/src/Repositories/UsersRepository.php index 7ba20c36..dcaef1af 100644 --- a/src/Repositories/UsersRepository.php +++ b/src/Repositories/UsersRepository.php @@ -60,9 +60,17 @@ public function getByToken(Token $token): ?Users 'status' => Flags::ACTIVE, ]; - $result = $this->queryService->getRecords(Users::class, $parameters); + /** + * getFirst() rather than [0]: ResultsetInterface declares the former, + * and answers null on an empty set. + * + * @var Users|null $user + */ + $user = $this->queryService->getRecords(Users::class, $parameters) + ->getFirst() + ; - return $result[0] ?? null; + return $user; } /** @@ -100,7 +108,7 @@ public function getByUsernameAndPassword( false ); /** @var Users|null $user */ - $user = $result[0] ?? null; + $user = $result->getFirst(); if (null === $user) { $this->security->checkHash($password, self::DUMMY_HASH); From 459e068cbbbfaf934625ccf7a501af5a1a679740 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:35:07 -0500 Subject: [PATCH 43/47] [#42] - adding coc --- CODE_OF_CONDUCT.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..634f5e97 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,25 @@ +# Contributor Covenant Code of Conduct + +## TL;DR + +Common sense rules. Treat people the same way you want to be treated. + +## Detail + +This is an open source project. Everyone and anyone is welcome to contribute +to it as much or as little as they want to. + +Personal views of contributors have no effect on the project and should be +kept in the personal realm not this project. + +It is a requirement to treat other contributors as well as maintainers with +respect, when communicating in the realm of this project. It is OK to agree to +disagree. + +## Conflict + +If ever conflict arises, please bring it to the attention of the maintainers +privately. You can always find us on our [Discord](https://phalcon.io/discord) +server or you can send us an email at [team@phalcon.io](mailto:team@phalcon.io) + +The core team maintains the final decision on any conflict that may arise. From 6647b1155111b83cf77bf70dc76f4fb8ea81276a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 15 Jul 2026 23:37:08 -0500 Subject: [PATCH 44/47] [#42] - raise static analysis to level 8 --- resources/phpstan.neon | 2 +- src/Bootstrap/AbstractBootstrap.php | 23 ++++++++++++++--------- src/Mvc/Model/AbstractModel.php | 17 ++++++++++++----- src/Traits/FractalTrait.php | 6 +++++- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/resources/phpstan.neon b/resources/phpstan.neon index bbb82ff9..7d5ca519 100644 --- a/resources/phpstan.neon +++ b/resources/phpstan.neon @@ -4,7 +4,7 @@ parameters: - ../public/index.php # Climbing towards max. Raise one level at a time, clearing the errors each # step introduces before moving up. - level: 7 + level: 8 reportUnmatchedIgnoredErrors: false tmpDir: ../tests/_output # The application registers its own namespaces via library/Core/autoload.php, diff --git a/src/Bootstrap/AbstractBootstrap.php b/src/Bootstrap/AbstractBootstrap.php index 3ee5227d..158228f7 100644 --- a/src/Bootstrap/AbstractBootstrap.php +++ b/src/Bootstrap/AbstractBootstrap.php @@ -25,12 +25,17 @@ abstract class AbstractBootstrap { /** - * @var Console|Micro|null + * Both are set by the time the constructor returns - the container either + * by a subclass before parent::__construct() or by it, the application by + * setupApplication(). Declared without a null default so that is the type + * rather than a promise. + * + * @var Console|Micro */ - protected Console|Micro|null $application = null; + protected Console|Micro $application; - /** @var FactoryDefault|PhCli|null */ - protected FactoryDefault|PhCli|null $container = null; + /** @var FactoryDefault|PhCli */ + protected FactoryDefault|PhCli $container; /** @var array */ protected array $options = []; @@ -43,7 +48,7 @@ abstract class AbstractBootstrap */ public function __construct() { - if (null === $this->container) { + if (false === isset($this->container)) { $this->container = new FactoryDefault(); } @@ -58,17 +63,17 @@ public function __construct() } /** - * @return Console|Micro|null + * @return Console|Micro */ - public function getApplication(): Console|Micro|null + public function getApplication(): Console|Micro { return $this->application; } /** - * @return FactoryDefault|PhCli|null + * @return FactoryDefault|PhCli */ - public function getContainer(): FactoryDefault|PhCli|null + public function getContainer(): FactoryDefault|PhCli { return $this->container; } diff --git a/src/Mvc/Model/AbstractModel.php b/src/Mvc/Model/AbstractModel.php index 9539a2e2..db426cdd 100644 --- a/src/Mvc/Model/AbstractModel.php +++ b/src/Mvc/Model/AbstractModel.php @@ -151,17 +151,24 @@ private function getSetFields(string $type, string $field, $value = ''): mixed /** * Uses the Phalcon Filter to sanitize the variable passed * - * @param mixed $value The value to sanitize - * @param string|array $filter The filter, or a chain of them + * @param mixed $value The value to sanitize + * @param string|array $filter The filter, or a chain of them * * @return mixed + * @throws ModelException */ private function sanitize($value, $filter): mixed { + $container = $this->getDI(); + + if (null === $container) { + throw new ModelException( + 'A model cannot sanitize its fields without a container' + ); + } + /** @var Filter $filterService */ - $filterService = $this->getDI() - ->get('filter') - ; + $filterService = $container->get('filter'); return $filterService->sanitize($value, $filter); } diff --git a/src/Traits/FractalTrait.php b/src/Traits/FractalTrait.php index fc68ecea..0cbc8e86 100644 --- a/src/Traits/FractalTrait.php +++ b/src/Traits/FractalTrait.php @@ -61,9 +61,13 @@ protected function format( /** @var class-string $class */ $class = sprintf('League\Fractal\Resource\%s', ucfirst($method)); + /** + * Scope::toArray() is typed nullable; an empty document is an empty + * array here rather than null. + */ return $manager ->createData(new $class($results, new $transformer($fields, $resource), $resource)) - ->toArray() + ->toArray() ?? [] ; } } From 9b6953d8185366def7a4a9146171e25e39f3d977 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 16 Jul 2026 09:27:34 -0500 Subject: [PATCH 45/47] [#42] - updating composer --- composer.json | 10 +++++++++- composer.lock | 16 ++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 194d4685..f57a11c3 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,18 @@ "suggest": { "ext-phalcon": "Install the Phalcon v5 C extension to run on v5 (the default); alternatively run on v6 via the phalcon/phalcon package - see the Dockerfile PHALCON_VARIANT build arg and the README." }, + "autoload": { + "psr-4": { + "Phalcon\\Api\\": "src/" + } + }, "config": { "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": true, - "allow-plugins": {} + "allow-plugins": { + "infection/extension-installer": true + } }, "require-dev": { "friendsofphp/php-cs-fixer": "^3", @@ -54,6 +61,7 @@ "cs-fixer": "php-cs-fixer fix --config=resources/php-cs-fixer.php --dry-run --diff", "cs-fixer-fix": "php-cs-fixer fix --config=resources/php-cs-fixer.php", "test": "talon run", + "test-mutation": "infection --configuration=resources/infection.json5 --threads=1 --test-framework-options=--testsuite=unit,integration,cli", "test-coverage": "phpunit -c resources/phpunit.xml.dist --coverage-clover tests/_output/coverage.xml", "test-coverage-html": "phpunit -c resources/phpunit.xml.dist --coverage-html tests/_output/coverage", "migrate": "phinx migrate -c resources/phinx.php", diff --git a/composer.lock b/composer.lock index ddb55438..f4684a64 100644 --- a/composer.lock +++ b/composer.lock @@ -67,7 +67,7 @@ }, { "name": "cakephp/core", - "version": "5.2.14", + "version": "5.2.15", "source": { "type": "git", "url": "https://github.com/cakephp/core.git", @@ -134,16 +134,16 @@ }, { "name": "cakephp/database", - "version": "5.2.14", + "version": "5.2.15", "source": { "type": "git", "url": "https://github.com/cakephp/database.git", - "reference": "3dbc1354d6e4ce06db3442c8ec724b3fe99a1659" + "reference": "5f271aa7e6243ea72914a3bb47b0bf60b2142e0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/database/zipball/3dbc1354d6e4ce06db3442c8ec724b3fe99a1659", - "reference": "3dbc1354d6e4ce06db3442c8ec724b3fe99a1659", + "url": "https://api.github.com/repos/cakephp/database/zipball/5f271aa7e6243ea72914a3bb47b0bf60b2142e0d", + "reference": "5f271aa7e6243ea72914a3bb47b0bf60b2142e0d", "shasum": "" }, "require": { @@ -197,11 +197,11 @@ "issues": "https://github.com/cakephp/cakephp/issues", "source": "https://github.com/cakephp/database" }, - "time": "2026-07-14T03:43:51+00:00" + "time": "2026-07-16T03:58:12+00:00" }, { "name": "cakephp/datasource", - "version": "5.2.14", + "version": "5.2.15", "source": { "type": "git", "url": "https://github.com/cakephp/datasource.git", @@ -268,7 +268,7 @@ }, { "name": "cakephp/utility", - "version": "5.2.14", + "version": "5.2.15", "source": { "type": "git", "url": "https://github.com/cakephp/utility.git", From 75acc7c8938959764d1546c13131dbde9321b8d9 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 16 Jul 2026 09:29:04 -0500 Subject: [PATCH 46/47] [#42] - make integration tests idempotent via db truncation and cache reset Drop the Codeception rollback machinery from AbstractIntegrationTestCase: no rollback option, no begin/rollback, no savedRecords tracking, no delete loop through the models under test. tearDown truncates the co_ tables at the database level and setUp clears the Redis data cache, so every test starts from an empty database and cache regardless of order. Fix testLoginKnownUserGetUnknownUser to ask for the id after the one it creates - a fixed /users/1 only worked while ids ran into the thousands. --- tests/Api/Users/GetTest.php | 10 ++- .../AbstractIntegrationTestCase.php | 75 ++++++++++++------- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/tests/Api/Users/GetTest.php b/tests/Api/Users/GetTest.php index c6fc3bd7..86faf855 100644 --- a/tests/Api/Users/GetTest.php +++ b/tests/Api/Users/GetTest.php @@ -136,7 +136,7 @@ public function testLoginKnownUserExpiredToken(): void public function testLoginKnownUserGetUnknownUser(): void { - $this->addApiUserRecord(); + $record = $this->addApiUserRecord(); $this->unsetHttpHeader('Authorization'); $this->sendPost(Data::$loginUrl, Data::loginJson()); $this->assertResponseIsSuccessful(); @@ -145,7 +145,13 @@ public function testLoginKnownUserGetUnknownUser(): void $token = $response['data']['token']; $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - $this->sendGet(Data::$usersUrl . '/1'); + /** + * This test creates a single user, which owns the lowest id, so the + * next id up cannot exist. Asking for a fixed `1` only ever worked + * while ids ran into the thousands; now that tearDown truncates and + * they restart from one, `1` is the user we just made. + */ + $this->sendGet(Data::$usersUrl . '/' . ($record->get('id') + 1)); $this->assertResponseIs404(); } diff --git a/tests/Integration/AbstractIntegrationTestCase.php b/tests/Integration/AbstractIntegrationTestCase.php index 4b362950..7dd15e58 100644 --- a/tests/Integration/AbstractIntegrationTestCase.php +++ b/tests/Integration/AbstractIntegrationTestCase.php @@ -39,22 +39,38 @@ /** * Ported from the Codeception `Helper\Integration` module. Boots the application - * container for each test and tracks the records a test creates so they can be - * removed afterwards. + * container for each test. + * + * No transactions, no rollback, no bookkeeping of created records. A test that + * writes to the database owns what it wrote and removes it itself. Wrapping each + * test in a transaction the harness controlled made it impossible to ever assert + * a real commit or rollback, and deleting through the models under test fell + * apart the moment mutation testing broke those models. */ abstract class AbstractIntegrationTestCase extends AbstractUnitTestCase { - protected ?DiInterface $diContainer = null; + /** + * Every table a test may write to. tearDown() empties them all, which is + * what keeps the suite idempotent now that nothing is rolled back. Order is + * irrelevant: the foreign keys are switched off around the truncation. + * + * @var array + */ + private const TABLES = [ + 'co_companies', + 'co_companies_x_products', + 'co_individual_types', + 'co_individuals', + 'co_product_types', + 'co_products', + 'co_users', + ]; - /** @var array */ - protected array $options = ['rollback' => false]; + protected ?DiInterface $diContainer = null; /** @var array> */ protected array $savedModels = []; - /** @var array */ - protected array $savedRecords = []; - protected function setUp(): void { parent::setUp(); @@ -62,24 +78,20 @@ protected function setUp(): void $app = new Api(); $this->diContainer = $app->getContainer(); - if ($this->options['rollback']) { - $this->diContainer->get('db')->begin(); - } + $this->savedModels = []; - $this->savedModels = []; - $this->savedRecords = []; + /** + * The data cache is Redis, shared with the running server the api suite + * drives over HTTP. A record cached under one test would still answer a + * later test that expects it gone, so each test starts from an empty + * cache as well as an empty database. + */ + $this->diContainer->get('cache')->clear(); } protected function tearDown(): void { - if (!$this->options['rollback']) { - foreach ($this->savedRecords as $record) { - $record->delete(); - } - } else { - $this->diContainer->get('db')->rollback(); - } - + $this->truncateTables(); $this->diContainer->get('db')->close(); parent::tearDown(); @@ -294,8 +306,6 @@ protected function haveRecordWithFields(string $modelName, array $fields = []) $this->savedModels[$modelName] = $fields; $this->assertNotSame(false, $result); - $this->savedRecords[] = $record; - return $record; } @@ -393,12 +403,27 @@ protected function seeRecordSaved(string $model, array $by, array $fields): void { $this->savedModels[$model] = array_merge($by, $fields); - $record = $this->seeRecordFieldsValid( + $this->seeRecordFieldsValid( $model, array_keys($by), array_keys($by) ); + } - $this->savedRecords[] = $record; + /** + * Empties every table a test may have written to, at the database level so + * a broken model cannot get in the way. Foreign keys are switched off for + * the duration: with them on a plain TRUNCATE refuses on any table another + * one references, and the point is precisely to clear all of them. + */ + private function truncateTables(): void + { + $db = $this->diContainer->get('db'); + + $db->execute('SET FOREIGN_KEY_CHECKS = 0'); + foreach (self::TABLES as $table) { + $db->execute('TRUNCATE TABLE ' . $table); + } + $db->execute('SET FOREIGN_KEY_CHECKS = 1'); } } From 886f2c475b9d3f9b3fec3bc7c99bb0121e71e748 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 16 Jul 2026 09:29:14 -0500 Subject: [PATCH 47/47] [#42] - add infection config and harden tests to kill mutants Add resources/infection.json5 with method-scoped mutator ignores, the test-mutation composer script, the extension-installer allow-plugin and a psr-4 map so infection can reflect the source. The infection dependency is not committed - it pulls thecodingmachine/safe dev-master, which deprecation warns on newer php; install it on demand to run. Harden the tests so mutants die: Response send hash and meta, ClearcacheTask exact output with redis, UsersRepository lookups, the model logger call and BaseTransformer field selection. Covered MSI 92%. --- resources/infection.json5 | 155 ++++++++++++++++++ tests/Integration/Library/ModelTest.php | 24 ++- .../Repositories/UsersRepositoryTest.php | 52 ++++++ .../Transformers/BaseTransformerTest.php | 34 ++++ tests/Unit/Cli/ClearCacheTest.php | 127 ++++++++++++-- tests/Unit/Library/ErrorHandlerTest.php | 151 +++++++++++++---- tests/Unit/Library/Http/ResponseTest.php | 56 +++++++ 7 files changed, 547 insertions(+), 52 deletions(-) create mode 100644 resources/infection.json5 diff --git a/resources/infection.json5 b/resources/infection.json5 new file mode 100644 index 00000000..12056564 --- /dev/null +++ b/resources/infection.json5 @@ -0,0 +1,155 @@ +{ + "$schema": "../vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "../tests/_output/infection/infection.log", + "summary": "../tests/_output/infection/summary.log" + }, + "tmpDir": "../tests/_output/infection/tmp", + // Bootstrap and provider code is covered by every integration test, since + // each one boots the whole application; a mutant there re-runs the entire + // suite, and each test now truncates its tables and clears the cache. The + // default 10s is not enough for that, and the shortfall shows up as false + // timeouts rather than real verdicts. + "timeout": 30, + "phpUnit": { + "configDir": "." + }, + "mutators": { + "@default": true, + + // Visibility mutators are equivalent across this codebase. protected -> + // private never shows without a subclass leaning on the wider access, + // and there is none. The public -> protected cases that remain are all + // framework hooks the framework still reaches (Model::initialize and + // validation, Validation::initialize) or transformer include methods + // that League Fractal calls through $this-> from the parent class, + // where protected reads exactly like public. + "ProtectedVisibility": false, + "PublicVisibility": false, + + // memory_get_usage()/1_000_000 and hrtime()/1_000_000: at the two and + // four decimals number_format() keeps, +/-1 on the divisor is below the + // rounding for any real measurement. TokenTrait and the Users token + // leeway carry the same shape - an integer literal whose +/-1 neighbour + // is indistinguishable from a moving clock. + "IncrementInteger": { + "ignore": [ + "Phalcon\\Api\\ErrorHandler::shutdown", + "Phalcon\\Api\\Models\\Users::getValidationData", + "Phalcon\\Api\\Traits\\TokenTrait::getTokenTimeExpiration", + "Phalcon\\Api\\Traits\\TokenTrait::getTokenTimeNotBefore" + ] + }, + "DecrementInteger": { + "ignore": [ + "Phalcon\\Api\\ErrorHandler::shutdown", + "Phalcon\\Api\\Models\\Users::getValidationData", + "Phalcon\\Api\\Traits\\TokenTrait::getTokenTimeExpiration", + "Phalcon\\Api\\Traits\\TokenTrait::getTokenTimeNotBefore" + ] + }, + // time() + 0 and time() - 0 are the same instant. + "Plus": { + "ignore": [ + "Phalcon\\Api\\Traits\\TokenTrait::getTokenTimeNotBefore" + ] + }, + + // The help banner is built from empty-string and PHP_EOL fragments; + // every reshuffle of those produces byte-identical output. BaseTest + // asserts the whole banner and still cannot tell the mutants apart. + "Concat": { + "ignore": [ + "Phalcon\\Api\\Cli\\Tasks\\MainTask::mainAction" + ] + }, + "ConcatOperandRemoval": { + "ignore": [ + "Phalcon\\Api\\Cli\\Tasks\\MainTask::mainAction" + ] + }, + + // getopt() reads the real process arguments, which phpunit cannot set. + // With no arguments every branch of processArguments leaves the task as + // Main, so mutating the option map or its loop changes nothing the suite + // can observe. + "UnwrapArrayKeys": { + "ignore": [ + "Phalcon\\Api\\Bootstrap\\Cli::processArguments" + ] + }, + "Foreach_": { + "ignore": [ + "Phalcon\\Api\\Bootstrap\\Cli::processArguments" + ] + }, + + // The '.' entry is redundant with the isDir() guard right below, which + // already skips the current directory, and del() never returns 0 for a + // key keys('*') just handed back, so > 0 and >= 0 cannot differ. + "GreaterThan": { + "ignore": [ + "Phalcon\\Api\\Cli\\Tasks\\ClearcacheTask::clearRedis" + ] + }, + + // setup() only tunes phql literals and not-null validation; the suite + // sets every field it saves, so neither flag - nor the call itself, nor + // the parent::initialize() that carries it - changes an observable + // outcome. Model::initialize keeps its setSource/relationship calls + // regardless, which the model tests do assert. + // setFilters applies the string filter, which only escapes markup - it + // never empties a value, so it cannot change the presence check's + // outcome and is invisible to validate()'s messages. + "MethodCallRemoval": { + "ignore": [ + "Phalcon\\Api\\Models\\Companies::initialize", + "Phalcon\\Api\\Models\\CompaniesXProducts::initialize", + "Phalcon\\Api\\Models\\Individuals::initialize", + "Phalcon\\Api\\Models\\IndividualTypes::initialize", + "Phalcon\\Api\\Models\\ProductTypes::initialize", + "Phalcon\\Api\\Models\\Products::initialize", + "Phalcon\\Api\\Mvc\\Model\\AbstractModel::initialize", + "Phalcon\\Api\\Repositories\\UsersRepository::getByUsernameAndPassword", + "Phalcon\\Api\\Validation\\CompaniesValidator::initialize" + ] + }, + "ArrayItemRemoval": { + "ignore": [ + "Phalcon\\Api\\Bootstrap\\Cli::processArguments", + "Phalcon\\Api\\Cli\\Tasks\\ClearcacheTask::clearFileCache", + "Phalcon\\Api\\Mvc\\Model\\AbstractModel::initialize" + ] + }, + "FalseValue": { + "ignore": [ + "Phalcon\\Api\\Mvc\\Model\\AbstractModel::initialize", + "Phalcon\\Api\\Repositories\\UsersRepository::getByUsernameAndPassword" + ] + }, + "TrueValue": { + "ignore": [ + "Phalcon\\Api\\Bootstrap\\Cli::processArguments" + ] + }, + "Identical": { + "ignore": [ + "Phalcon\\Api\\Bootstrap\\Cli::processArguments" + ] + }, + + // getByToken looks up by issuer AND tokenId AND status. Turning one + // '=>' into '>' drops a key but the surviving keys still resolve the + // same single row either way, so the lookup is unchanged. + "ArrayItem": { + "ignore": [ + "Phalcon\\Api\\Repositories\\UsersRepository::getByToken" + ] + } + } +} diff --git a/tests/Integration/Library/ModelTest.php b/tests/Integration/Library/ModelTest.php index 2c83e3ec..db38d5f7 100644 --- a/tests/Integration/Library/ModelTest.php +++ b/tests/Integration/Library/ModelTest.php @@ -16,10 +16,12 @@ use Phalcon\Api\Exception\ModelException; use Phalcon\Api\Models\Users; use Phalcon\Api\Tests\Integration\AbstractIntegrationTestCase; +use Phalcon\Logger\Adapter\Stream; use Phalcon\Logger\Logger; use Phalcon\Messages\Message; use function Phalcon\Api\Core\appPath; +use function uniqid; final class ModelTest extends AbstractIntegrationTestCase { @@ -48,9 +50,7 @@ public function testCheckModelMessages(): void public function testCheckModelMessagesWithLogger(): void { - /** @var Logger $logger */ - $logger = $this->grabFromDi('logger'); - $user = $this->mockWithConstructor( + $user = $this->mockWithConstructor( Users::class, [], [ @@ -62,18 +62,28 @@ public function testCheckModelMessagesWithLogger(): void ] ); - $fileName = appPath('storage/logs/api.log'); - $result = $user + $result = $user ->set('username', 'test') ->save() ; $this->assertFalse($result); $this->assertSame('error 1
error 2
', $user->getModelMessages()); + /** + * A fresh, empty log file. The shared api.log is only ever appended to, + * so asserting against it passed even with the logging removed - the + * lines were left over from an earlier test. This one holds only what + * this call writes. + */ + $logFile = appPath('storage/logs/') . uniqid('model-test-') . '.log'; + $logger = new Logger('test', ['main' => new Stream($logFile)]); + $user->getModelMessages($logger); - $this->assertFileContentsContains($fileName, "error 1\n"); - $this->assertFileContentsContains($fileName, "error 2\n"); + $this->assertFileContentsContains($logFile, 'error 1'); + $this->assertFileContentsContains($logFile, 'error 2'); + + $this->safeDeleteFile($logFile); } public function testModelGetNonExistingFields(): void diff --git a/tests/Integration/Library/Repositories/UsersRepositoryTest.php b/tests/Integration/Library/Repositories/UsersRepositoryTest.php index 0b9b87dd..23ab4c71 100644 --- a/tests/Integration/Library/Repositories/UsersRepositoryTest.php +++ b/tests/Integration/Library/Repositories/UsersRepositoryTest.php @@ -30,6 +30,31 @@ final class UsersRepositoryTest extends AbstractIntegrationTestCase { use TokenTrait; + public function testGetByTokenReturnsUser(): void + { + $this->addUserRecord(); + + $signer = new Hmac(); + $builder = new Builder($signer); + $token = $builder + ->setIssuer('phalcon.io') + ->setAudience($this->getTokenAudience()) + ->setId(Data::$testTokenId) + ->setPassphrase(Data::$strongPassphrase) + ->getToken() + ; + + $dbUser = $this->getRepository()->getByToken($token); + + /** + * The lookup is by issuer and token id together; a mutant that breaks + * either key out of the parameter array stops the match, so asserting + * the user comes back is what proves both are still in play. + */ + $this->assertNotNull($dbUser); + $this->assertSame(Data::$testUsername, $dbUser->get('username')); + } + public function testGetByUsernameAndPassword(): void { $this->addUserRecord(); @@ -42,6 +67,33 @@ public function testGetByUsernameAndPassword(): void $this->assertNotNull($dbUser); } + /** + * The username has to be part of the query, not just the active status. + * A record whose password matches but whose name does not must never be + * returned - otherwise any active user's credentials would unlock the + * first active row the database happened to hand back. + */ + public function testGetByUsernameDoesNotMatchOnStatusAlone(): void + { + $this->haveRecordWithFields( + Users::class, + [ + 'username' => 'someone-else', + 'password' => Data::$testPasswordHash, + 'status' => 1, + 'issuer' => 'phalcon.io', + 'tokenId' => Data::$testTokenId, + ] + ); + + $dbUser = $this->getRepository()->getByUsernameAndPassword( + Data::$testUsername, + Data::$testPassword + ); + + $this->assertNull($dbUser); + } + /** * @throws ModelException */ diff --git a/tests/Integration/Library/Transformers/BaseTransformerTest.php b/tests/Integration/Library/Transformers/BaseTransformerTest.php index a6c28536..7f6d3c8c 100644 --- a/tests/Integration/Library/Transformers/BaseTransformerTest.php +++ b/tests/Integration/Library/Transformers/BaseTransformerTest.php @@ -47,4 +47,38 @@ public function testTransformer(): void $this->assertSame($expected, $transformer->transform($company)); } + + /** + * Field selection is intersected against the model's public set: a request + * is never widened to the full set, and never trusted as given. Asking for + * one public field alongside one that is not public returns only the public + * one - which pins the coalesce and the intersection down at once, since + * dropping either would hand back every field or reach for a column the + * model does not publish. + * + * @throws ModelException + */ + public function testTransformerReturnsOnlyTheRequestedPublicFields(): void + { + /** @var Companies $company */ + $company = $this->haveRecordWithFields( + Companies::class, + [ + 'name' => 'acme', + 'address' => '123 Phalcon way', + 'city' => 'World', + 'phone' => '555-999-4444', + ] + ); + + $transformer = new BaseTransformer( + ['companies' => ['name', 'secret']], + 'companies' + ); + + $this->assertSame( + ['name' => $company->get('name')], + $transformer->transform($company) + ); + } } diff --git a/tests/Unit/Cli/ClearCacheTest.php b/tests/Unit/Cli/ClearCacheTest.php index 40c96f0c..3c565fee 100644 --- a/tests/Unit/Cli/ClearCacheTest.php +++ b/tests/Unit/Cli/ClearCacheTest.php @@ -17,24 +17,66 @@ use Phalcon\Api\Cli\Tasks\ClearcacheTask; use Phalcon\Api\Providers\CacheDataProvider; use Phalcon\Api\Providers\ConfigProvider; +use Phalcon\Config\Config; use Phalcon\Di\FactoryDefault\Cli; use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Redis; +use SplFileInfo; +use function chmod; use function fclose; +use function file_exists; +use function file_put_contents; +use function fopen; +use function fwrite; use function iterator_count; use function ob_end_clean; use function ob_get_contents; use function ob_start; use function Phalcon\Api\Core\appPath; use function uniqid; +use function unlink; + +use const PHP_EOL; final class ClearCacheTest extends AbstractUnitTestCase { + /** + * Tracked files that the task's whitelist deliberately spares. The task + * clears the real cache tree, so a mutant that drops '.gitignore' from + * that whitelist deletes them for good: the working tree goes dirty and + * every later run of this test measures the wreckage instead of the task. + * Put them back first so each run starts from the same tree. + */ + private const KEPT_FILES = [ + '/storage/cache/.gitignore', + '/storage/cache/data/.gitignore', + '/storage/cache/metadata/.gitignore', + ]; + + protected function setUp(): void + { + parent::setUp(); + + foreach (self::KEPT_FILES as $file) { + $path = appPath($file); + + if (false === file_exists($path)) { + file_put_contents($path, '*' . PHP_EOL . '!.gitignore' . PHP_EOL); + // Restore the tracked mode; recreating them would leave the + // working tree dirty on the permission bits alone. + chmod($path, 0755); + } + } + } + public function testClearCache(): void { require appPath('vendor/autoload.php'); - $path = appPath('/storage/cache/data'); + $dataPath = appPath('/storage/cache/data'); $container = new Cli(); $config = new ConfigProvider(); $config->register($container); @@ -43,38 +85,95 @@ public function testClearCache(): void $task = new ClearcacheTask(); $task->setDI($container); - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); - $count = iterator_count($iterator); - + /** + * The task reports counts and prints a dot per item cleared, so the + * output is only deterministic if the state going in is. Empty the + * whole tree bar the whitelisted .gitignore files, then seed exactly + * four cache files. + */ + $this->emptyCacheTree(); $this->createFile(); $this->createFile(); $this->createFile(); $this->createFile(); - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); - $this->assertSame($count + 4, iterator_count($iterator)); + $redis = $this->connectRedis($container); + $redis->flushAll(); + $redis->set('api-data-one', 'x'); + $redis->set('api-data-two', 'y'); + // A key outside the api-data namespace the task must leave untouched. + $redis->set('other-key', 'z'); ob_start(); $task->mainAction(); $actual = ob_get_contents(); ob_end_clean(); - // Codeception's assertGreaterOrEquals is spelled assertGreaterThanOrEqual in - // PHPUnit. NOTE: both assertions are vacuous - strpos() returns false when the - // needle is absent, and false >= 0 is true. assertStringContainsString is almost - // certainly the intent; left as-is because that is a change of test logic. - $this->assertGreaterThanOrEqual(0, strpos($actual, 'Clearing Cache folders')); - $this->assertGreaterThanOrEqual(0, strpos($actual, 'Cleared Cache folders')); + /** + * The exact transcript, asserted whole: the phase banners, the two + * counts, and one dot per item. A bare "contains" check let the + * newlines wander and the counts drift; this pins every operand of + * every line down. + */ + $expected = 'Clearing Cache folders' . PHP_EOL + . 'Found 4 files' . PHP_EOL + . '....' + . PHP_EOL . 'Cleared Cache folders' . PHP_EOL + . 'Clearing data cache' . PHP_EOL + . 'Found 3 keys' . PHP_EOL + . '..' + . PHP_EOL . 'Cleared data cache' . PHP_EOL; - $iterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); + $this->assertSame($expected, $actual); + + // The four seeded files are gone; only the .gitignore remains. + $iterator = new FilesystemIterator($dataPath, FilesystemIterator::SKIP_DOTS); $this->assertSame(1, iterator_count($iterator)); + + // The api-data keys were deleted, the unrelated key was spared. + $this->assertSame(0, $redis->exists('api-data-one', 'api-data-two')); + $this->assertSame(1, $redis->exists('other-key')); + + $redis->flushAll(); + } + + private function connectRedis(Cli $container): Redis + { + /** @var Config $config */ + $config = $container->get('config'); + $options = $config->path('cache') + ->toArray()['options'] + ; + + $redis = new Redis(); + $redis->connect($options['host'], (int) $options['port']); + + return $redis; } private function createFile(): void { - $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; + $name = appPath('/storage/cache/data/') . uniqid('cache_') . '.cache'; $pointer = fopen($name, 'wb'); fwrite($pointer, 'test'); fclose($pointer); } + + private function emptyCacheTree(): void + { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + appPath('storage/cache'), + FilesystemIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + /** @var SplFileInfo $file */ + foreach ($iterator as $file) { + if (true !== $file->isDir() && '.gitignore' !== $file->getFilename()) { + unlink($file->getPathname()); + } + } + } } diff --git a/tests/Unit/Library/ErrorHandlerTest.php b/tests/Unit/Library/ErrorHandlerTest.php index 696b29bb..86fdb587 100644 --- a/tests/Unit/Library/ErrorHandlerTest.php +++ b/tests/Unit/Library/ErrorHandlerTest.php @@ -14,55 +14,144 @@ namespace Phalcon\Api\Tests\Unit\Library; use Phalcon\Api\ErrorHandler; -use Phalcon\Api\Logger; -use Phalcon\Api\Providers\ConfigProvider; use Phalcon\Api\Providers\LoggerProvider; +use Phalcon\Config\Config; use Phalcon\Di\FactoryDefault; +use Phalcon\Logger\Logger; use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use function file_exists; +use function file_get_contents; +use function hrtime; use function Phalcon\Api\Core\appPath; +use function preg_match; +use function str_replace; final class ErrorHandlerTest extends AbstractUnitTestCase { + private string $logFile = ''; + + /** + * The logger appends. Left alone, the file still holds what earlier tests + * - and earlier runs - wrote, so every assertion here would pass even with + * the logging removed outright. Start each test from an empty slate. + */ + protected function setUp(): void + { + parent::setUp(); + + $this->logFile = appPath('storage/logs/api.log'); + + $this->safeDeleteFile($this->logFile); + } + public function testLogErrorOnError(): void { - $diContainer = new FactoryDefault(); - $provider = new ConfigProvider(); - $provider->register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - - /** @var Config $config */ - $config = $diContainer->getShared('config'); - /** @var Logger $logger */ - $logger = $diContainer->getShared('logger'); - $handler = new ErrorHandler($logger, $config); + $handler = new ErrorHandler($this->getLogger(), $this->getConfig(true)); + + $result = $handler->handle(1, 'test error', 'file.php', 4); + + $this->assertFileContentsContains( + $this->logFile, + '[error] [#:1]-[L: 4] : test error (file.php)' + ); - $handler->handle(1, 'test error', 'file.php', 4); - $fileName = appPath('storage/logs/api.log'); - $expected = '[error] [#:1]-[L: 4] : test error (file.php)'; + /** + * set_error_handler() reads a falsy return as "not handled" and runs + * PHP's own handler as well. The error has been logged, so it is. + */ + $this->assertTrue($result); + } + + /** + * Not every caller knows where the error came from; the signature defaults + * say so, and the log line has to stay readable when they are taken. + */ + public function testLogErrorOnErrorWithoutFileAndLine(): void + { + $handler = new ErrorHandler($this->getLogger(), $this->getConfig(true)); - $this->assertFileContentsContains($fileName, $expected); + $handler->handle(2, 'no location'); + + $this->assertFileContentsContains( + $this->logFile, + '[error] [#:2]-[L: 0] : no location ()' + ); } public function testLogErrorOnShutdown(): void { - $diContainer = new FactoryDefault(); - $provider = new ConfigProvider(); - $provider->register($diContainer); - $provider = new LoggerProvider(); - $provider->register($diContainer); - - /** @var Config $config */ - $config = $diContainer->getShared('config'); - /** @var Logger $logger */ - $logger = $diContainer->getShared('logger'); - $handler = new ErrorHandler($logger, $config); + $handler = new ErrorHandler($this->getLogger(), $this->getConfig(true)); + + $handler->shutdown(); + + $contents = (string) file_get_contents($this->logFile); + + /** + * The numbers move with the machine, so pin their shape instead: four + * decimals for the milliseconds, two for the megabytes, which is what + * the number_format() precision arguments promise. + */ + $pattern = '/\[info\] Shutdown completed ' + . '\[([\d,]+\.\d{4})\]ms - \[([\d,]+\.\d{2})\]MB/'; + + $this->assertMatchesRegularExpression($pattern, $contents); + + preg_match($pattern, $contents, $matches); + + $execution = (float) str_replace(',', '', $matches[1]); + $memory = (float) str_replace(',', '', $matches[2]); + + /** + * Bounds, not values: one division turns nanoseconds into + * milliseconds, the other bytes into megabytes. Get the operator or + * the operand wrong and the result lands orders of magnitude away, + * which is the only part worth asserting on a moving number. + */ + $this->assertGreaterThanOrEqual(0, $execution); + $this->assertLessThan(60000, $execution); + $this->assertLessThan(1000, $memory); + } + + /** + * The metrics are a development aid. Outside devMode the shutdown has + * nothing to say, and saying it anyway would put timings in production + * logs. + */ + public function testNoLogOnShutdownWhenNotInDevMode(): void + { + $handler = new ErrorHandler($this->getLogger(), $this->getConfig(false)); $handler->shutdown(); - $fileName = appPath('storage/logs/api.log'); - $expected = '[info] Shutdown completed'; - $this->assertFileContentsContains($fileName, $expected); + $contents = true === file_exists($this->logFile) + ? (string) file_get_contents($this->logFile) + : ''; + + $this->assertStringNotContainsString('Shutdown completed', $contents); + } + + private function getConfig(bool $devMode): Config + { + return new Config( + [ + 'app' => [ + 'devMode' => $devMode, + 'time' => hrtime(true), + ], + ] + ); + } + + private function getLogger(): Logger + { + $container = new FactoryDefault(); + + (new LoggerProvider())->register($container); + + /** @var Logger $logger */ + $logger = $container->getShared('logger'); + + return $logger; } } diff --git a/tests/Unit/Library/Http/ResponseTest.php b/tests/Unit/Library/Http/ResponseTest.php index c31fcdef..c9028138 100644 --- a/tests/Unit/Library/Http/ResponseTest.php +++ b/tests/Unit/Library/Http/ResponseTest.php @@ -20,6 +20,9 @@ use function is_string; use function json_decode; +use function ob_end_clean; +use function ob_start; +use function sha1; final class ResponseTest extends AbstractUnitTestCase { @@ -129,6 +132,59 @@ public function testResponseWithValidationErrors(): void $this->assertSame('goodbye', $payload['errors'][1]); } + public function testSendWrapsPayloadWithMetaAndHash(): void + { + $response = new Response(); + $response->setPayloadSuccess(['a' => 'b']); + + $original = $response->getContent(); + + ob_start(); + $response->send(); + ob_end_clean(); + + $payload = json_decode($response->getContent(), true); + + /** + * The meta envelope and both of its members have to survive - dropping + * either leaves clients without the timestamp or the integrity hash. + */ + $this->assertArrayHasKey('meta', $payload); + $this->assertArrayHasKey('timestamp', $payload['meta']); + $this->assertArrayHasKey('hash', $payload['meta']); + + /** + * The hash is sha1 over the timestamp followed by the original body, in + * that order. Reversing the operands or dropping either one produces a + * different digest, which is what the recomputation here pins down. + */ + $this->assertSame( + sha1($payload['meta']['timestamp'] . $original), + $payload['meta']['hash'] + ); + + $this->assertSame(sha1($original), $response->getHeaders()->get('E-Tag')); + + $this->assertSame('1.0', $payload['jsonapi']['version']); + $this->assertSame(['a' => 'b'], $payload['data']); + } + + /** + * An input that already carries a `data` key must be sent as-is, not nested + * under a second `data`. The two branches of setPayloadSuccess only diverge + * for this shape, so it is what nails the is_array test down; a plain array + * takes the same path either way. + */ + public function testSetPayloadSuccessDoesNotDoubleWrapDataKey(): void + { + $response = new Response(); + $response->setPayloadSuccess(['data' => 'value']); + + $payload = $this->checkPayload($response); + + $this->assertSame('value', $payload['data']); + } + private function checkPayload(Response $response, bool $error = false): array { $contents = $response->getContent();