diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c89fca5 --- /dev/null +++ b/composer.json @@ -0,0 +1,80 @@ +{ + "name": "philippe-vandermoere/skeleton", + "type": "project", + "license": "MIT", + "minimum-stability": "stable", + "require": { + "php": "^7.4", + "ext-ctype": "*", + "ext-iconv": "*", + "ext-json": "*", + "symfony/flex": "^1.6" + }, + "flex-require": { + "symfony/console": "*", + "symfony/dotenv": "*", + "symfony/expression-language": "*", + "symfony/framework-bundle": "*", + "symfony/http-client": "*", + "symfony/monolog-bundle": "^3.5", + "symfony/orm-pack": "^1.0", + "symfony/yaml": "*" + }, + "flex-require-dev": { + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-doctrine": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-symfony": "^0.12", + "phpunit/phpunit": "^8.5", + "squizlabs/php_codesniffer": "^3.5", + "symfony/maker-bundle": "^1.0" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "paragonie/random_compat": "2.*", + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php71": "*", + "symfony/polyfill-php70": "*", + "symfony/polyfill-php56": "*" + }, + "scripts": { + "auto-scripts":[ + ], + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ], + "post-create-project-cmd": [ + "./post_create_project.sh" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": true, + "require": "^5.0" + } + } +} diff --git a/post_create_project.sh b/post_create_project.sh new file mode 100755 index 0000000..7bc2fa3 --- /dev/null +++ b/post_create_project.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -e + +readonly GREEN='\e[0;32m' +readonly RESET='\e[0m' + +readonly PROJECT_FOLDER=$(realpath "$(dirname "$0")") +readonly PROJECT_NAME=$(basename "${PROJECT_FOLDER}") +readonly PROJECT_DEV_URL="${PROJECT_NAME,,}.philou.dev" + +cp -rpf "${PROJECT_FOLDER}/skeleton/". "${PROJECT_FOLDER}/" + +rm -f "${PROJECT_FOLDER}/config/services.yaml" +rm -f "${PROJECT_FOLDER}/config/routes.yaml" + +echo -e "${GREEN}Update config files${RESET}" +for file in README.md docs/DOCKER.md docker/dev/.env.dist; do + if [[ -w "${PROJECT_FOLDER}/${file}" ]]; then + sed s/skeleton_name/${PROJECT_NAME}/g -i "${PROJECT_FOLDER}/${file}" + sed s/skeleton_url/${PROJECT_DEV_URL}/g -i "${PROJECT_FOLDER}/${file}" + fi +done + +echo -e "${GREEN}Remove post-create-project-cmd in composer.json${RESET}" +readonly TMP_COMPOSER_FILE=$(mktemp) +mv "${PROJECT_FOLDER}/composer.json" "${TMP_COMPOSER_FILE}" +cat "${TMP_COMPOSER_FILE}" | jq 'del(.scripts."post-create-project-cmd")' --indent 4 > "${PROJECT_FOLDER}/composer.json" +rm -f "${TMP_COMPOSER_FILE}" + +echo -e "${GREEN}Remove Skeleton files${RESET}" +rm -rf "${PROJECT_FOLDER}/skeleton" +rm -f "$0" + +echo -e "${GREEN}Update .gitignore${RESET}" +echo '/.idea' >> "${PROJECT_FOLDER}/.gitignore" +echo '/docker/.env' >> "${PROJECT_FOLDER}/.gitignore" + +echo -e "${GREEN}Initialize GIT repository for ${PROJECT_NAME^}${RESET}" +cd ${PROJECT_FOLDER} +git init -q +git add . +git commit -m "bootstrap project ${PROJECT_NAME}" -q diff --git a/skeleton/.circleci/config.yml b/skeleton/.circleci/config.yml new file mode 100644 index 0000000..5bcccd4 --- /dev/null +++ b/skeleton/.circleci/config.yml @@ -0,0 +1,96 @@ +version: '2.1' +commands: + alpine_checkout: + description: Optimize Alpine checkout. + steps: + - run: + name: Install alpine requirements + command: apk add git openssh-client curl make + - checkout + +executors: + php: + docker: + - image: php:7.4-cli-alpine + +jobs: + vendor: + executor: php + working_directory: ~/repo + steps: + - alpine_checkout + - restore_cache: + key: vendor-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }} + - run: + name: composer + command: | + if [[ ! -f vendor/autoload.php ]]; then + curl --location --silent https://getcomposer.org/composer.phar -o /usr/bin/composer; \ + chmod +x /usr/bin/composer; \ + composer global require hirak/prestissimo; \ + composer install --no-progress --no-interaction; \ + fi + - save_cache: + key: vendor-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }} + paths: + - vendor + - persist_to_workspace: + root: . + paths: + - vendor + + phpcs: + executor: php + working_directory: ~/repo + steps: + - alpine_checkout + - attach_workspace: + at: . + - run: + name: phpcs + command: make phpcs + + phpstan: + executor: php + working_directory: ~/repo + steps: + - alpine_checkout + - attach_workspace: + at: . + - run: + name: phpstan + command: make phpstan + + phpunit: + executor: php + working_directory: ~/repo + steps: + - alpine_checkout + - attach_workspace: + at: . + - run: + name: phpunit + command: make phpunit options="--log-junit ~/phpunit/junit.xml --coverage-html ~/coverage-html" + - store_artifacts: + path: ~/coverage-html + destination: coverage-html + - store_artifacts: + path: ~/phpunit + destination: phpunit + - store_test_results: + path: ~/phpunit + +workflows: + version: '2.1' + tests: + jobs: + - vendor + - phpcs: + requires: + - vendor + - phpstan: + requires: + - vendor + - phpunit: + requires: + - vendor diff --git a/skeleton/.editorconfig b/skeleton/.editorconfig new file mode 100755 index 0000000..1791e19 --- /dev/null +++ b/skeleton/.editorconfig @@ -0,0 +1,12 @@ +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[Makefile] +indent_style = tab + +[*.{php,less,js,yml,yaml}] +indent_style = space +indent_size = 4 diff --git a/skeleton/LICENSE b/skeleton/LICENSE new file mode 100644 index 0000000..4b99069 --- /dev/null +++ b/skeleton/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Philippe VANDERMOERE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skeleton/Makefile b/skeleton/Makefile new file mode 100644 index 0000000..8886732 --- /dev/null +++ b/skeleton/Makefile @@ -0,0 +1,81 @@ +ifndef VERBOSE + MAKEFLAGS += --no-print-directory +endif + +default: + +phpcs: + vendor/bin/phpcs + +phpstan: + APP_ENV=test bin/console cache:clear + vendor/bin/phpstan analyse + +phpunit: + phpdbg -qrr vendor/bin/phpunit $(options) + +tests: phpcs phpstan phpunit + +install: + composer install + bin/console doctrine:database:create --no-interaction --if-not-exists + bin/console doctrine:migrations:migrate --no-interaction --query-time --allow-no-migration + +.out_docker: +ifeq (, $(shell which docker)) + $(error "You must run this command outside the docker container. ") +endif + +start: .out_docker + $(shell docker/dev/configure.sh) + @cd docker/dev && docker-compose build --pull --parallel --quiet + @cd docker/dev && docker-compose up --detach --remove-orphans --quiet-pull --scale nginx=2 --scale php-fpm=2 + @cd docker/dev && docker-compose exec php-fpm make .waiting_for_dependency + @cd docker/dev && docker-compose exec php-fpm make install + +.waiting_for_dependency: + @make .waiting_for service=mysql port=3306 timeout=30 + +.waiting_for: + @echo -e "\e[1;33mWaiting for $(service) is Ready\e[0m" + @/bin/sh -c 'for i in `seq 1 $(timeout)`;do nc $(service) $(port) -w 1 -z && exit 0;sleep 1;done;exit 1' + @echo -e "\e[1;32m$(service) is ready\e[0m" + +stop: .out_docker + @cd docker/dev && docker-compose stop + +remove: .out_docker + @cd docker/dev && docker-compose down --remove-orphans --volumes + +restart: .out_docker + @cd docker/dev && docker-compose restart + +ps: .out_docker + @cd docker/dev && docker-compose ps + +shell: .out_docker + @cd docker/dev && docker-compose exec php-fpm /bin/bash + +mysql_cli: .out_docker + @cd docker/dev && docker-compose exec mysql /bin/bash -c 'mysql -uroot -p$${MYSQL_ROOT_PASSWORD} $${MYSQL_DATABASE}' + +redis_cli: .out_docker + @cd docker/dev && docker-compose exec redis redis-cli + +logs: .out_docker + @cd docker/dev && docker-compose logs --timestamps --follow --tail=50 $(service) + +logs_php-nginx: + @make logs service=nginx + +logs_php-fpm: + @make logs service=php-fpm + +logs_mysql: + @make logs service=mysql + +logs_redis: + @make logs service=redis + +logs_rabbitmq: + @make logs service=rabbitmq diff --git a/skeleton/README.md b/skeleton/README.md new file mode 100644 index 0000000..3ce91de --- /dev/null +++ b/skeleton/README.md @@ -0,0 +1,36 @@ +# skeleton_name + +## Development + +### Installation + +- [with Docker](docs/DOCKER.md) (recommended) + +### Tests + +Run all tests: +- PHPCs +- PHPStan +- PHPUnit + +```bash +make tests +``` + +Run PHPCs tests: + +```bash +make phpcs +``` + +Run PHPStan tests: + +```bash +make phpstan +``` + +Run PHPUnit tests: + +```bash +make phpunit +``` diff --git a/skeleton/config/services/controller.yaml b/skeleton/config/services/controller.yaml new file mode 100644 index 0000000..13562ef --- /dev/null +++ b/skeleton/config/services/controller.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + App\Controller\: + resource: '../../src/Controller' + tags: ['controller.service_arguments'] diff --git a/skeleton/docker/dev/.env.dist b/skeleton/docker/dev/.env.dist new file mode 100644 index 0000000..cb4d211 --- /dev/null +++ b/skeleton/docker/dev/.env.dist @@ -0,0 +1,13 @@ +COMPOSE_PROJECT_NAME=skeleton_name +HTTP_HOST=skeleton_url +DOCKER_UID= +GITHUB_TOKEN= +GITHUB_CERTIFICATES_REPOSITORY= +TIMEZONE=Europe/Paris +MYSQL_ROOT_PASSWORD=root +MYSQL_DATABASE=test +MYSQL_USER=test +MYSQL_PASSWORD=test +AMQP_USER=test +AMQP_PASSWORD=test +AMQP_VHOST=/ diff --git a/skeleton/docker/dev/configure.sh b/skeleton/docker/dev/configure.sh new file mode 100755 index 0000000..b40ed7a --- /dev/null +++ b/skeleton/docker/dev/configure.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +set -e + +# PROMPT COLOURS +readonly RESET='\033[0;0m' +readonly BLACK='\033[0;30m' +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly PURPLE='\033[0;35m' +readonly CYAN='\033[0;36m' +readonly WHITE='\033[0;37m' + +function ask_value() { + local message=$1 + local default_value=$2 + local count=${3:-0} + local value + local default_value_message='' + + if [[ ${count} -ge 3 ]]; then + exit 1 + fi + + if [[ -n ${default_value} ]]; then + default_value_message=" (default: ${YELLOW}${default_value}${CYAN})" + fi + + echo -e "${CYAN}${message}${default_value_message}: ${RESET}" > /dev/tty + read -r value < /dev/tty + + if [[ -z "${value}" ]]; then + if [[ -z ${default_value} ]]; then + value=$(ask_value "${message}" '' $(( count +1 ))) + else + value="${default_value}" + fi + fi + + echo "${value}" +} + +function add_host() { + local host=$1 + + if [[ $(grep -c "${host}" /etc/hosts ) -eq 0 ]]; then + sudo /bin/bash -c "echo \"127.0.0.1 ${host}\" >> /etc/hosts" + fi +} + +function get_compute_env_value() { + local key=$1 + local default_value=$2 + local value + + case ${key} in + COMPOSE_PROJECT_NAME) + value="${default_value}" + ;; + HTTP_HOST) + value=${default_value} + add_host "${value}" + ;; + DOCKER_UID) + value=$(id -u) + ;; + esac + + echo "${value}" +} + +function configure_env_value() { + local env_file=$1 + local key=$2 + local default_value=$3 + local value + + if [[ ! -f "${env_file}" ]]; then + touch "${env_file}" + fi + + value=$(get_compute_env_value "${key}" "${default_value}") + + if [[ -z "${value}" ]]; then + if [[ $(grep -Ec "^${key}=" "${env_file}") -eq 0 ]]; then + value=$(ask_value "Define the value of ${key}" "${default_value}") + + if [[ -z "${value}" ]]; then + echo -e "${RED}No value provide for key ${key}.${RESET}" > /dev/tty + exit 1 + fi + + else + value=$(awk -F "${key} *= *" '{print $2}' "${env_file}") + fi + fi + + sed -e "/^${key}=/d" -i "${env_file}" + echo "${key}=${value}" >> "${env_file}" +} + +cd "$(dirname "$0")" + +while read -r line; do + if [[ -z "${line}" ]]; then + continue + fi + + configure_env_value .env "${line%%=*}" "${line#*=}" +done < .env.dist diff --git a/skeleton/docker/dev/docker-compose.yml b/skeleton/docker/dev/docker-compose.yml new file mode 100644 index 0000000..ebf37b5 --- /dev/null +++ b/skeleton/docker/dev/docker-compose.yml @@ -0,0 +1,73 @@ +version: '3.7' +services: + nginx: + labels: &label-docker-proxy + com.docker-proxy.domain: ${HTTP_HOST} + com.docker-proxy.ssl: true + com.docker-proxy.certificate-provider.name: github + com.docker-proxy.certificate-provider.token: ${GITHUB_TOKEN} + com.docker-proxy.certificate-provider.repository: ${GITHUB_CERTIFICATES_REPOSITORY} + com.docker-proxy.certificate-provider.certificate_path: ${HTTP_HOST}/fullchain.pem + com.docker-proxy.certificate-provider.private_key_path: ${HTTP_HOST}/privkey.pem + build: + context: nginx + environment: + PHP_FPM_UPSTREAM: php-fpm:9000 + DNS_RESOLVER: 127.0.0.11 + volumes: + - ./../../public:/var/www/html/public + + php-fpm: &php-service + hostname: ${COMPOSE_PROJECT_NAME}-php-fpm + build: + context: php + args: + DOCKER_UID: ${DOCKER_UID} + environment: + DATABASE_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE}?serverVersion=8.0&charset=utf8 + TIMEZONE: ${TIMEZONE} + volumes: + - ./../..:/var/www/html + tmpfs: + - /var/www/html/var/cache:uid=${DOCKER_UID},gid=82 + + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + TZ: ${TIMEZONE} + volumes: + - mysql:/var/lib/mysql + + adminer: + labels: + <<: *label-docker-proxy + com.docker-proxy.port: 8080 + com.docker-proxy.path: /adminer + image: adminer:4.7 + environment: + ADMINER_DEFAULT_SERVER: mysql + + redis: + image: redis:5.0-alpine + + rabbitmq: + labels: + <<: *label-docker-proxy + com.docker-proxy.port: 15672 + com.docker-proxy.path: /rabbitmq + image: rabbitmq:3.8-management-alpine + environment: + RABBITMQ_DEFAULT_USER: ${AMQP_USER} + RABBITMQ_DEFAULT_PASS: ${AMQP_PASSWORD} + RABBITMQ_DEFAULT_VHOST: ${AMQP_VHOST} + RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: -rabbitmq_management path_prefix "/rabbitmq" + volumes: + - rabbitmq:/var/lib/rabbitmq + +volumes: + mysql: + rabbitmq: diff --git a/skeleton/docker/dev/nginx/Dockerfile b/skeleton/docker/dev/nginx/Dockerfile new file mode 100644 index 0000000..a4c1349 --- /dev/null +++ b/skeleton/docker/dev/nginx/Dockerfile @@ -0,0 +1,11 @@ +FROM nginx:1.16-alpine + +ENV PHP_FPM_UPSTREAM=php:9000 \ + DNS_RESOLVER=127.0.0.11 + +COPY config/vhost.conf /etc/nginx/conf.d/vhost.template +COPY config/nginx.conf /etc/nginx/nginx.conf + +WORKDIR /var/www/html + +CMD ["/bin/sh", "-c", "envsubst '$PHP_FPM_UPSTREAM,$DNS_RESOLVER' < /etc/nginx/conf.d/vhost.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"] diff --git a/skeleton/docker/dev/nginx/config/nginx.conf b/skeleton/docker/dev/nginx/config/nginx.conf new file mode 100644 index 0000000..5e0f548 --- /dev/null +++ b/skeleton/docker/dev/nginx/config/nginx.conf @@ -0,0 +1,40 @@ +user nginx; +worker_processes auto; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + server_tokens off; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_requests 100; + keepalive_timeout 65; + + types_hash_max_size 2048; + + server_names_hash_bucket_size 128; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_x_request_id $request_uid { + default $http_x_request_id; + "" $request_id; + } + + log_format docker '$remote_addr - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" $request_time $request_uid'; + + access_log /var/log/nginx/access.log docker; + error_log /var/log/nginx/error.log error; + + gzip on; + gzip_disable "msie6"; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/skeleton/docker/dev/nginx/config/vhost.conf b/skeleton/docker/dev/nginx/config/vhost.conf new file mode 100644 index 0000000..4119c57 --- /dev/null +++ b/skeleton/docker/dev/nginx/config/vhost.conf @@ -0,0 +1,34 @@ +server { + root /var/www/html/public; + + add_header X-Request-Id $request_uid; + + location / { + try_files $uri /index.php$is_args$args; + } + + location ~ ^/index.php(/|$) { + resolver_timeout 5s; + resolver ${DNS_RESOLVER} valid=10s; + + fastcgi_pass ${PHP_FPM_UPSTREAM}; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param HTTP_X_REQUEST_ID $request_uid; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + internal; + } + + location ~ \.php$ { + return 404; + } + + location /health { + return 200; + } + + location /status { + stub_status on; + access_log off; + } +} diff --git a/skeleton/docker/dev/php/Dockerfile b/skeleton/docker/dev/php/Dockerfile new file mode 100644 index 0000000..d259392 --- /dev/null +++ b/skeleton/docker/dev/php/Dockerfile @@ -0,0 +1,60 @@ +FROM php:7.4-fpm-alpine + +# php-fpm config +ENV PHP_FPM_PM_LOG_LEVEL=warning \ + PHP_FPM_PM_MAX_CHILDREN=5 \ + PHP_FPM_PM_START_SERVER=2 \ + PHP_FPM_PM_MIN_SPARE_SERVER=1 \ + PHP_FPM_PM_MAX_SPARE_SERVER=3 \ + PHP_FPM_PM_STATUS_PATH=/status \ + PHP_FPM_PM_PING_PATH=/ping \ + TIMEZONE=UTC + +# Install requirement +RUN set -xe; \ + apk add --update --no-cache \ + bash \ + bash-completion \ + curl \ + openssl \ + git \ + make \ + ; + +# Install php extension +RUN docker-php-ext-install -j$(nproc) \ + opcache \ + pdo \ + pdo_mysql \ + ; + +# Install composer +RUN set -xe; \ + curl -sl https://getcomposer.org/composer.phar -o /usr/local/bin/composer; \ + chmod +x /usr/local/bin/composer; + +ARG DOCKER_UID + +RUN set -xe; \ + apk add --no-cache --virtual .build-deps shadow; \ + mkdir -p /var/www/html; \ + usermod -u ${DOCKER_UID} www-data -d /var/www; \ + chown -R www-data:www-data /var/www; \ + apk del --no-network .build-deps; + +# Add bashrc +COPY config/.bashrc /var/www/.bashrc + +# Configure php-fpm +COPY config/php-fpm/* /usr/local/etc/php-fpm.d/ + +# Configure php +COPY config/php/* /usr/local/etc/php/conf.d/ + +USER www-data + +RUN composer global require hirak/prestissimo; + +WORKDIR /var/www/html + +CMD ["/bin/sh", "-c", "bin/console cache:warmup --env=dev && php-fpm"] diff --git a/skeleton/docker/dev/php/config/.bashrc b/skeleton/docker/dev/php/config/.bashrc new file mode 100644 index 0000000..380a213 --- /dev/null +++ b/skeleton/docker/dev/php/config/.bashrc @@ -0,0 +1,72 @@ +# PROMPT COLOURS +BLACK='\[\e[0;30m\]' # Black - Regular +RED='\[\e[0;31m\]' # Red - Regular +GREEN='\[\e[0;32m\]' # Green - Regular +YELLOW='\[\e[0;33m\]' # Yellow - Regular +BLUE='\[\e[0;34m\]' # Blue - Regular +PURPLE='\[\e[0;35m\]' # Purple - Regular +CYAN='\[\e[0;36m\]' # Cyan - Regular +WHITE='\[\e[0;37m\]' # White - Regular +BLACK_BOLD='\[\e[1;30m\]' # Black - Bold +RED_BOLD='\[\e[1;31m\]' # Red - Bold +GREEN_BOLD='\[\e[1;32m\]' # Green - Bold +YELLOW_BOLD='\[\e[1;33m\]' # Yellow - Bold +BLUE_BOLD='\[\e[1;34m\]' # Blue - Bold +PURPLE_BOLD='\[\e[1;35m\]' # Purple - Bold +CYAN_BOLD='\[\e[1;36m\]' # Cyan - Bold +WHITE_BOLD='\[\e[1;37m\]' # White - Bold +BLACK_FOREGROUND='\[\e[100m\]' # Black - Foreground +RED_FOREGROUND='\[\e[101m\]' # Red - Foreground +GREEN_FOREGROUND='\[\e[102m\]' # Green - Foreground +YELLOW_FOREGROUND='\[\e[103m\]' # Yellow - Foreground +BLUE_FOREGROUND='\[\e[104m\]' # Blue - Foreground +PURPLE_FOREGROUND='\[\e[105m\]' # Purple - Foreground +CYAN_FOREGROUND='\[\e[106m\]' # Cyan - Foreground +WHITE_FOREGROUND='\[\e[107m\]' # White - Foreground +RESET='\[\e[0m\]' # Text Reset + +alias ls='ls --color=auto' +alias ll='ls -lah' +alias grep='grep --color=auto' + +if [[ -f /etc/bash_completion ]]; then + . /etc/bash_completion +fi + +if [[ -f /etc/profile.d/bash_completion.sh ]]; then + source /etc/profile.d/bash_completion.sh +fi + +get_user() { + if [[ $(id -u) -eq 0 ]]; then + echo -n "${YELLOW_BOLD}\u" + else + echo -n "${RED_BOLD}\u" + fi +} + +get_host() { + echo -n "${BLUE_BOLD}\h" +} + +get_working_directory() { + echo -n "${PURPLE_BOLD}\w" +} + +get_branch() { + local branch=$(git symbolic-ref HEAD --short 2> /dev/null) + + if [[ ! -z ${branch} ]]; then + echo -n "(${branch})" + fi +} + +get_tag() { + local tag=$(git describe --exact-match --tags HEAD 2> /dev/null) + + if [[ ! -z ${tag} ]]; then + echo -n "(${tag})" + fi +} + +export PS1="$(get_user)${CYAN_BOLD}@$(get_host) $(get_working_directory) ${YELLOW_BOLD}\$(get_branch)${GREEN_BOLD}\$(get_tag) ${BLACK_BOLD}\n\$${RESET} " diff --git a/skeleton/docker/dev/php/config/php-fpm/fpm.conf b/skeleton/docker/dev/php/config/php-fpm/fpm.conf new file mode 100644 index 0000000..a98ea91 --- /dev/null +++ b/skeleton/docker/dev/php/config/php-fpm/fpm.conf @@ -0,0 +1,4 @@ +[global] +daemonize = no +log_level = ${PHP_FPM_PM_LOG_LEVEL} +error_log = /proc/self/fd/2; diff --git a/skeleton/docker/dev/php/config/php-fpm/www.conf b/skeleton/docker/dev/php/config/php-fpm/www.conf new file mode 100644 index 0000000..bf559cb --- /dev/null +++ b/skeleton/docker/dev/php/config/php-fpm/www.conf @@ -0,0 +1,19 @@ +[www] +pm = dynamic +pm.max_children = ${PHP_FPM_PM_MAX_CHILDREN} +pm.start_servers = ${PHP_FPM_PM_START_SERVER} +pm.min_spare_servers = ${PHP_FPM_PM_MIN_SPARE_SERVER} +pm.max_spare_servers = ${PHP_FPM_PM_MAX_SPARE_SERVER} + +pm.status_path = ${PHP_FPM_PM_STATUS_PATH} +ping.path = ${PHP_FPM_PM_PING_PATH} + +listen = [::]:9000 +clear_env = no +catch_workers_output = yes + +php_flag[log_errors] = yes +php_flag[display_errors] = off + +; access.format = %{HTTP_X_REQUEST_ID}e +access.log = /proc/self/fd/2 diff --git a/skeleton/docker/dev/php/config/php/preload.ini b/skeleton/docker/dev/php/config/php/preload.ini new file mode 100644 index 0000000..9a12ef1 --- /dev/null +++ b/skeleton/docker/dev/php/config/php/preload.ini @@ -0,0 +1 @@ +opcache.preload=/var/www/html/var/cache/dev/App_KernelDevDebugContainer.preload.php diff --git a/skeleton/docker/dev/php/config/php/timezone.ini b/skeleton/docker/dev/php/config/php/timezone.ini new file mode 100644 index 0000000..920e4a0 --- /dev/null +++ b/skeleton/docker/dev/php/config/php/timezone.ini @@ -0,0 +1,2 @@ +[Date] +date.timezone = ${TIMEZONE} diff --git a/skeleton/docs/DOCKER.md b/skeleton/docs/DOCKER.md new file mode 100644 index 0000000..d0552f2 --- /dev/null +++ b/skeleton/docs/DOCKER.md @@ -0,0 +1,38 @@ +# Docker + +## Requirements + +- [Docker](https://docs.docker.com/install/#supported-platforms) >= 18.06.0 +- [Docker compose](https://docs.docker.com/compose/install) >= 1.25 +- [Docker Proxy](https://github.com/philippe-vandermoere/docker-proxy) + +## Start + +To run the Docker's stack, execute: + +```bash +make start +``` + +This command does: +- Build Docker's image +- Install PHP vendor +- Install or upgrade database + +Your application is reachable at `https://skeleton_url` + +## Shell + +To connect to the PHP shell in the docker's container, execute: + +```bash +make shell +``` + +## Logs + +If you want to see the logs of docker's container, execute: + +```bash +make logs +``` diff --git a/skeleton/phpcs.xml.dist b/skeleton/phpcs.xml.dist new file mode 100644 index 0000000..c42a038 --- /dev/null +++ b/skeleton/phpcs.xml.dist @@ -0,0 +1,65 @@ + + + + + + + + + + src + tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/skeleton/phpstan.neon b/skeleton/phpstan.neon new file mode 100644 index 0000000..80741ce --- /dev/null +++ b/skeleton/phpstan.neon @@ -0,0 +1,17 @@ +parameters: + level: max + paths: + - src/ + - tests/ + + excludes_analyse: + - src/Migrations/ + + symfony: + container_xml_path: var/cache/test/App_KernelTestDebugContainer.xml + + checkGenericClassInNonGenericObjectType: false +includes: + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-doctrine/extension.neon + - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/skeleton/phpunit.xml.dist b/skeleton/phpunit.xml.dist new file mode 100644 index 0000000..233b36c --- /dev/null +++ b/skeleton/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + tests + + + + + + src + + src/Kernel.php + + + + + + + + diff --git a/skeleton/src/Kernel.php b/skeleton/src/Kernel.php new file mode 100644 index 0000000..3fc35f3 --- /dev/null +++ b/skeleton/src/Kernel.php @@ -0,0 +1,56 @@ +getProjectDir() . '/config/bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function getProjectDir(): string + { + return \dirname(__DIR__); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); + $container->setParameter('container.dumper.inline_class_loader', true); + $container->setParameter('container.dumper.inline_factories', true); + + $confDir = $this->getProjectDir() . '/config'; + + $loader->load($confDir . '/{packages}/*' . static::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/{packages}/' . $this->environment . '/*' . static::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/{services}/' . $this->environment . '/**/*' . static::CONFIG_EXTS, 'glob'); + $loader->load($confDir . '/{services}/*' . static::CONFIG_EXTS, 'glob'); + } + + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $confDir = $this->getProjectDir() . '/config'; + + $routes->import($confDir . '/{routes}/' . $this->environment . '/*' . static::CONFIG_EXTS, '/', 'glob'); + $routes->import($confDir . '/{routes}/*' . static::CONFIG_EXTS, '/', 'glob'); + } +}