diff --git a/.env b/.env index 069a74b..8d91fa9 100644 --- a/.env +++ b/.env @@ -23,7 +23,7 @@ APP_SECRET=18196d82bf6849e427a0d8c3a42809fe # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml # # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" -# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" +DATABASE_URL=mysql://app:secret@127.0.0.1:3306/app +# DATABASE_URL="postgresql://app:secret@api-postgres/app" # DATABASE_URL="postgresql://app:secret@api-postgres/app" - ###< doctrine/doctrine-bundle ### diff --git a/Makefile b/Makefile index 60aa774..4167fd5 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,26 @@ -init: down build up api-composer-install init-db +init: down up up: - docker-compose -f docker-compose.yaml up -d + docker-compose -f docker-compose.yml up -d down: - docker-compose -f docker-compose.yaml down -v --remove-orphans -build: - docker-compose -f docker-compose.yaml build --pull -api-composer-install: - docker-compose -f docker-compose.yaml run --rm php-cli composer install -api-composer-update: - docker-compose -f docker-compose.yaml run --rm php-cli composer update -init-db: - docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:schema:drop --force && \ - docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:schema:create -clear-cache: - docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:cache:clear-metadata && \ - docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:cache:clear-query \ No newline at end of file + docker-compose -f docker-compose.yml down -v --remove-orphans + + +#init: down build up api-composer-install init-db +#up: +# docker-compose -f docker-compose.yaml up -d +#down: +# docker-compose -f docker-compose.yaml down -v --remove-orphans +#build: +# docker-compose -f docker-compose.yaml build --pull +#api-composer-install: +# docker-compose -f docker-compose.yaml run --rm php-cli composer install +#api-composer-update: +# docker-compose -f docker-compose.yaml run --rm php-cli composer update +#init-db: +# docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:schema:drop --force && \ +# docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:schema:create +#clear-cache: +# docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:cache:clear-metadata && \ +# docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:cache:clear-query +#load-fixture: +# docker-compose -f docker-compose.yaml run --rm php-cli bin/console doctrine:fixtures:load \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..9a25c7c --- /dev/null +++ b/Readme.md @@ -0,0 +1,45 @@ +# Symfony 5 web application **libraries** + +## Getting started + +### To get it working, follow these steps: + +- Clone the repository from github + ```git + https://github.com/SyntaxErrorLineNULL/start-mobile.git + ``` + +### If you have Docker installed +### Init DataBase +```` + make init +```` + ++ run migrations + +````bash + php bin/console doctrine:migrations:migrate + OR + php bin/console doctrine:schema:create +```` + +if you don't want to work with an empty project, you can add data to the table + +```` + php bin/console doctrine:fixtures:load +```` + +Start the built-in web server + +You can use Nginx or Apache, but the built-in web server works great: +````bash +php -S 127.0.0.1:8000 -t public + or +symfony server:start +```` + +##### Now check out the site at https://127.0.0.1:8000 + +### What has not been done yet +- [ ] not work docker container in use PHP-fpm, PHP-cli. Database is not connection for doctrine, with this config(PHP-fpm, cli, nginx). +- [ ] create autoload OpenApi documentation diff --git a/composer.json b/composer.json index 26bb332..fc0b7ab 100644 --- a/composer.json +++ b/composer.json @@ -16,9 +16,12 @@ "doctrine/orm": "^2.8", "fakerphp/faker": "^1.17", "friendsofsymfony/rest-bundle": "^3.0", + "sensio/framework-extra-bundle": "^6.2", "symfony/console": "5.2.*", "symfony/dotenv": "5.2.*", + "symfony/expression-language": "5.2.*", "symfony/flex": "^1.3.1", + "symfony/form": "5.2.*", "symfony/framework-bundle": "5.2.*", "symfony/monolog-bundle": "^3.7", "symfony/proxy-manager-bridge": "5.2.*", diff --git a/composer.lock b/composer.lock index 2432b2c..7dd58e4 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": "0a6363f2a5ce58725094ea5ae4b54452", + "content-hash": "6fc0bd52da151a6c521ff55108b0fd1c", "packages": [ { "name": "composer/package-versions-deprecated", @@ -2053,6 +2053,84 @@ }, "time": "2021-07-14T16:41:46+00:00" }, + { + "name": "sensio/framework-extra-bundle", + "version": "v6.2.2", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", + "reference": "8be174975f05cb9a91860308b8b306213689b736" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/8be174975f05cb9a91860308b8b306213689b736", + "reference": "8be174975f05cb9a91860308b8b306213689b736", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0" + }, + "conflict": { + "doctrine/doctrine-cache-bundle": "<1.3.1", + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "doctrine/dbal": "^2.10|^3.0", + "doctrine/doctrine-bundle": "^1.11|^2.0", + "doctrine/orm": "^2.5", + "symfony/browser-kit": "^4.4|^5.0|^6.0", + "symfony/doctrine-bridge": "^4.4|^5.0|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/monolog-bridge": "^4.0|^5.0|^6.0", + "symfony/monolog-bundle": "^3.2", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", + "symfony/security-bundle": "^4.4|^5.0|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "twig/twig": "^1.34|^2.4|^3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "6.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sensio\\Bundle\\FrameworkExtraBundle\\": "src/" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "This bundle provides a way to configure your controllers with annotations", + "keywords": [ + "annotations", + "controllers" + ], + "support": { + "issues": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/issues", + "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v6.2.2" + }, + "time": "2021-12-17T02:08:16+00:00" + }, { "name": "symfony/cache", "version": "v5.2.12", @@ -2973,6 +3051,70 @@ ], "time": "2021-07-12T14:48:14+00:00" }, + { + "name": "symfony/expression-language", + "version": "v5.2.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "d4367d36217dd395b10f61649a6bf2c1367140d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/d4367d36217dd395b10f61649a6bf2c1367140d8", + "reference": "d4367d36217dd395b10f61649a6bf2c1367140d8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/cache": "^4.4|^5.0", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "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 an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v5.2.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": "2021-07-21T12:38:00+00:00" + }, { "name": "symfony/filesystem", "version": "v5.2.12", @@ -3163,6 +3305,107 @@ ], "time": "2021-11-29T15:39:37+00:00" }, + { + "name": "symfony/form", + "version": "v5.2.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "929811a7667aa5814f93a70974423870b14c05aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/929811a7667aa5814f93a70974423870b14c05aa", + "reference": "929811a7667aa5814f93a70974423870b14c05aa", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/intl": "^4.4|^5.0", + "symfony/options-resolver": "^5.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/property-access": "^5.0.8", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<4.4", + "symfony/doctrine-bridge": "<4.4", + "symfony/error-handler": "<4.4.5", + "symfony/framework-bundle": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/intl": "<4.4", + "symfony/translation": "<4.4", + "symfony/translation-contracts": "<1.1.7", + "symfony/twig-bridge": "<4.4" + }, + "require-dev": { + "doctrine/collections": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/security-csrf": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/validator": "^4.4.17|^5.1.9", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/twig-bridge": "For templating with Twig.", + "symfony/validator": "For form validation." + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Form\\": "" + }, + "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 to easily create, process and reuse HTML forms", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/form/tree/v5.2.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": "2021-07-23T15:54:19+00:00" + }, { "name": "symfony/framework-bundle", "version": "v5.2.12", @@ -3575,6 +3818,94 @@ ], "time": "2021-07-29T06:52:15+00:00" }, + { + "name": "symfony/intl", + "version": "v5.2.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "23ae12a613eb77725c6ef6a15d37b0e9956c6a2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/23ae12a613eb77725c6ef6a15d37b0e9956c6a2a", + "reference": "23ae12a613eb77725c6ef6a15d37b0e9956c6a2a", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/filesystem": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "to use the component with locales other than \"en\"" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "support": { + "source": "https://github.com/symfony/intl/tree/v5.2.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": "2021-07-23T15:54:19+00:00" + }, { "name": "symfony/monolog-bridge", "version": "v5.2.12", @@ -3739,6 +4070,75 @@ ], "time": "2021-11-05T10:34:29+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v5.2.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "1935d2e5329aba28cbb9ef6cc5687d007619d96d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/1935d2e5329aba28cbb9ef6cc5687d007619d96d", + "reference": "1935d2e5329aba28cbb9ef6cc5687d007619d96d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.2.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": "2021-07-23T15:54:19+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.23.1", @@ -3820,6 +4220,93 @@ ], "time": "2021-05-27T12:26:48+00:00" }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "4a80a521d6176870b6445cfb469c130f9cae1dda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/4a80a521d6176870b6445cfb469c130f9cae1dda", + "reference": "4a80a521d6176870b6445cfb469c130f9cae1dda", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "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": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.23.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": "2021-05-24T10:04:56+00:00" + }, { "name": "symfony/polyfill-intl-normalizer", "version": "v1.23.0", diff --git a/config/bundles.php b/config/bundles.php index 5d8c18d..6fc89a1 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -11,4 +11,5 @@ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], ]; diff --git a/config/packages/sensio_framework_extra.yaml b/config/packages/sensio_framework_extra.yaml new file mode 100644 index 0000000..1821ccc --- /dev/null +++ b/config/packages/sensio_framework_extra.yaml @@ -0,0 +1,3 @@ +sensio_framework_extra: + router: + annotations: false diff --git a/config/services.yaml b/config/services.yaml index c7296dd..10be7e1 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -29,3 +29,6 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + App\EventSubscriber\HeaderExistsExceptionListener: + tags: + - { name: kernel.event_listener, event: kernel.exception } \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 6d57cd9..bd53750 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,6 +27,8 @@ services: volumes: - ./:/app working_dir: /app + links: + - start-mobile-database php-cli: container_name: mobile_php-cli @@ -40,12 +42,24 @@ services: DB_NAME: app volumes: - ./:/app + links: + - start-mobile-database - api-postgres: - image: postgres:13.1-alpine - environment: - POSTGRES_USER: app - POSTGRES_PASSWORD: secret - POSTGRES_DB: app + start-mobile-database: + image: mysql:8.0 ports: - - "127.0.0.1:54324:5432" \ No newline at end of file + - "3306:3306" + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_DATABASE: app + MYSQL_USER: app + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: root + volumes: + - ./dump:/docker-entrypoint-initdb.d + - ./conf:/etc/mysql/conf.d + - persistent:/var/lib/mysql + networks: + - default +volumes: + persistent: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ecb89ac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + db: + image: mysql:8.0 + ports: + - "3306:3306" + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_DATABASE: app + MYSQL_USER: app + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: root + volumes: + - ./dump:/docker-entrypoint-initdb.d + - ./conf:/etc/mysql/conf.d + - persistent:/var/lib/mysql + networks: + - default +volumes: + persistent: \ No newline at end of file diff --git a/docker/php-cli/Dockerfile b/docker/php-cli/Dockerfile index f1d5996..84a6a0a 100644 --- a/docker/php-cli/Dockerfile +++ b/docker/php-cli/Dockerfile @@ -2,10 +2,13 @@ FROM php:8.0-cli-alpine3.12 RUN apk add --no-cache autoconf g++ make +#install mysql +RUN docker-php-ext-install pdo pdo_mysql + #install PG -RUN apk add --no-cache postgresql-dev bash coreutils \ - && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ - && docker-php-ext-install pdo_pgsql opcache +#RUN apk add --no-cache postgresql-dev bash coreutils \ +# && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ +# && docker-php-ext-install pdo_pgsql opcache RUN apk add --no-cache unzip diff --git a/docker/php-fpm/Dockerfile b/docker/php-fpm/Dockerfile index 739a223..1d4000a 100644 --- a/docker/php-fpm/Dockerfile +++ b/docker/php-fpm/Dockerfile @@ -1,7 +1,10 @@ FROM php:8.0-fpm-alpine3.13 -RUN apk add --no-cache postgresql-dev fcgi \ - && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ - && docker-php-ext-install pdo_pgsql +#install mysql +RUN docker-php-ext-install pdo pdo_mysql + +#RUN apk add --no-cache postgresql-dev fcgi \ +# && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ +# && docker-php-ext-install pdo_pgsql RUN docker-php-ext-install bcmath fileinfo \ No newline at end of file diff --git a/migrations/Version20211220074118.php b/migrations/Version20211220074118.php new file mode 100644 index 0000000..9234290 --- /dev/null +++ b/migrations/Version20211220074118.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE author (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(55) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE book (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(155) NOT NULL, description VARCHAR(255) DEFAULT NULL, author_id INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE author'); + $this->addSql('DROP TABLE book'); + } +} diff --git a/src/Common/Admin/Dto/Author.php b/src/Common/Admin/Dto/Author.php new file mode 100644 index 0000000..63e85d4 --- /dev/null +++ b/src/Common/Admin/Dto/Author.php @@ -0,0 +1,31 @@ +id = $id; + $this->name = $name; + $this->countBook = $countBook; + } + +} \ No newline at end of file diff --git a/src/Common/Admin/Mapper/AuthorMapper.php b/src/Common/Admin/Mapper/AuthorMapper.php new file mode 100644 index 0000000..9b0b78b --- /dev/null +++ b/src/Common/Admin/Mapper/AuthorMapper.php @@ -0,0 +1,34 @@ +bookRepository->countBook($entity->getId()); + return new Author( + $entity->getId(), + $entity->getName(), + $countBook + ); + } +} \ No newline at end of file diff --git a/src/Common/Api/Dto/Author.php b/src/Common/Api/Dto/Author.php new file mode 100644 index 0000000..c6acc88 --- /dev/null +++ b/src/Common/Api/Dto/Author.php @@ -0,0 +1,27 @@ +id = $id; + $this->name = $name; + } + +} \ No newline at end of file diff --git a/src/Common/Api/Dto/Book.php b/src/Common/Api/Dto/Book.php new file mode 100644 index 0000000..dc550e6 --- /dev/null +++ b/src/Common/Api/Dto/Book.php @@ -0,0 +1,35 @@ +id = $id; + $this->title = $title; + $this->description = $description; + $this->author = $author; + } + +} \ No newline at end of file diff --git a/src/Common/Api/Mapper/AuthorMapper.php b/src/Common/Api/Mapper/AuthorMapper.php new file mode 100644 index 0000000..a0b08e1 --- /dev/null +++ b/src/Common/Api/Mapper/AuthorMapper.php @@ -0,0 +1,22 @@ +getId(), + $entity->getName() + ); + } +} \ No newline at end of file diff --git a/src/Common/Api/Mapper/BookMapper.php b/src/Common/Api/Mapper/BookMapper.php new file mode 100644 index 0000000..faa9982 --- /dev/null +++ b/src/Common/Api/Mapper/BookMapper.php @@ -0,0 +1,33 @@ +authorRepository->findById($entity->getAuthorId()); + + return new Book( + $entity->getId(), + $entity->getTitle(), + $entity->getDescription(), + $author ? $this->authorMapper->map($author) : null + ); + } +} \ No newline at end of file diff --git a/src/Controller/Admin/.gitignore b/src/Controller/Admin/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Controller/Admin/AuthorsController.php b/src/Controller/Admin/AuthorsController.php new file mode 100644 index 0000000..0d038ec --- /dev/null +++ b/src/Controller/Admin/AuthorsController.php @@ -0,0 +1,87 @@ +authorRepository->findAll(); + $item = array_map([$this->mapper, 'map'], $authors); + return $this->render('authors/index.html.twig', [ + 'authors' => $item, + ]); + } + + #[Route('/new', name: 'authors_new', methods: ['GET', 'POST'])] + public function new(Request $request): Response + { + $author = new Author(); + $form = $this->createForm(AuthorType::class, $author); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->authorRepository->add($author); + $this->flusher->flush(); + + return $this->redirectToRoute('authors_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('authors/new.html.twig', [ + 'author' => $author, + 'form' => $form->createView(), + ]); + } + + #[Route('/{id}', name: 'authors_show', methods: ['GET'])] + public function show(Author $author): Response + { + return $this->render('authors/show.html.twig', [ + 'author' => $author, + ]); + } + + #[Route('/{id}/edit', name: 'authors_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, Author $author): Response + { + $form = $this->createForm(AuthorType::class, $author); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->flusher->flush(); + + return $this->redirectToRoute('authors_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('authors/edit.html.twig', [ + 'author' => $author, + 'form' => $form->createView(), + ]); + } + + #[Route('/{id}', name: 'authors_delete', methods: ['POST'])] + public function delete(Request $request, Author $author): Response + { + if ($this->isCsrfTokenValid('delete'.$author->getId(), $request->request->get('_token'))) { + $this->authorRepository->remove($author); + $this->flusher->flush(); + } + + return $this->redirectToRoute('authors_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/src/Controller/Admin/BooksController.php b/src/Controller/Admin/BooksController.php new file mode 100644 index 0000000..7448244 --- /dev/null +++ b/src/Controller/Admin/BooksController.php @@ -0,0 +1,87 @@ +bookRepository->findAll(); + $item = array_map([$this->mapper, 'map'], $books); + return $this->render('books/index.html.twig', [ + 'books' => $item, + ]); + } + + #[Route('/new', name: 'books_new', methods: ['GET', 'POST'])] + public function new(Request $request): Response + { + $book = new Book(); + $form = $this->createForm(BookType::class, $book); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->bookRepository->add($book); + $this->flusher->flush(); + + return $this->redirectToRoute('books_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('books/new.html.twig', [ + 'book' => $book, + 'form' => $form->createView(), + ]); + } + + #[Route('/{id}', name: 'books_show', methods: ['GET'])] + public function show(Book $book): Response + { + return $this->render('books/show.html.twig', [ + 'book' => $book, + ]); + } + + #[Route('/{id}/edit', name: 'books_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, Book $book): Response + { + $form = $this->createForm(BookType::class, $book); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->flusher->flush(); + + return $this->redirectToRoute('books_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('books/edit.html.twig', [ + 'book' => $book, + 'form' => $form->createView(), + ]); + } + + #[Route('/{id}', name: 'books_delete', methods: ['POST'])] + public function delete(Request $request, Book $book, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$book->getId(), $request->request->get('_token'))) { + $this->bookRepository->remove($book); + $this->flusher->flush(); + } + + return $this->redirectToRoute('books_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/src/Controller/Api/.gitignore b/src/Controller/Api/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Controller/Api/BookController.php b/src/Controller/Api/BookController.php new file mode 100644 index 0000000..13283dc --- /dev/null +++ b/src/Controller/Api/BookController.php @@ -0,0 +1,68 @@ +bookRepository->findAll(); + $item = array_map([$this->mapper, 'map'], $books); + return new JsonResponse($item, Response::HTTP_OK); + } + + #[Route('/{id}', name: 'singleBook', methods: ['GET'])] + public function singleBook(int $id): JsonResponse { + $book = $this->bookRepository->findById($id); + return new JsonResponse($this->mapper->map($book), Response::HTTP_OK); + } + + #[Route('/update/{id}', name: 'updateBook', methods: ['POST'])] + public function updateBook(Request $request, int $id): JsonResponse { + $requestSchema = $this->requestSchema->getRequestProperty(UpdateBookSchema::class, $request); + $book = $this->bookRepository->findById($id); + + $book->setTitle($requestSchema->title); + $book->setDescription($requestSchema->description); + $this->flusher->flush(); + + return new JsonResponse(['message' => 'success update book'], Response::HTTP_CREATED); + } + + /** + * @throws ORMException + */ + #[Route('/remove/{id}', name: 'removeBook', methods: ['DELETE'])] + public function removeBook(int $id): JsonResponse { + $book = $this->bookRepository->findById($id); + $this->bookRepository->remove($book); + $this->flusher->flush(); + + return new JsonResponse(['message' => 'success remove book'], Response::HTTP_CREATED); + } +} \ No newline at end of file diff --git a/src/Controller/Api/BookHeaderInterface.php b/src/Controller/Api/BookHeaderInterface.php new file mode 100644 index 0000000..65685f8 --- /dev/null +++ b/src/Controller/Api/BookHeaderInterface.php @@ -0,0 +1,14 @@ +name()); - $manager->persist($author); - for ($i = 0; $i < 5; $i++) { - $book = new Book($faker->name, $faker->title, $author); - $manager->persist($book); + + /** @var Author $authorOne */ + $authorOne = $this->getReference(AuthorsFixtures::REF); + + /** @var Author $authorTwo */ + $authorTwo = $this->getReference(AuthorRefFixture::REF); + + for ($i = 0; $i < 100000; $i++) { + $bookOne = new Book(); + $bookOne->setTitle($faker->realText(10)); + $bookOne->setDescription($faker->realText(20)); + $bookOne->setAuthorId($authorOne->getId()); + $manager->persist($bookOne); + + $bookTwo = new Book(); + $bookTwo->setTitle($faker->realText(10)); + $bookTwo->setDescription($faker->realText(20)); + $bookTwo->setAuthorId($authorTwo->getId()); + $manager->persist($bookTwo); } $manager->flush(); + $manager->clear(); + } + + public function getDependencies(): array + { + return [ + AuthorsFixtures::class, + AuthorRefFixture::class + ]; } } diff --git a/src/DataFixtures/AuthorRefFixture.php b/src/DataFixtures/AuthorRefFixture.php new file mode 100644 index 0000000..381173f --- /dev/null +++ b/src/DataFixtures/AuthorRefFixture.php @@ -0,0 +1,30 @@ +setName($faker->userName()); + $this->addReference(self::REF, $author); + $manager->persist($author); + $manager->flush(); + } +} \ No newline at end of file diff --git a/src/DataFixtures/AuthorsFixtures.php b/src/DataFixtures/AuthorsFixtures.php new file mode 100644 index 0000000..6e0378d --- /dev/null +++ b/src/DataFixtures/AuthorsFixtures.php @@ -0,0 +1,29 @@ +setName($faker->userName()); + $this->addReference(self::REF, $author); + $manager->persist($author); + $manager->flush(); + } +} \ No newline at end of file diff --git a/src/Entity/Author.php b/src/Entity/Author.php index 029876b..74566b3 100644 --- a/src/Entity/Author.php +++ b/src/Entity/Author.php @@ -23,26 +23,27 @@ class Author * @ORM\GeneratedValue * @ORM\Column(type="integer") */ - public int $id; + private int $id; /** - * @ORM\Column(type="string", length=65, nullable=false) + * @ORM\Column(type="string", length=55) */ - public string $name; + private string $name; - /** - * @ORM\OneToMany(targetEntity=Book::class,mappedBy="author",cascade={"all"}) - * @var Collection - */ - public Collection $books; + public function getId(): ?int + { + return $this->id; + } - /** - * @param string $name - */ - public function __construct(string $name) + public function getName(): ?string { - $this->name = $name; - $this->books = new ArrayCollection(); + return $this->name; } + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } } \ No newline at end of file diff --git a/src/Entity/Book.php b/src/Entity/Book.php index 551eee3..0690cf3 100644 --- a/src/Entity/Book.php +++ b/src/Entity/Book.php @@ -15,34 +15,61 @@ class Book * @ORM\GeneratedValue * @ORM\Column(type="integer") */ - public int $id; + private int $id; /** - * @ORM\Column(type="string", length=65, nullable=true) + * @ORM\Column(type="string", length=155) */ - public string $name; + private string $title; /** * @ORM\Column(type="string", length=255, nullable=true) */ - public string $description; + private ?string $description; /** - * @ORM\ManyToOne(targetEntity=Author::class) - * @var Author + * @ORM\Column(type="integer") */ - public Author $author; + private int $authorId; - /** - * @param string $name - * @param string $description - * @param Author $author - */ - public function __construct(string $name, string $description, Author $author) + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self { - $this->name = $name; $this->description = $description; - $this->author = $author; + + return $this; } + public function getAuthorId(): ?int + { + return $this->authorId; + } + + public function setAuthorId(int $authorId): self + { + $this->authorId = $authorId; + + return $this; + } } diff --git a/src/EventSubscriber/HeaderCheckerSubscriber.php b/src/EventSubscriber/HeaderCheckerSubscriber.php new file mode 100644 index 0000000..86f3673 --- /dev/null +++ b/src/EventSubscriber/HeaderCheckerSubscriber.php @@ -0,0 +1,47 @@ +getController(); + + // when a controller class defines multiple action methods, the controller + // is returned as [$controllerInstance, 'methodName'] + if (is_array($controller)) { + $controller = $controller[0]; + } + + if ($controller instanceof BookHeaderInterface) { + $header = $event->getRequest()->headers->has(self::HEADER); + if (!$header) throw new HeaderExistsException('Header X-API-User-Name is not found', Response::HTTP_FORBIDDEN); + + $admin = $event->getRequest()->headers->get(self::HEADER); + if ($admin !== 'admin') throw new HeaderExistsException('Header X-API-User-Name is not valid', Response::HTTP_FORBIDDEN); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER => 'onKernelController' + ]; + } +} \ No newline at end of file diff --git a/src/EventSubscriber/HeaderExistsException.php b/src/EventSubscriber/HeaderExistsException.php new file mode 100644 index 0000000..f056305 --- /dev/null +++ b/src/EventSubscriber/HeaderExistsException.php @@ -0,0 +1,25 @@ +code; + } + + public function getHeaders(): array + { + return []; + } +} \ No newline at end of file diff --git a/src/EventSubscriber/HeaderExistsExceptionListener.php b/src/EventSubscriber/HeaderExistsExceptionListener.php new file mode 100644 index 0000000..0f32b43 --- /dev/null +++ b/src/EventSubscriber/HeaderExistsExceptionListener.php @@ -0,0 +1,41 @@ +getThrowable(); + $message = sprintf( + json_encode('Error: %s with code: %s'), + $exception->getMessage(), + $exception->getCode() + ); + + // Customize your response object to display the exception details + $response = new Response(); + $response->setContent($message)->headers->set('Content-Type', 'application/json'); + + // HttpExceptionInterface is a special type of exception that + // holds status code and header details + if ($exception instanceof HttpExceptionInterface) { + $response->setStatusCode($exception->getStatusCode()); + } else { + $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR); + } + + // sends the modified response object to the event + $event->setResponse($response); + } +} \ No newline at end of file diff --git a/src/EventSubscriber/HeaderExistsMessageException.php b/src/EventSubscriber/HeaderExistsMessageException.php new file mode 100644 index 0000000..16635e9 --- /dev/null +++ b/src/EventSubscriber/HeaderExistsMessageException.php @@ -0,0 +1,14 @@ +add('name') + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Author::class, + ]); + } +} diff --git a/src/Form/BookType.php b/src/Form/BookType.php new file mode 100644 index 0000000..572f557 --- /dev/null +++ b/src/Form/BookType.php @@ -0,0 +1,27 @@ +add('title') + ->add('description') + ->add('authorId') + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Book::class, + ]); + } +} diff --git a/src/HTTP/Request/RequestSchema.php b/src/HTTP/Request/RequestSchema.php index 0a8c780..51d9102 100644 --- a/src/HTTP/Request/RequestSchema.php +++ b/src/HTTP/Request/RequestSchema.php @@ -35,10 +35,10 @@ public function __construct(RequestValidator $requestValidator) $this->requestValidator = $requestValidator; } - /*public function getRequestProperty(string $schema, Request $request): object + public function getRequestProperty(string $schema, Request $request): object { - $schema = $this->serializer->deserialize($request->getBody(), $schema, self::TYPE); + $schema = $this->serializer->deserialize($request->getContent(), $schema, self::TYPE); $this->requestValidator->validate($schema); return $schema; - }*/ + } } \ No newline at end of file diff --git a/src/Repository/AuthorRepository.php b/src/Repository/AuthorRepository.php index c0f417a..a93f441 100644 --- a/src/Repository/AuthorRepository.php +++ b/src/Repository/AuthorRepository.php @@ -10,6 +10,7 @@ use App\Entity\Author; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\ORMException; use Doctrine\Persistence\ManagerRegistry; /** @@ -24,4 +25,23 @@ public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Author::class); } + + /** + * @throws ORMException + */ + public function add(Author $author): void { + $this->_em->persist($author); + } + + public function findById(int $id): ?Author { + /** @var Author|null $book */ + return $this->find($id); + } + + /** + * @throws ORMException + */ + public function remove(Author $author): void { + $this->_em->remove($author); + } } \ No newline at end of file diff --git a/src/Repository/BookRepository.php b/src/Repository/BookRepository.php index 5576140..583cb17 100644 --- a/src/Repository/BookRepository.php +++ b/src/Repository/BookRepository.php @@ -4,7 +4,11 @@ use App\Entity\Book; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; +use Doctrine\ORM\ORMException; use Doctrine\Persistence\ManagerRegistry; +use function Doctrine\ORM\QueryBuilder; /** * @method Book|null find($id, $lockMode = null, $lockVersion = null) @@ -19,32 +23,34 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, Book::class); } - // /** - // * @return Book[] Returns an array of Book objects - // */ - /* - public function findByExampleField($value) - { - return $this->createQueryBuilder('b') - ->andWhere('b.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('b.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; + /** + * @throws ORMException + */ + public function add(Book $book): void { + $this->_em->persist($book); } - */ - /* - public function findOneBySomeField($value): ?Book - { - return $this->createQueryBuilder('b') - ->andWhere('b.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; + public function findById(int $id): ?Book { + /** @var Book|null $book */ + return $this->find($id); + } + + /** + * @throws ORMException + */ + public function remove(Book $book): void { + $this->_em->remove($book); + } + + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function countBook(int $authorId): int { + $qb = $this->createQueryBuilder('book'); + $qb->select('COUNT(book.authorId)') + ->andWhere($qb->expr()->eq('book.authorId', ':authorId')) + ->setParameter('authorId', $authorId); + return $qb->getQuery()->getSingleScalarResult(); } - */ } diff --git a/src/Service/Flusher.php b/src/Service/Flusher.php new file mode 100644 index 0000000..94522aa --- /dev/null +++ b/src/Service/Flusher.php @@ -0,0 +1,25 @@ +entityManager->flush(); + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 05fe0b4..2513921 100644 --- a/symfony.lock +++ b/symfony.lock @@ -142,6 +142,18 @@ "psr/log": { "version": "1.1.4" }, + "sensio/framework-extra-bundle": { + "version": "6.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.2", + "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" + }, + "files": [ + "./config/packages/sensio_framework_extra.yaml" + ] + }, "symfony/cache": { "version": "v5.2.8" }, @@ -199,6 +211,9 @@ "symfony/event-dispatcher-contracts": { "version": "v2.4.0" }, + "symfony/expression-language": { + "version": "v5.2.12" + }, "symfony/filesystem": { "version": "v5.2.7" }, @@ -217,6 +232,9 @@ ".env" ] }, + "symfony/form": { + "version": "v5.2.12" + }, "symfony/framework-bundle": { "version": "5.2", "recipe": { @@ -246,6 +264,9 @@ "symfony/http-kernel": { "version": "v5.2.8" }, + "symfony/intl": { + "version": "v5.2.12" + }, "symfony/maker-bundle": { "version": "1.0", "recipe": { @@ -273,12 +294,18 @@ "config/packages/test/monolog.yaml" ] }, + "symfony/options-resolver": { + "version": "v5.2.12" + }, "symfony/orm-pack": { "version": "v2.1.0" }, "symfony/polyfill-intl-grapheme": { "version": "v1.22.1" }, + "symfony/polyfill-intl-icu": { + "version": "v1.23.0" + }, "symfony/polyfill-intl-normalizer": { "version": "v1.22.1" }, diff --git a/templates/authors/_delete_form.html.twig b/templates/authors/_delete_form.html.twig new file mode 100644 index 0000000..68a3f2c --- /dev/null +++ b/templates/authors/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/authors/_form.html.twig b/templates/authors/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/authors/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/authors/edit.html.twig b/templates/authors/edit.html.twig new file mode 100644 index 0000000..17b23f8 --- /dev/null +++ b/templates/authors/edit.html.twig @@ -0,0 +1,13 @@ + + +Edit Authors + +{% block body %} +

Edit Authors

+ + {{ include('authors/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('authors/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/authors/index.html.twig b/templates/authors/index.html.twig new file mode 100644 index 0000000..8bf1ca7 --- /dev/null +++ b/templates/authors/index.html.twig @@ -0,0 +1,37 @@ + + +Authors index + +{% block body %} +

Authors index

+ + + + + + + + + + + + {% for author in authors %} + + + + + + + {% else %} + + + + {% endfor %} + +
IdNameCount bookactions
{{ author.id }}{{ author.name }}{{ author.countBook }} + show + edit +
no records found
+ + Create new +{% endblock %} diff --git a/templates/authors/new.html.twig b/templates/authors/new.html.twig new file mode 100644 index 0000000..bfc4cf0 --- /dev/null +++ b/templates/authors/new.html.twig @@ -0,0 +1,11 @@ + + +New Authors + +{% block body %} +

Create new Authors

+ + {{ include('authors/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/templates/authors/show.html.twig b/templates/authors/show.html.twig new file mode 100644 index 0000000..cd1f9fd --- /dev/null +++ b/templates/authors/show.html.twig @@ -0,0 +1,26 @@ + + +Authors + +{% block body %} +

Authors

+ + + + + + + + + + + + +
Id{{ author.id }}
Name{{ author.name }}
+ + back to list + + edit + + {{ include('authors/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/books/_delete_form.html.twig b/templates/books/_delete_form.html.twig new file mode 100644 index 0000000..b961d3e --- /dev/null +++ b/templates/books/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/books/_form.html.twig b/templates/books/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/books/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/books/edit.html.twig b/templates/books/edit.html.twig new file mode 100644 index 0000000..9b0e06e --- /dev/null +++ b/templates/books/edit.html.twig @@ -0,0 +1,13 @@ + + +Edit Books + +{% block body %} +

Edit Books

+ + {{ include('books/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('books/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/books/index.html.twig b/templates/books/index.html.twig new file mode 100644 index 0000000..5920d4d --- /dev/null +++ b/templates/books/index.html.twig @@ -0,0 +1,39 @@ + + +Books index + +{% block body %} +

Books index

+ + + + + + + + + + + + + {% for book in books %} + + + + + + + + {% else %} + + + + {% endfor %} + +
IdTitleDescriptionAuthor Nameactions
{{ book.id }}{{ book.title }}{{ book.description }}{{ book.author.name }} + show + edit +
no records found
+ + Create new +{% endblock %} diff --git a/templates/books/new.html.twig b/templates/books/new.html.twig new file mode 100644 index 0000000..35390c3 --- /dev/null +++ b/templates/books/new.html.twig @@ -0,0 +1,11 @@ + + +New Books + +{% block body %} +

Create new Books

+ + {{ include('books/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/templates/books/show.html.twig b/templates/books/show.html.twig new file mode 100644 index 0000000..0356ead --- /dev/null +++ b/templates/books/show.html.twig @@ -0,0 +1,34 @@ + + +Books + +{% block body %} +

Books

+ + + + + + + + + + + + + + + + + + + + +
Id{{ book.id }}
Title{{ book.title }}
Description{{ book.description }}
AuthorId{{ book.authorId }}
+ + back to list + + edit + + {{ include('books/_delete_form.html.twig') }} +{% endblock %}