├── .env ├── .gitignore ├── README.md ├── bin └── console ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── prod │ │ ├── doctrine.yaml │ │ └── routing.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── test │ │ ├── framework.yaml │ │ ├── twig.yaml │ │ ├── validator.yaml │ │ └── web_profiler.yaml │ ├── twig.yaml │ └── validator.yaml ├── routes.yaml ├── routes │ ├── annotations.yaml │ └── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml └── services.yaml ├── imagens ├── consulta-sql.png ├── debugbar.png ├── delete.png ├── folders.png ├── fomulario-starter.png ├── form-bulma.png ├── formulario-com-imagem.png ├── formulario-novo-bulma.png ├── home-page.png ├── listagem-com-links.png ├── login-basico.png ├── login.png ├── main-page.png ├── novo-post.png ├── php-page.png ├── post-com-categoria.png ├── post-com-imagem.png ├── post-criado.png ├── post.png ├── register.png ├── registration-page.png ├── sql-user.png ├── sql.png ├── symfony-page.png ├── tabela-posts.png ├── tela-com-botao.png ├── upload-feito.png ├── user-logged.png └── welcome.png ├── public ├── index.php └── uploads │ ├── 2638a90dc3f23427a95f9746dcecef00.jpeg │ ├── 5675847631b3fc0aa72a86fd98c3ff94.jpeg │ ├── 7e9298891f41b244d863f5b435406675.jpeg │ └── edc7906065dbc89980459f2e8588194b.jpeg ├── src ├── Controller │ ├── .gitignore │ ├── MainController.php │ ├── PostController.php │ ├── RegistrationController.php │ └── SecurityController.php ├── Entity │ ├── .gitignore │ ├── Category.php │ ├── Post.php │ └── User.php ├── Form │ └── PostType.php ├── Kernel.php ├── Migrations │ └── .gitignore ├── Repository │ ├── .gitignore │ ├── CategoryRepository.php │ ├── PostRepository.php │ └── UserRepository.php ├── Security │ └── CustomAuthenticator.php └── Service │ └── Uploader.php ├── symfony.lock ├── templates ├── base.html.twig ├── home │ ├── custom.html.twig │ └── index.html.twig ├── post │ ├── create.html.twig │ ├── index.html.twig │ └── show.html.twig ├── registration │ └── index.html.twig └── security │ └── login.html.twig └── tutorial ├── ARQUIVOS.md ├── COMPLETO.md ├── CONTROLLER.md ├── DEBUG.md ├── FLASH.md ├── FORMULARIO.md ├── INICIANDO.md ├── INSTALACAO.md ├── ORM.md ├── RELACOES-1.md ├── RELACOES-2.md ├── ROTAS.md ├── SEGURANCA.md ├── SERVICES.md ├── TEMPLATE.md ├── TOQUES.md └── VIEWS.md /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=dev 18 | APP_SECRET=f5d74f6af36725a1ddf448ec8640369a 19 | #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 20 | #TRUSTED_HOSTS='^(localhost|example\.com)$' 21 | ###< symfony/framework-bundle ### 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | .idea/* 11 | .idea 12 | ###< symfony/framework-bundle ### 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony 2 | ![](https://symfony.com/images/opengraph/symfony.png) 3 | 4 | ### O que é o Framework Symfony? 5 | Symfony é um framework - conjunto de bibliotecas e ferramentas - em PHP para a criação de aplicações de alta performance e de fácil manutenção. É altamente inspirado pelo projeto Spring da comunidade Java e nasceu inicialmente direcionado à produzir sistemas de qualidade para o mundo enterprise em PHP oferecendo soluções modulares com o máximo de reaproveitamento de código. 6 | 7 | ### Popularidade e Uso 8 | 9 | O Symfony é um framework modular, voltado ao público enterprise e muito eficiente para a criação de microserviços. Todas essas partes de sua natureza combianda torna muito dificil estimar de forma quantitativa quantas empresas usam Symfony, uma vez em que muitos projetos utilizam apenas algumas partes ou serviços e as companhias do ramo de software enterprise não costumam divulgar o que usam para construir seus produtos. 10 | 11 | Porém é facil de dizer que Symfony é um dos frameworks php mais populares devido ao fato da W3CTech medir o Symfony como o terceiro framework mais usado na internet, além disso foi o framework que [mais recebeu contribuições em 2019](https://symfony.com/blog/symfony-was-the-backend-framework-with-the-most-contributors-in-2019). 12 | 13 | ### Empresas que usam Symfony 14 | 15 | #### Spotify 16 | Segundo o site [EtonDigital](https://www.etondigital.com/popular-symfony-projects/) o Spotify usa o Symfony para construir o backend do site e isso foi confirmado por o ex-engenheiro [Mathias Petter Johansen](https://www.quora.com/On-what-language-is-Spotify-built) (porém, ele deixa claro que o Symfony não é usado na aplicação principal/player que tem o backend escrito em Clojure e Java). 17 | 18 | ### Dailymotion 19 | O Dailymotion é completamente construído usando Symfony. Isso inclusive faz parte dos [estudos de caso disponíveis](https://symfony.com/blog/dailymotion-powered-by-symfony) no site do framework. Segundo o Rank Global da Alexa o site é o #207 mais visitado no mundo e o quarto maior volume de mídias da internet. 20 | 21 | ### ( ͡° ͜ʖ ͡°) 22 | Ainda segundo o site [EtonDigital](https://www.etondigital.com/popular-symfony-projects/) e confirmado no Quora e no Fórum Laracasts por funcionários da empresa o **PornHub** é construído com Symfony e o que levou a empresa a migrar o código PHP para Symfony foi justamente o grande número de requisições por dia que exigiram uma arquitetura de sistemas mais robusta para aguentar o tráfego na casa dos bilhões de requisições. 23 | 24 | ## Tutorial 25 | Para mostrar e exemplificar o funcionamento do framework foi elaborado um pequeno tutorial de uma _toy application_ de um microblog de imagens. 26 | 27 | ![main page](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/main-page.png) 28 | 29 | ### Índice 30 | 31 | 1. [Requisitos Minimos/Instalação](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/INSTALACAO.md) 32 | 2. [Iniciando um novo projeto em Symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/INICIANDO.md) 33 | 3. [Ferramenta make e o primeiro Controller](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/CONTROLLER.md) 34 | 4. [Rotas](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/ROTAS.md) 35 | 5. [Views ](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/VIEWS.md) 36 | 6. [Template](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/TEMPLATE.md) 37 | 7. [ORM](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/ORM.md) 38 | 8. [Flash Messages](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/FLASH.md) 39 | 9. [Formulários](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/FORMULARIO.md) 40 | 10. [Debug](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/DEBUG.md) 41 | 11. [Segurança/Autenticação](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/SEGURANCA.md) 42 | 12. [Relação entre tabelas, parte 1](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/RELACOES-1.md) 43 | 13. [Upload de arquivos](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/ARQUIVOS.md) 44 | 14. [Relação entre tabelas, parte 2](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/RELACOES-2.md) 45 | 15. [Services](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/SERVICES.md) 46 | 16. [Toques Finais](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/tutorial/TOQUES.md) 47 | 48 |
49 | 50 | __Obs__: Por motivos de diferença de leitura de fim de linha do editor de código para o de markdown alguns códigos ficarão mal identados, para resolver isso use a extensão beautify do VS Code ou CTRL+ALT+L no PHP Storm. 51 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 24 | } 25 | 26 | if ($input->hasParameterOption('--no-debug', true)) { 27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 28 | } 29 | 30 | require dirname(__DIR__).'/config/bootstrap.php'; 31 | 32 | if ($_SERVER['APP_DEBUG']) { 33 | umask(0000); 34 | 35 | if (class_exists(Debug::class)) { 36 | Debug::enable(); 37 | } 38 | } 39 | 40 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 41 | $application = new Application($kernel); 42 | $application->run($input); 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "license": "proprietary", 4 | "require": { 5 | "php": "^7.2.5", 6 | "ext-ctype": "*", 7 | "ext-iconv": "*", 8 | "doctrine/annotations": "^1.8", 9 | "symfony/console": "5.0.*", 10 | "symfony/dotenv": "5.0.*", 11 | "symfony/flex": "^1.3.1", 12 | "symfony/form": "5.0.*", 13 | "symfony/framework-bundle": "5.0.*", 14 | "symfony/maker-bundle": "^1.14", 15 | "symfony/orm-pack": "^1.0", 16 | "symfony/security-bundle": "5.0.*", 17 | "symfony/twig-bundle": "5.0.*", 18 | "symfony/validator": "5.0.*", 19 | "symfony/web-profiler-bundle": "5.0.*", 20 | "symfony/yaml": "5.0.*" 21 | }, 22 | "require-dev": { 23 | }, 24 | "config": { 25 | "preferred-install": { 26 | "*": "dist" 27 | }, 28 | "sort-packages": true 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "App\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "App\\Tests\\": "tests/" 38 | } 39 | }, 40 | "replace": { 41 | "paragonie/random_compat": "2.*", 42 | "symfony/polyfill-ctype": "*", 43 | "symfony/polyfill-iconv": "*", 44 | "symfony/polyfill-php72": "*", 45 | "symfony/polyfill-php71": "*", 46 | "symfony/polyfill-php70": "*", 47 | "symfony/polyfill-php56": "*" 48 | }, 49 | "scripts": { 50 | "auto-scripts": { 51 | "cache:clear": "symfony-cmd", 52 | "assets:install %PUBLIC_DIR%": "symfony-cmd" 53 | }, 54 | "post-install-cmd": [ 55 | "@auto-scripts" 56 | ], 57 | "post-update-cmd": [ 58 | "@auto-scripts" 59 | ] 60 | }, 61 | "conflict": { 62 | "symfony/symfony": "*" 63 | }, 64 | "extra": { 65 | "symfony": { 66 | "allow-contrib": false, 67 | "require": "5.0.*" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /config/bootstrap.php: -------------------------------------------------------------------------------- 1 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) { 10 | foreach ($env as $k => $v) { 11 | $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); 12 | } 13 | } elseif (!class_exists(Dotenv::class)) { 14 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 15 | } else { 16 | // load all the .env files 17 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 18 | } 19 | 20 | $_SERVER += $_ENV; 21 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 22 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 23 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 24 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 6 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 7 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 8 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 9 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 11 | ]; 12 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(resolve:DATABASE_URL)%' 4 | 5 | # IMPORTANT: You MUST configure your server version, 6 | # either here or in the DATABASE_URL env var (see .env file) 7 | #server_version: '5.7' 8 | orm: 9 | auto_generate_proxy_classes: true 10 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 11 | auto_mapping: true 12 | mappings: 13 | App: 14 | is_bundle: false 15 | type: annotation 16 | dir: '%kernel.project_dir%/src/Entity' 17 | prefix: 'App\Entity' 18 | alias: App 19 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | dir_name: '%kernel.project_dir%/src/Migrations' 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | namespace: DoctrineMigrations 6 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #csrf_protection: true 4 | #http_method_override: true 5 | 6 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 7 | # Remove or comment this section to explicitly disable session support. 8 | session: 9 | handler_id: null 10 | cookie_secure: auto 11 | cookie_samesite: lax 12 | 13 | #esi: true 14 | #fragments: true 15 | php_errors: 16 | log: true 17 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | -------------------------------------------------------------------------------- /config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | encoders: 3 | App\Entity\User: 4 | algorithm: auto 5 | 6 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 7 | providers: 8 | # used to reload user from session & other features (e.g. switch_user) 9 | app_user_provider: 10 | entity: 11 | class: App\Entity\User 12 | property: username 13 | firewalls: 14 | dev: 15 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 16 | security: false 17 | main: 18 | anonymous: lazy 19 | provider: app_user_provider 20 | guard: 21 | authenticators: 22 | - App\Security\CustomAuthenticator 23 | logout: 24 | path: app_logout 25 | # where to redirect after logout 26 | # target: app_any_route 27 | 28 | # activate different ways to authenticate 29 | # https://symfony.com/doc/current/security.html#firewalls-authentication 30 | 31 | # https://symfony.com/doc/current/security/impersonating_user.html 32 | # switch_user: true 33 | 34 | # Easy way to control access for large sections of your site 35 | # Note: Only the *first* access control that matches will be used 36 | access_control: 37 | - { path: ^/main, roles: IS_AUTHENTICATED_ANONYMOUSLY } 38 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 39 | - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY } 40 | - { path: ^/, roles: ROLE_USER } 41 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | 5 | kernel: 6 | resource: ../../src/Kernel.php 7 | type: annotation 8 | -------------------------------------------------------------------------------- /config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | uploads_dir: '%kernel.project_dir%/public/uploads/' 8 | 9 | services: 10 | # default configuration for services in *this* file 11 | _defaults: 12 | autowire: true # Automatically injects dependencies in your services. 13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 14 | 15 | # makes classes in src/ available to be used as services 16 | # this creates a service per class whose id is the fully-qualified class name 17 | App\: 18 | resource: '../src/*' 19 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' 20 | 21 | # controllers are imported separately to make sure services can be injected 22 | # as action arguments even if you don't extend any base controller class 23 | App\Controller\: 24 | resource: '../src/Controller' 25 | tags: ['controller.service_arguments'] 26 | 27 | # add more service definitions when explicit configuration is needed 28 | # please note that last definitions always *replace* previous ones 29 | -------------------------------------------------------------------------------- /imagens/consulta-sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/consulta-sql.png -------------------------------------------------------------------------------- /imagens/debugbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/debugbar.png -------------------------------------------------------------------------------- /imagens/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/delete.png -------------------------------------------------------------------------------- /imagens/folders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/folders.png -------------------------------------------------------------------------------- /imagens/fomulario-starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/fomulario-starter.png -------------------------------------------------------------------------------- /imagens/form-bulma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/form-bulma.png -------------------------------------------------------------------------------- /imagens/formulario-com-imagem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/formulario-com-imagem.png -------------------------------------------------------------------------------- /imagens/formulario-novo-bulma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/formulario-novo-bulma.png -------------------------------------------------------------------------------- /imagens/home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/home-page.png -------------------------------------------------------------------------------- /imagens/listagem-com-links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/listagem-com-links.png -------------------------------------------------------------------------------- /imagens/login-basico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/login-basico.png -------------------------------------------------------------------------------- /imagens/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/login.png -------------------------------------------------------------------------------- /imagens/main-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/main-page.png -------------------------------------------------------------------------------- /imagens/novo-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/novo-post.png -------------------------------------------------------------------------------- /imagens/php-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/php-page.png -------------------------------------------------------------------------------- /imagens/post-com-categoria.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/post-com-categoria.png -------------------------------------------------------------------------------- /imagens/post-com-imagem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/post-com-imagem.png -------------------------------------------------------------------------------- /imagens/post-criado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/post-criado.png -------------------------------------------------------------------------------- /imagens/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/post.png -------------------------------------------------------------------------------- /imagens/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/register.png -------------------------------------------------------------------------------- /imagens/registration-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/registration-page.png -------------------------------------------------------------------------------- /imagens/sql-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/sql-user.png -------------------------------------------------------------------------------- /imagens/sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/sql.png -------------------------------------------------------------------------------- /imagens/symfony-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/symfony-page.png -------------------------------------------------------------------------------- /imagens/tabela-posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/tabela-posts.png -------------------------------------------------------------------------------- /imagens/tela-com-botao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/tela-com-botao.png -------------------------------------------------------------------------------- /imagens/upload-feito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/upload-feito.png -------------------------------------------------------------------------------- /imagens/user-logged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/user-logged.png -------------------------------------------------------------------------------- /imagens/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/imagens/welcome.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /public/uploads/2638a90dc3f23427a95f9746dcecef00.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/public/uploads/2638a90dc3f23427a95f9746dcecef00.jpeg -------------------------------------------------------------------------------- /public/uploads/5675847631b3fc0aa72a86fd98c3ff94.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/public/uploads/5675847631b3fc0aa72a86fd98c3ff94.jpeg -------------------------------------------------------------------------------- /public/uploads/7e9298891f41b244d863f5b435406675.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/public/uploads/7e9298891f41b244d863f5b435406675.jpeg -------------------------------------------------------------------------------- /public/uploads/edc7906065dbc89980459f2e8588194b.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/public/uploads/edc7906065dbc89980459f2e8588194b.jpeg -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/src/Controller/.gitignore -------------------------------------------------------------------------------- /src/Controller/MainController.php: -------------------------------------------------------------------------------- 1 | findAll(); 19 | return $this->render('home/index.html.twig', compact('posts')); 20 | } 21 | 22 | /** 23 | * @Route("/custom/{name?}", name="custom") 24 | */ 25 | public function custom(Request $request) 26 | { 27 | $name = $request->get('name'); 28 | return $this->render('home/custom.html.twig', compact('name')); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Controller/PostController.php: -------------------------------------------------------------------------------- 1 | findAll(); 25 | return $this->render('post/index.html.twig', [ 26 | 'controller_name' => 'PostController', 27 | 'posts' => $posts 28 | ]); 29 | } 30 | 31 | /** 32 | * @Route("/post/create", name="post.create") 33 | * @param Request $request 34 | * @return Response 35 | */ 36 | public function create(Request $request, Uploader $uploader) 37 | { 38 | // cria um novo post com titulo 39 | $post = new Post(); 40 | 41 | // cria um novo formulário usando PostType de modelo que após preenchido 42 | // passa as informações para o objeto $post 43 | $form = $this->createForm(PostType::class, $post); 44 | 45 | $form->handleRequest($request); 46 | 47 | if($form->isSubmitted() && $form->isValid()) { 48 | $file = $request->files->get('post')['image']; 49 | // entity manager 50 | $em = $this->getDoctrine()->getManager(); 51 | $em->persist($post); 52 | if ($file) { 53 | $filename = $uploader->uploadFile($file); 54 | 55 | // adiciona o caminho ao post para que seja persistido 56 | $post->setImage($filename); 57 | } 58 | $em->flush(); 59 | 60 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 61 | 62 | return $this->redirect($this->generateUrl('post')); 63 | } 64 | 65 | // return a response 66 | return $this->render('post/create.html.twig', [ 67 | 'form' => $form->createView() 68 | ]); 69 | } 70 | 71 | /** 72 | * @Route("/post/show/{id}", name="post.show") 73 | * @param Request $request 74 | * @return Response 75 | */ 76 | public function show(PostRepository $repository, $id) 77 | { 78 | $post = $repository->find($id); 79 | return $this->render('post/show.html.twig', [ 'post' => $post]); 80 | } 81 | 82 | /** 83 | * @Route("/post/delete/{id}", name="post.delete") 84 | * @param Request $request 85 | * @return Response 86 | */ 87 | public function remove(PostRepository $repository, $id) 88 | { 89 | $post = $repository->find($id); 90 | 91 | // entity manager 92 | $em = $this->getDoctrine()->getManager(); 93 | $em->remove($post); // remove 94 | $em->flush(); 95 | 96 | $this->addFlash('success', 'O Post foi deletado'); 97 | 98 | return $this->redirect($this->generateUrl('post')); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Controller/RegistrationController.php: -------------------------------------------------------------------------------- 1 | createFormBuilder() 26 | ->add('username', null, [ 27 | 'attr' => [ 28 | 'class' => 'input is-primary' 29 | ], 30 | 'row_attr' => [ 31 | 'class' => 'field' 32 | ], 33 | 'label_attr' => [ 34 | 'class' => 'label' 35 | ] 36 | ]) 37 | ->add('password', RepeatedType::class, [ 38 | 'type' => PasswordType::class, 39 | 'required' => true, 40 | 'first_options' => [ 41 | 'label' => 'Password', 42 | 'attr' => [ 43 | 'class' => 'input is-primary' 44 | ], 45 | 'row_attr' => [ 46 | 'class' => 'field' 47 | ], 48 | 'label_attr' => [ 49 | 'class' => 'label' 50 | ] 51 | ], 52 | 'second_options' => [ 53 | 'label' => 'Repeat Password', 54 | 'attr' => [ 55 | 'class' => 'input is-primary' 56 | ], 57 | 'row_attr' => [ 58 | 'class' => 'field' 59 | ], 60 | 'label_attr' => [ 61 | 'class' => 'label' 62 | ] 63 | ], 64 | 65 | ]) 66 | ->add('save', SubmitType::class, [ 67 | 'attr' => [ 68 | 'class' => 'button is-primary' 69 | ] 70 | ]) 71 | ->getForm() 72 | ; 73 | 74 | $form->handleRequest($request); 75 | 76 | if($form->isSubmitted() && $form->isValid()) { 77 | // pega os dados do formulário 78 | $data = $form->getData(); 79 | 80 | // cria o objeto User a ser persistido 81 | $user = new User(); 82 | $user->setUsername($data['username']); 83 | $user->setPassword($encoder->encodePassword($user, $data['password'])); 84 | 85 | // persist the data 86 | $em = $this->getDoctrine()->getManager(); 87 | $em->persist($user); 88 | $em->flush(); 89 | 90 | return $this->redirect($this->generateUrl('app_login')); 91 | } 92 | return $this->render('registration/index.html.twig', [ 93 | 'controller_name' => 'RegistrationController', 94 | 'form' => $form->createView() 95 | ]); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Controller/SecurityController.php: -------------------------------------------------------------------------------- 1 | getUser()) { 18 | // return $this->redirectToRoute('target_path'); 19 | // } 20 | 21 | // get the login error if there is one 22 | $error = $authenticationUtils->getLastAuthenticationError(); 23 | // last username entered by the user 24 | $lastUsername = $authenticationUtils->getLastUsername(); 25 | 26 | return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); 27 | } 28 | 29 | /** 30 | * @Route("/logout", name="app_logout") 31 | */ 32 | public function logout() 33 | { 34 | throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Entity/Category.php: -------------------------------------------------------------------------------- 1 | post = new ArrayCollection(); 34 | } 35 | 36 | public function getId(): ?int 37 | { 38 | return $this->id; 39 | } 40 | 41 | public function getName(): ?string 42 | { 43 | return $this->name; 44 | } 45 | 46 | public function setName(string $name): self 47 | { 48 | $this->name = $name; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * @return Collection|Post[] 55 | */ 56 | public function getPost(): Collection 57 | { 58 | return $this->post; 59 | } 60 | 61 | public function addPost(Post $post): self 62 | { 63 | if (!$this->post->contains($post)) { 64 | $this->post[] = $post; 65 | $post->setCategory($this); 66 | } 67 | 68 | return $this; 69 | } 70 | 71 | public function removePost(Post $post): self 72 | { 73 | if ($this->post->contains($post)) { 74 | $this->post->removeElement($post); 75 | // set the owning side to null (unless already changed) 76 | if ($post->getCategory() === $this) { 77 | $post->setCategory(null); 78 | } 79 | } 80 | 81 | return $this; 82 | } 83 | 84 | public function __toString() 85 | { 86 | return $this->getName(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Entity/Post.php: -------------------------------------------------------------------------------- 1 | id; 38 | } 39 | 40 | public function getTitle(): ?string 41 | { 42 | return $this->title; 43 | } 44 | 45 | public function setTitle(string $title): self 46 | { 47 | $this->title = $title; 48 | 49 | return $this; 50 | } 51 | 52 | public function getImage(): ?string 53 | { 54 | return $this->image; 55 | } 56 | 57 | public function setImage(string $image): self 58 | { 59 | $this->image = $image; 60 | 61 | return $this; 62 | } 63 | 64 | public function getCategory(): ?Category 65 | { 66 | return $this->category; 67 | } 68 | 69 | public function setCategory(?Category $category): self 70 | { 71 | $this->category = $category; 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Entity/User.php: -------------------------------------------------------------------------------- 1 | id; 39 | } 40 | 41 | /** 42 | * A visual identifier that represents this user. 43 | * 44 | * @see UserInterface 45 | */ 46 | public function getUsername(): string 47 | { 48 | return (string) $this->username; 49 | } 50 | 51 | public function setUsername(string $username): self 52 | { 53 | $this->username = $username; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @see UserInterface 60 | */ 61 | public function getRoles(): array 62 | { 63 | $roles = $this->roles; 64 | // guarantee every user at least has ROLE_USER 65 | $roles[] = 'ROLE_USER'; 66 | 67 | return array_unique($roles); 68 | } 69 | 70 | public function setRoles(array $roles): self 71 | { 72 | $this->roles = $roles; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @see UserInterface 79 | */ 80 | public function getPassword(): string 81 | { 82 | return (string) $this->password; 83 | } 84 | 85 | public function setPassword(string $password): self 86 | { 87 | $this->password = $password; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @see UserInterface 94 | */ 95 | public function getSalt() 96 | { 97 | // not needed when using the "bcrypt" algorithm in security.yaml 98 | } 99 | 100 | /** 101 | * @see UserInterface 102 | */ 103 | public function eraseCredentials() 104 | { 105 | // If you store any temporary, sensitive data on the user, clear it here 106 | // $this->plainPassword = null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Form/PostType.php: -------------------------------------------------------------------------------- 1 | add('title', null, [ 20 | 'attr' => [ 21 | 'class' => 'input is-primary' 22 | ], 23 | 'row_attr' => [ 24 | 'class' => 'field' 25 | ], 26 | 'label_attr' => [ 27 | 'class' => 'label' 28 | ] 29 | ]) 30 | ->add('image', FileType::class, [ 31 | 'mapped' => false, 32 | 'row_attr' => [ 33 | 'class' => 'field' 34 | ], 35 | 'label_attr' => [ 36 | 'class' => 'label' 37 | ] 38 | ]) 39 | ->add('category', EntityType::class, [ 40 | 'class' => Category::class, 41 | 'label' => false, 42 | 'row_attr' => [ 43 | 'class' => 'field select is-rounded is-primary' 44 | ], 45 | 46 | ]) 47 | ->add('save', SubmitType::class, [ 48 | 'attr' => [ 49 | 'class' => 'button is-primary' 50 | ] 51 | ]) 52 | ; 53 | } 54 | 55 | public function configureOptions(OptionsResolver $resolver) 56 | { 57 | $resolver->setDefaults([ 58 | 'data_class' => Post::class, 59 | ]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | getProjectDir().'/config/bundles.php'; 21 | foreach ($contents as $class => $envs) { 22 | if ($envs[$this->environment] ?? $envs['all'] ?? false) { 23 | yield new $class(); 24 | } 25 | } 26 | } 27 | 28 | public function getProjectDir(): string 29 | { 30 | return \dirname(__DIR__); 31 | } 32 | 33 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void 34 | { 35 | $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); 36 | $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug); 37 | $container->setParameter('container.dumper.inline_factories', true); 38 | $confDir = $this->getProjectDir().'/config'; 39 | 40 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); 41 | $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob'); 42 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); 43 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); 44 | } 45 | 46 | protected function configureRoutes(RouteCollectionBuilder $routes): void 47 | { 48 | $confDir = $this->getProjectDir().'/config'; 49 | 50 | $routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob'); 51 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); 52 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/src/Migrations/.gitignore -------------------------------------------------------------------------------- /src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Camilotk/symfony-tutorial/a8608505fd2f358b3dfa446c6cc137c40223fd6b/src/Repository/.gitignore -------------------------------------------------------------------------------- /src/Repository/CategoryRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 29 | ->andWhere('c.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('c.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Category 41 | { 42 | return $this->createQueryBuilder('c') 43 | ->andWhere('c.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/PostRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('p') 29 | ->andWhere('p.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('p.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Post 41 | { 42 | return $this->createQueryBuilder('p') 43 | ->andWhere('p.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | setPassword($newEncodedPassword); 35 | $this->_em->persist($user); 36 | $this->_em->flush(); 37 | } 38 | 39 | // /** 40 | // * @return User[] Returns an array of User objects 41 | // */ 42 | /* 43 | public function findByExampleField($value) 44 | { 45 | return $this->createQueryBuilder('u') 46 | ->andWhere('u.exampleField = :val') 47 | ->setParameter('val', $value) 48 | ->orderBy('u.id', 'ASC') 49 | ->setMaxResults(10) 50 | ->getQuery() 51 | ->getResult() 52 | ; 53 | } 54 | */ 55 | 56 | /* 57 | public function findOneBySomeField($value): ?User 58 | { 59 | return $this->createQueryBuilder('u') 60 | ->andWhere('u.exampleField = :val') 61 | ->setParameter('val', $value) 62 | ->getQuery() 63 | ->getOneOrNullResult() 64 | ; 65 | } 66 | */ 67 | } 68 | -------------------------------------------------------------------------------- /src/Security/CustomAuthenticator.php: -------------------------------------------------------------------------------- 1 | entityManager = $entityManager; 35 | $this->urlGenerator = $urlGenerator; 36 | $this->csrfTokenManager = $csrfTokenManager; 37 | $this->passwordEncoder = $passwordEncoder; 38 | } 39 | 40 | public function supports(Request $request) 41 | { 42 | return 'app_login' === $request->attributes->get('_route') 43 | && $request->isMethod('POST'); 44 | } 45 | 46 | public function getCredentials(Request $request) 47 | { 48 | $credentials = [ 49 | 'username' => $request->request->get('username'), 50 | 'password' => $request->request->get('password'), 51 | 'csrf_token' => $request->request->get('_csrf_token'), 52 | ]; 53 | $request->getSession()->set( 54 | Security::LAST_USERNAME, 55 | $credentials['username'] 56 | ); 57 | 58 | return $credentials; 59 | } 60 | 61 | public function getUser($credentials, UserProviderInterface $userProvider) 62 | { 63 | $token = new CsrfToken('authenticate', $credentials['csrf_token']); 64 | if (!$this->csrfTokenManager->isTokenValid($token)) { 65 | throw new InvalidCsrfTokenException(); 66 | } 67 | 68 | $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]); 69 | 70 | if (!$user) { 71 | // fail authentication with a custom error 72 | throw new CustomUserMessageAuthenticationException('Username could not be found.'); 73 | } 74 | 75 | return $user; 76 | } 77 | 78 | public function checkCredentials($credentials, UserInterface $user) 79 | { 80 | return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); 81 | } 82 | 83 | /** 84 | * Used to upgrade (rehash) the user's password automatically over time. 85 | */ 86 | public function getPassword($credentials): ?string 87 | { 88 | return $credentials['password']; 89 | } 90 | 91 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) 92 | { 93 | if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { 94 | return new RedirectResponse($targetPath); 95 | } 96 | 97 | return new RedirectResponse($this->urlGenerator->generate('post')); 98 | } 99 | 100 | protected function getLoginUrl() 101 | { 102 | return $this->urlGenerator->generate('app_login'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Service/Uploader.php: -------------------------------------------------------------------------------- 1 | container = $container; 20 | } 21 | public function uploadFile(UploadedFile $file) 22 | { 23 | // cria um nome único para cada imagem 24 | // isso evita conflitos caso 2 tenham mesmo nome 25 | $filename = md5(uniqid()) . '.' . $file->guessClientExtension(); 26 | 27 | // move as imagens, pega o valor de uploads_dir em services.yaml 28 | // e renomeia o arquivo com o valor em $filename 29 | $file->move($this->container->getParameter('uploads_dir'), $filename); 30 | 31 | return $filename; 32 | } 33 | } -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "doctrine/annotations": { 3 | "version": "1.0", 4 | "recipe": { 5 | "repo": "github.com/symfony/recipes", 6 | "branch": "master", 7 | "version": "1.0", 8 | "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" 9 | }, 10 | "files": [ 11 | "config/routes/annotations.yaml" 12 | ] 13 | }, 14 | "doctrine/cache": { 15 | "version": "1.10.0" 16 | }, 17 | "doctrine/collections": { 18 | "version": "1.6.4" 19 | }, 20 | "doctrine/common": { 21 | "version": "2.12.0" 22 | }, 23 | "doctrine/dbal": { 24 | "version": "v2.10.1" 25 | }, 26 | "doctrine/doctrine-bundle": { 27 | "version": "2.0", 28 | "recipe": { 29 | "repo": "github.com/symfony/recipes", 30 | "branch": "master", 31 | "version": "2.0", 32 | "ref": "a9f2463b9f73efe74482f831f03a204a41328555" 33 | }, 34 | "files": [ 35 | "config/packages/doctrine.yaml", 36 | "config/packages/prod/doctrine.yaml", 37 | "src/Entity/.gitignore", 38 | "src/Repository/.gitignore" 39 | ] 40 | }, 41 | "doctrine/doctrine-migrations-bundle": { 42 | "version": "1.2", 43 | "recipe": { 44 | "repo": "github.com/symfony/recipes", 45 | "branch": "master", 46 | "version": "1.2", 47 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1" 48 | }, 49 | "files": [ 50 | "config/packages/doctrine_migrations.yaml", 51 | "src/Migrations/.gitignore" 52 | ] 53 | }, 54 | "doctrine/event-manager": { 55 | "version": "1.1.0" 56 | }, 57 | "doctrine/inflector": { 58 | "version": "1.3.1" 59 | }, 60 | "doctrine/instantiator": { 61 | "version": "1.3.0" 62 | }, 63 | "doctrine/lexer": { 64 | "version": "1.2.0" 65 | }, 66 | "doctrine/migrations": { 67 | "version": "2.2.1" 68 | }, 69 | "doctrine/orm": { 70 | "version": "v2.7.1" 71 | }, 72 | "doctrine/persistence": { 73 | "version": "1.3.6" 74 | }, 75 | "doctrine/reflection": { 76 | "version": "v1.1.0" 77 | }, 78 | "jdorn/sql-formatter": { 79 | "version": "v1.2.17" 80 | }, 81 | "nikic/php-parser": { 82 | "version": "v4.3.0" 83 | }, 84 | "ocramius/package-versions": { 85 | "version": "1.5.1" 86 | }, 87 | "ocramius/proxy-manager": { 88 | "version": "2.2.3" 89 | }, 90 | "php": { 91 | "version": "7.3" 92 | }, 93 | "psr/cache": { 94 | "version": "1.0.1" 95 | }, 96 | "psr/container": { 97 | "version": "1.0.0" 98 | }, 99 | "psr/event-dispatcher": { 100 | "version": "1.0.0" 101 | }, 102 | "psr/log": { 103 | "version": "1.1.2" 104 | }, 105 | "symfony/cache": { 106 | "version": "v5.0.5" 107 | }, 108 | "symfony/cache-contracts": { 109 | "version": "v2.0.1" 110 | }, 111 | "symfony/config": { 112 | "version": "v5.0.5" 113 | }, 114 | "symfony/console": { 115 | "version": "4.4", 116 | "recipe": { 117 | "repo": "github.com/symfony/recipes", 118 | "branch": "master", 119 | "version": "4.4", 120 | "ref": "ea8c0eda34fda57e7d5cd8cbd889e2a387e3472c" 121 | }, 122 | "files": [ 123 | "bin/console", 124 | "config/bootstrap.php" 125 | ] 126 | }, 127 | "symfony/dependency-injection": { 128 | "version": "v5.0.5" 129 | }, 130 | "symfony/doctrine-bridge": { 131 | "version": "v5.0.5" 132 | }, 133 | "symfony/dotenv": { 134 | "version": "v5.0.5" 135 | }, 136 | "symfony/error-handler": { 137 | "version": "v5.0.5" 138 | }, 139 | "symfony/event-dispatcher": { 140 | "version": "v5.0.5" 141 | }, 142 | "symfony/event-dispatcher-contracts": { 143 | "version": "v2.0.1" 144 | }, 145 | "symfony/filesystem": { 146 | "version": "v5.0.5" 147 | }, 148 | "symfony/finder": { 149 | "version": "v5.0.5" 150 | }, 151 | "symfony/flex": { 152 | "version": "1.0", 153 | "recipe": { 154 | "repo": "github.com/symfony/recipes", 155 | "branch": "master", 156 | "version": "1.0", 157 | "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" 158 | }, 159 | "files": [ 160 | ".env" 161 | ] 162 | }, 163 | "symfony/form": { 164 | "version": "v5.0.5" 165 | }, 166 | "symfony/framework-bundle": { 167 | "version": "4.4", 168 | "recipe": { 169 | "repo": "github.com/symfony/recipes", 170 | "branch": "master", 171 | "version": "4.4", 172 | "ref": "23ecaccc551fe2f74baf613811ae529eb07762fa" 173 | }, 174 | "files": [ 175 | "config/bootstrap.php", 176 | "config/packages/cache.yaml", 177 | "config/packages/framework.yaml", 178 | "config/packages/test/framework.yaml", 179 | "config/routes/dev/framework.yaml", 180 | "config/services.yaml", 181 | "public/index.php", 182 | "src/Controller/.gitignore", 183 | "src/Kernel.php" 184 | ] 185 | }, 186 | "symfony/http-foundation": { 187 | "version": "v5.0.5" 188 | }, 189 | "symfony/http-kernel": { 190 | "version": "v5.0.5" 191 | }, 192 | "symfony/inflector": { 193 | "version": "v5.0.5" 194 | }, 195 | "symfony/intl": { 196 | "version": "v5.0.5" 197 | }, 198 | "symfony/maker-bundle": { 199 | "version": "1.0", 200 | "recipe": { 201 | "repo": "github.com/symfony/recipes", 202 | "branch": "master", 203 | "version": "1.0", 204 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 205 | } 206 | }, 207 | "symfony/mime": { 208 | "version": "v5.0.5" 209 | }, 210 | "symfony/options-resolver": { 211 | "version": "v5.0.5" 212 | }, 213 | "symfony/orm-pack": { 214 | "version": "v1.0.8" 215 | }, 216 | "symfony/polyfill-intl-icu": { 217 | "version": "v1.14.0" 218 | }, 219 | "symfony/polyfill-intl-idn": { 220 | "version": "v1.14.0" 221 | }, 222 | "symfony/polyfill-mbstring": { 223 | "version": "v1.14.0" 224 | }, 225 | "symfony/polyfill-php73": { 226 | "version": "v1.14.0" 227 | }, 228 | "symfony/property-access": { 229 | "version": "v5.0.5" 230 | }, 231 | "symfony/routing": { 232 | "version": "4.2", 233 | "recipe": { 234 | "repo": "github.com/symfony/recipes", 235 | "branch": "master", 236 | "version": "4.2", 237 | "ref": "683dcb08707ba8d41b7e34adb0344bfd68d248a7" 238 | }, 239 | "files": [ 240 | "config/packages/prod/routing.yaml", 241 | "config/packages/routing.yaml", 242 | "config/routes.yaml" 243 | ] 244 | }, 245 | "symfony/security-bundle": { 246 | "version": "4.4", 247 | "recipe": { 248 | "repo": "github.com/symfony/recipes", 249 | "branch": "master", 250 | "version": "4.4", 251 | "ref": "7b4408dc203049666fe23fabed23cbadc6d8440f" 252 | }, 253 | "files": [ 254 | "config/packages/security.yaml" 255 | ] 256 | }, 257 | "symfony/security-core": { 258 | "version": "v5.0.5" 259 | }, 260 | "symfony/security-csrf": { 261 | "version": "v5.0.5" 262 | }, 263 | "symfony/security-guard": { 264 | "version": "v5.0.5" 265 | }, 266 | "symfony/security-http": { 267 | "version": "v5.0.5" 268 | }, 269 | "symfony/service-contracts": { 270 | "version": "v2.0.1" 271 | }, 272 | "symfony/stopwatch": { 273 | "version": "v5.0.5" 274 | }, 275 | "symfony/translation-contracts": { 276 | "version": "v2.0.1" 277 | }, 278 | "symfony/twig-bridge": { 279 | "version": "v5.0.5" 280 | }, 281 | "symfony/twig-bundle": { 282 | "version": "5.0", 283 | "recipe": { 284 | "repo": "github.com/symfony/recipes", 285 | "branch": "master", 286 | "version": "5.0", 287 | "ref": "fab9149bbaa4d5eca054ed93f9e1b66cc500895d" 288 | }, 289 | "files": [ 290 | "config/packages/test/twig.yaml", 291 | "config/packages/twig.yaml", 292 | "templates/base.html.twig" 293 | ] 294 | }, 295 | "symfony/validator": { 296 | "version": "4.3", 297 | "recipe": { 298 | "repo": "github.com/symfony/recipes", 299 | "branch": "master", 300 | "version": "4.3", 301 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 302 | }, 303 | "files": [ 304 | "config/packages/test/validator.yaml", 305 | "config/packages/validator.yaml" 306 | ] 307 | }, 308 | "symfony/var-dumper": { 309 | "version": "v5.0.5" 310 | }, 311 | "symfony/var-exporter": { 312 | "version": "v5.0.5" 313 | }, 314 | "symfony/web-profiler-bundle": { 315 | "version": "3.3", 316 | "recipe": { 317 | "repo": "github.com/symfony/recipes", 318 | "branch": "master", 319 | "version": "3.3", 320 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" 321 | }, 322 | "files": [ 323 | "config/packages/dev/web_profiler.yaml", 324 | "config/packages/test/web_profiler.yaml", 325 | "config/routes/dev/web_profiler.yaml" 326 | ] 327 | }, 328 | "symfony/yaml": { 329 | "version": "v5.0.5" 330 | }, 331 | "twig/twig": { 332 | "version": "v3.0.3" 333 | }, 334 | "zendframework/zend-code": { 335 | "version": "3.4.1" 336 | }, 337 | "zendframework/zend-eventmanager": { 338 | "version": "3.2.1" 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | 7 | {% block stylesheets %}{% endblock %} 8 | 9 | 10 | 57 |
58 |
59 | {% block body %}{% endblock %} 60 |
61 |
62 | {% block javascripts %}{% endblock %} 63 | 64 | 65 | -------------------------------------------------------------------------------- /templates/home/custom.html.twig: -------------------------------------------------------------------------------- 1 | {% extends('base.html.twig') %} 2 | {% block title %} Meu primeiro Twig! {% endblock %} 3 | {% block body %}

Hello {{ name }}!

{% endblock %} -------------------------------------------------------------------------------- /templates/home/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends('base.html.twig') %} 2 | {% block title %} Home {% endblock %} 3 | {% block body %} 4 |
5 |

Meu Blog

6 |

Microblog com minhas imagens e fotos preferidas

7 |
8 | 9 |
10 | {% for post in posts %} 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 | {{post.category}} 20 | 21 |
22 |
23 | 26 |
27 |
28 | {% endfor %} 29 |
30 | {% endblock %} -------------------------------------------------------------------------------- /templates/post/create.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %} New Post {% endblock %} 4 | 5 | {% block body %} 6 |
7 | {{ form(form) }} 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /templates/post/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Listagem de posts{% endblock %} 4 | 5 | {% block body %} 6 | {% for message in app.flashes('success') %} 7 |
8 |
9 | {{ message }} 10 |
11 |
12 | {% endfor %} 13 |

Post List

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for post in posts %} 31 | 32 | 33 | 38 | 41 | 42 | {% endfor %} 43 | 44 |
IDTitleActions
IDTitleActions
{{ post.id }} 34 | 35 | {{ post.title }} 36 | 37 | 39 | Delete 40 |
45 |
46 | 47 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /templates/post/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %} {{ post.title }}{% endblock %} 4 | 5 | {% block body %} 6 |

ID: {{ post.id }} Titulo: {{ post.title }}

7 | {{ post.category }} 8 |
9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /templates/registration/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %} Register {% endblock %} 4 | 5 | {% block body %} 6 | {{ form(form) }} 7 | {% endblock %} -------------------------------------------------------------------------------- /templates/security/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Log in!{% endblock %} 4 | 5 | {% block body %} 6 |
7 | {% if error %} 8 |
{{ error.messageKey|trans(error.messageData, 'security') }}
9 | {% endif %} 10 | 11 | {% if app.user %} 12 |
13 | You are logged in as {{ app.user.username }}, Logout 14 |
15 | {% endif %} 16 | 17 |

Please sign in

18 |
19 | 20 |
21 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 | 35 | 36 |
37 |
38 | 41 |
42 |
43 | 44 |
45 |
46 | 49 |
50 |
51 |
52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /tutorial/ARQUIVOS.md: -------------------------------------------------------------------------------- 1 | ### Upload de Arquivos 2 | Agora que temos nossa entidade que consegue armazenar o caminho das imagens precisamos fazer o upload de arquivos em si. Para isso precisamos adicionar um input file no nosso formulário de novo post. Vamos mudar o $builder para: 3 | ```php 4 | $builder 5 | ->add('title', null, [ 6 | 'attr' => [ 7 | 'class' => 'input' 8 | ], 9 | 'row_attr' => [ 10 | 'class' => 'field' 11 | ], 12 | 'label_attr' => [ 13 | 'class' => 'label' 14 | ] 15 | ]) ->add('image', FileType::class, [ 16 | 'mapped' => false, 17 | 'row_attr' => [ 18 | 'class' => 'field' 19 | ], 20 | 'label_attr' => [ 21 | 'class' => 'label' 22 | ] 23 | ]) ->add('save', SubmitType::class, [ 24 | 'attr' => [ 25 | 'class' => 'button is-primary' 26 | ] 27 | ]); 28 | ``` 29 | Note que adicionamos 'mapped' como falso, isso siginifica que esse campo não será automaticamente persistido e terá que ser tratado no controller, a documentação do symfony diz que isso é necessário caso quisermos salvar o arquivo em alguma pasta que não a temporária. 30 | 31 | Atualmente nossa tela de novo post deve estar assim: 32 | ![formulario com o campo de imagem](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/formulario-com-imagem.png) 33 | 34 | Ok, como disse antes, agora teremos que tratar o arquivo no controller. Para isso teremos que alterar a função create em PostController 35 | ```php 36 | /** 37 | * @Route("/post/create", name="post.create") 38 | * @param Request $request 39 | * @return Response 40 | */ 41 | public function create(Request $request) 42 | { 43 | // cria um novo post com titulo 44 | $post = new Post(); 45 | 46 | // cria um novo formulário usando PostType de modelo que após preenchido 47 | // passa as informações para o objeto $post 48 | $form = $this->createForm(PostType::class, $post); 49 | 50 | $form->handleRequest($request); 51 | 52 | if($form->isSubmitted() && $form->isValid()) { 53 | $file = $request->files->get('post')['image']; 54 | // entity manager 55 | $em = $this->getDoctrine()->getManager(); 56 | $em->persist($post); 57 | if ($file) { 58 | // cria um nome único para cada imagem 59 | // isso evita conflitos caso 2 tenham mesmo nome 60 | $filename = md5(uniqid()) . '.' . $file->guessClientExtension(); 61 | 62 | // move as imagens, pega o valor de uploads_dir em services.yaml 63 | // e renomeia o arquivo com o valor em $filename 64 | $file->move($this->getParameter('uploads_dir'), $filename); 65 | 66 | // adiciona o caminho ao post para que seja persistido 67 | $post->setImage($filename); 68 | } 69 | $em->flush(); 70 | 71 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 72 | 73 | return $this->redirect($this->generateUrl('post')); 74 | } 75 | 76 | // return a response 77 | return $this->render('post/create.html.twig', [ 78 | 'form' => $form->createView() 79 | ]); 80 | } 81 | ``` 82 | Agora precisamos alterar config/services.yaml para que tenhamos a chave que pegamos com getParameter(), para isso vamos adicionar um valor em parameters apontando o local da pasta em que vamos salvar os arquivos. 83 | ```yaml 84 | parameters: 85 | uploads_dir: '%kernel.project_dir%/public/uploads/' 86 | ``` 87 | Agora precisamos migrar nossos novos campos para o BD para que possa recebe-los: 88 | ``` php bin/console doctrine:schema:update --force``` 89 | Após migrarmos os dados podemos enviar nosso arquivo que será salvo dentro de public/uploads/ e persistido. 90 | ![imagem salva](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/upload-feito.png) 91 | Estamos quase prontos, para finalizarmos o upload de arquivos devemos também exibir a imagem salva quando acessarmos um de nossos posts. Para isso temos que ir no nosso template /post/show.html.twig e adicionar a imagem: 92 | ```twig 93 | {% extends 'base.html.twig' %} 94 | 95 | {% block title %} {{ post.title }}{% endblock %} 96 | 97 | {% block body %} 98 |

ID: {{ post.id }} Titulo: {{ post.title }}

99 | 100 | {% endblock %} 101 | ``` 102 | **Obs:** O caminho '/sftutorial/public/uploads/' é para quem está usando apache sem virtualhost, para quem está usando htacess, emdded server do php ou symfony ou com o virtualhost configurado o caminho é '/uploads/' apenas. 103 | 104 | Se tudo deu certo você deve estar vendo seus novos posts com imagens, parabéns! 105 | ![post com imagem](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-com-imagem.png) 106 | -------------------------------------------------------------------------------- /tutorial/COMPLETO.md: -------------------------------------------------------------------------------- 1 | # Symfony 2 | ![](https://symfony.com/images/opengraph/symfony.png) 3 | 4 | ### O que é o Framework Symfony? 5 | Symfony é um framework - conjunto de bibliotecas e ferramentas - em PHP para a criação de aplicações de alta performance e de fácil manutenção. É altamente inspirado pelo projeto Spring da comunidade Java e nasceu inicialmente direcionado à produzir sistemas de qualidade para o mundo enterprise em PHP oferecendo soluções modulares com o máximo de reaproveitamento de código. 6 | 7 | ### Popularidade e Uso 8 | 9 | O Symfony é um framework modular, voltado ao público enterprise e muito eficiente para a criação de microserviços. Todas essas partes de sua natureza combianda torna muito dificil estimar de forma quantitativa quantas empresas usam Symfony, uma vez em que muitos projetos utilizam apenas algumas partes ou serviços e as companhias do ramo de software enterprise não costumam divulgar o que usam para construir seus produtos. 10 | 11 | Porém é facil de dizer que Symfony é um dos frameworks php mais populares devido ao fato da W3CTech medir o Symfony como o terceiro framework mais usado na internet, além disso foi o framework que [mais recebeu contribuições em 2019](https://symfony.com/blog/symfony-was-the-backend-framework-with-the-most-contributors-in-2019). 12 | 13 | ### Empresas que usam Symfony 14 | 15 | #### Spotify 16 | Segundo o site [EtonDigital](https://www.etondigital.com/popular-symfony-projects/) o Spotify usa o Symfony para construir o backend do site e isso foi confirmado por o ex-engenheiro [Mathias Petter Johansen](https://www.quora.com/On-what-language-is-Spotify-built) (porém, ele deixa claro que o Symfony não é usado na aplicação principal/player que tem o backend escrito em Clojure e Java). 17 | 18 | ### Dailymotion 19 | O Dailymotion é completamente construído usando Symfony. Isso inclusive faz parte dos [estudos de caso disponíveis](https://symfony.com/blog/dailymotion-powered-by-symfony) no site do framework. Segundo o Rank Global da Alexa o site é o #207 mais visitado no mundo e o quarto maior volume de mídias da internet. 20 | 21 | ### ( ͡° ͜ʖ ͡°) 22 | Ainda segundo o site [EtonDigital](https://www.etondigital.com/popular-symfony-projects/) e confirmado no Quora e no Fórum Laracasts por funcionários da empresa o **PornHub** é construído com Symfony e o que levou a empresa a migrar o código PHP para Symfony foi justamente o grande número de requisições por dia que exigiram uma arquitetura de sistemas mais robusta para aguentar o tráfego na casa dos bilhões de requisições. 23 | 24 | ## Instalação 25 | 26 | ### Requisitos Mínimos 27 | - Ter o PHP 7.2.5 or maior instalado e as seguintes extensões instaladas e habilitadas no arquivo php.ini: [Ctype](https://www.php.net/book.ctype), [iconv](https://www.php.net/book.iconv), [JSON](https://www.php.net/book.json), [PCRE](https://www.php.net/book.pcre), [Session](https://www.php.net/book.session), [SimpleXML](https://www.php.net/book.simplexml), and [Tokenizer](https://www.php.net/book.tokenizer); 28 | - Ter o Apache e um banco de dados devidamente instalados e configurados; 29 | - Ter o [Composer](https://getcomposer.org/download/), o package manager de php instalado e configurado nas variáveis de ambiente do sistema; 30 | - Instalar a ferramenta [Symfony](https://symfony.com/download), que adiciona diversas ferramentas para desenvolvimento com Symfony. 31 | 32 | Caso o desenvolvedor tenha dúvidas se seu ambiente já atende a todos esses requisitos basta usar o seguinte comando após instalar a ferramenta [Symfony](https://symfony.com/download): 33 | ```bash 34 | $ symfony check:requirements 35 | ``` 36 | ### Iniciando um novo projeto no Symfony 37 | 1. Navegue no terminal até a pasta do Apache 38 | ``` bash 39 | $ cd /etc/var/www/ 40 | ``` 41 | 2. Use a ferramenta de CLI do Symfony para criar um novo projeto: 42 | ```bash 43 | $ symfony new 44 | ``` 45 | 3. Acesse a URL http://localhost/nome_do_projeto/public/ e caso veja a página inicial do Symfony: _parabéns seu projeto foi iniciado e está funcionando_! 46 | 47 | ![Página inicial do Symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/welcome.png) 48 | 49 | 4. Agora é necessário importar/abrir o código que foi gerado dentro da pasta que contêm o seu projeto e como somos todos grandinhos e escolhemos nossas ferramentas deixo isso com você. 50 | ### Ferramenta make e o primeiro Controller 51 | 1. O Symfony é modular e minimalista isso significa que ele vem com quase nada e que precisamos baixar e instalar os pacotes que o framework disponibiliza conforme à necessidade - isso faz parte de sua filosofia que visa ser leve -, a primeira ferramenta que precisamos instalar é a make ela será responsável por gerar a maior parte do código repetitivo para nós, para isso usaremos o composer: 52 | ```composer require`make``` 53 | 2. Antes de conseguirmos criar nosso primeiro controller é necessário instalar o pacote annotations do Symfony que irá adicionar anotações como usamos em Java só que no PHP, para isso use o composer: 54 | ```composer require doctrine/annotations ``` 55 | 3. Após o make e módulo annotations ser instalado podemos usá-lo para gerar nosso primeiro controlador, para isso basta usar o comando no terminal: 56 | ```php bin/console make:controller MainController``` 57 | E isso irá gerar o arquivo **src/Controller/MainController.php** com o seguinte conteúdo: 58 | ```php 59 | json([ 74 | 'message' => 'Welcome to your new controller!', 75 | 'path' => 'src/Controller/MainController.php', 76 | ]); 77 | } 78 | } 79 | ``` 80 | ### Rotas 81 | Existem quatro formas de trabalhar com rotas no Symfony e fica à decisão do programador qual escolher: 82 | 1. **Annotations**: Similar ao Spring Boot/.NET, o roteamento é feito através de uma anotação sobre o método/classe. Como podemos ver no código gerado pelo comando make a classe já veio anotada com a rota que chama o método index() e seu nome. Anotações é o meio padrão de trabalho do Symfony, essa pode ser uma opção bem popular para desenvolvedores que venham desses frameworks, além de adaptar-se melhor ao restante do Workflow do projeto; 83 | 2. **yaml**: Arquivos YAML são arquivos chave-valor assim como o JSON, esse tipo de arquivo é muito utilizado na configuração do Symfony, umas das configurações disponíveis é o arquivo routes.yaml onde podem ser definidas todas as rotas da aplicação, por padrão, esse arquivo vem inteiramente comentado e caso deseje usar YAML para configurar as rotas é só apagar as annotations da classe gerada, descomentar o arquivo route.yaml e preencher o arquivo com o nome da rota como chave e um array - que YAML é declarado por identação - com dois elementos chave-valor o index com o caminho de valor e path que diz qual classe e método será chamado: 84 | ```yaml 85 | main: 86 | path: /main 87 | controller: App\Controller\MainController::index 88 | ``` 89 | 3. **XML**: Similar à outros frameworks enterprise como JSF, para usar basta renomear o arquivo routes.yaml para routes.xml e configurar as chaves e valores com a mesma estrutura esperada no YAML, tendo path e controller dentro do nome da rota. Essa pode ser uma opção interessante ao público enterprise que está migrando de JSF/.NET ou tenham ferramentas que integram melhor com XML como o Eclipse. 90 | ```xml 91 | 92 | 93 | 97 | 98 | 100 | 101 | 102 | ``` 103 | 4. **PHP**: Similar a outros frameworks PHP populares como Codeigniter e Laravel também é possível definir rotas utilizando PHP. Para isso basta renomear o arquivo routes.yaml para routes.php e utilizar um código similar a este: 104 | ```php 105 | use App\Controller\BlogController; 106 | use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; 107 | 108 | return function (RoutingConfigurator $routes) { 109 | $routes->add('main', '/main') 110 | ->controller([MainController::class, 'index']); 111 | }; 112 | ``` 113 | Após configurar as rotas da forma que você achar melhor basta acessar o link http://localhost/sftutorial/public/index.php/main e ver o retorno do método na tela. 114 | 115 | **Obs**: É necessário usar /index.php/main pois o Apache precisará pegar as rotas do arquivo index.php, para ter urls bacanas é necessário configurar o Virtua Hosts no Apache, aqui tem alguns tutoriais de como fazer isso no [Windows](https://www.raphaelfiga.com/desenvolvimento/instalando-configurando-virtualhost-apache-windows/), [Ubuntu](https://www.digitalocean.com/community/tutorials/como-configurar-apache-virtual-hosts-no-ubuntu-14-04-lts-pt) ou no [XAMPP](https://blog.mxcursos.com/criar-virtual-host-com-xampp/). *Lembre-se* que não basta apontar para a pasta do projeto tem que apontar em /public/index.php para funcionar. Caso não queira fazer isso tudo bem bata continuar usando [http://localhost/\/public/index.php/\](#) que está tudo bem. 116 | 117 | ### Recebendo valores através das rotas 118 | Para ilustar como poderiamos receber um slug, id ou nome pela URL vamos criar um novo método chamado custom e anotá-lo conforme. Note que no final da rota adicionamos "{name?}" isso é porque o valor entre chaves vai guardar o que foi passado aqui como valor da propriedade 'name' e vamos colocar '?' para indicar que esse valor é opcional e não irá gerar erro caso falte. Esse método recebe um objeto Request que contêm todas as informações vindas da requisição do usuário e retorna um objeto Response que contêm um HTML que será renderizado no navegador do usuário. 119 | ```php 120 | /** 121 | * @Route("/custom/{name?}", name="custom") 122 | */ 123 | public function custom(Request $request) 124 | { 125 | $name = $request->get('name'); 126 | return new Response('

Olá ' . $name. '!

'); 127 | } 128 | ``` 129 | ### Views 130 | Para renderizar as views o Symfony utiliza o template engine [Twig](https://twig.symfony.com/) que ajuda a separar a lógica da apresentação no projeto. O Twig irá renderizar o código php dentro de {{ }} para html muito similar à quando usamos , mas com diversas vantagens, como pré-compilação, cache e escaping. Antes de mais nada precisamos instalar o Twig no projeto, para isso usamos o composer: ```composer require symfony/twig-bundle```. 131 | 132 | Após termos instalado o Twig devemos criar uma pasta com o nome 'templates' dentro da **raíz do nosso projeto**, essa pasta irá conter todos os arquivos de view que criarmos. Agora dentro dessa pasta vamos criar uma outra pasta chamada 'home' que vai conter todos os nossos templates relacionados com a página 'home' e dentro um arquivo chamado custom.html.twig. 133 | 134 | ![Estrutura de pastas](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/folders.png) 135 | 136 | Devemos refatorar nosso código custom mudando o retorno que agora não vai ser mais o retorno, mas a função render() herdada de AbstractController que recebe o nome ou caminho do arquivo que será renderizado de dentro da pasta 'templates'. Além disso, precisamos passar a todas as variáveis que a View precisa dentro de um array em que a chave será o nome da variável na view e o valor a variável no controller. 137 | ```php 138 | /** 139 | * @Route("/custom/{name?}", name="custom") 140 | */public function custom(Request $request) 141 | { 142 | $name = $request->get('name'); 143 | return $this->render('home/custom.html.twig', ['name' => $name]); 144 | } 145 | ``` 146 | Ok, agora vamos editar nossa view para exibir os dados como antes, dentro de templates/home/custom.html.twig coloque o conteúdo: 147 | ```html 148 |

Hello {{ name }}!

149 | ``` 150 | Agora acesse a página passando diferentes nomes: 151 | - [http://localhost/\/public/index.php/custom/Symfony](#) 152 | - [http://localhost/\/public/index.php/custom/PHP](#) 153 | - [http://localhost/\/public/index.php/custom/IFRS](#) 154 | 155 | ### Criando o template da página 156 | Digamos que quiséssemos adicionar o Bulma CSS CDN no projeto e que todas as páginas exibissem uma navbar. Para isso teríamos que criar um template que diversas páginas usariam, o que reduz drasticamente a quantidade de código que temos que escrever além de facilitar a manutenção do front-end. Dependendo a IDE que você escolheu pode ser que ela tenha criado um arquivo dentro da pasta templates chamado base.html.twig, caso não tenha crie. Iremos criar um template padrão e adicionar o link do CDN no head, o que deve ficar assim: 157 | 158 | ```twig 159 | 160 | 161 | 162 | 163 | {% block title %}Welcome!{% endblock %} 164 | 165 | {% block stylesheets %}{% endblock %} 166 | 167 | 168 | 191 |
192 |
193 | {% block body %}{% endblock %} 194 |
195 |
196 | {% block javascripts %}{% endblock %} 197 | 198 | 199 | ``` 200 | Você deve ter notado os blocos {% %}, então, eles são as partes dinâmicas do nosso template, onde o conteúdo de cada página será inserido. No momento o que nos importa é o **body**. Para dizermos qual template nossa página irá usar passamos a função **extends(** *String arquivo_template* **)** e em seguida as tags block com o conteúdo que queremos inserir em cada bloco. 201 | ```twig 202 | {% extends('base.html.twig') %} 203 | {% block title %} Meu primeiro Twig! {% endblock %} 204 | {% block body %}

Hello {{ name }}!

{% endblock %} 205 | ``` 206 | Para que possamos entender a diferença entre a navegação de páginas vamos alterar nosso método index() na classe **MainController** para: 207 | ```php 208 | /** 209 | * @Route("/main", name="main") 210 | */public function index() 211 | { 212 | return $this->render('home/index.html.twig'); 213 | } 214 | ``` 215 | E dentro de templates/home/ vamos criar um novo arquivo chamado de index.html.twig com o seguinte conteúdo: 216 | ```php 217 | {% extends('base.html.twig') %} 218 | {% block title %} Home {% endblock %} 219 | {% block body %}

Home

{% endblock %} 220 | ``` 221 | O resultado será isso: 222 | **Home Page**: 223 | 224 | ![Página Home](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/home-page.png) 225 | 226 | **Greet Symfonry**: 227 | 228 | ![Página Greet Symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/symfony-page.png) 229 | 230 | **Greet PHP**: 231 | 232 | ![Página Greet PHP](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/php-page.png) 233 | 234 | Eu sei, isso deve estar parecendo muita coisa. E é. Mas vá com calma, aproveite para analisar o código do template e ver o que está acontecendo apesar de parecer dificil não é. Aproveite para tentar fazer alterações e ver o que acontece, por ex: Criar um link para Greet \ certamente ficará mais claro como as coisas funcionam. 235 | ### ORM 236 | O Symfony usa por padrão o **Doctrine** - que é um projeto separado - como ORM padrão. Como os demais componentes temos que primeiro instalar o ORM para que seja possível utilizá-lo. Para isso utilizaremos o composer: 237 | ```composer require`orm``` 238 | Após instalado irá aparecer a seguinte mensagem: 239 | ``` 240 | * Modify your DATABASE_URL config in .env 241 | * Configure the driver (mysql) and server_version (5.7) in config/packages/doctrine.yaml 242 | ``` 243 | Esse será nosso próximo passo. Se abrirmos a pasta config/packages vamos notar que teremos 2 arquivos novos **doctrine.yaml** e **doctrine_migrations.yaml**. Da mesma forma verá que no arquivo .env será inserido a linha: 244 | ``` 245 | DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7 246 | ``` 247 | Que nada mais é que nossa connection string com o Banco de Dados. Acima haverá um comentário explicando como configurar a string para outros Bancos de Dados além do MySQL. Para que possamos conectar nosso projeto com o banco precisaremos substituir os seguintes valores na URL: 248 | * db_user com o nome do usuário MySQL 249 | * db_password com a senha desse usuário 250 | * 127.0.0.1:3306 com endereço e porta que o Banco está rodando, para localhost não precisa mudar nada aqui. 251 | * db_name pelo nome do banco que será conectado 252 | 253 | Além de instalar o ORM e adicionar as configurações para nós, quando instalamos o Doctrine também instalamos diversos comandos CLI que vão nos ajudar a administrar o Banco de Dados, em seu terminal, digite: 254 | ``` php bin/console doctrine:database:create``` 255 | Vamos notar que nosso banco de dados ainda está vazio, no Doctrine cada tabela será um objeto do nosso sistema, conhecido como entidade, para criarmos nossa primeira entidade devemos usar o comando: 256 | ``` php bin/console make:entity``` 257 | Após entrarmos com esse comando o terminal irá fazer algumas perguntas sobre a entidade que vamos criar e vamos entrar com as seguintes respostas: 258 | - Class name of the entity to create or update (e.g. FierceElephant): 259 | \> **Post** 260 | - New property name (press to stop adding fields): 261 | \> **title** 262 | - Field type (enter ? to see all types) [string]: 263 | \> [Enter] 264 | - Field length [255]: 265 | \> [Enter] 266 | - Can this field be null in the database (nullable) (yes/no) [no]: 267 | \> [Enter] 268 | - Add another property? Enter the property name (or press to stop adding fields): 269 | \> [Enter] 270 | 271 | Pronto! Temos nossa primeira entidade que será Post com o atributo do tipo String title. Dentro da pasta src/Entity encontraremos Post.php que é nossa entidade. Você poderá notar que a Classe está cheia de annotations, elas servem para configurar como esses dados serão transformados em tabela no Banco de Dados (para quem já usou Java é assim que o JPA/Hibernate trabalham também). Para fazermos nossa primeira tabela devemos novamente usar o console do Symfony com o comando: 272 | ``` php bin/console doctrine:schema:update --force ``` 273 | Peraí, como assim --force? Sim, caso tentássemos executar esse comando sem a flag --force o Doctrine nos avisaria que em produção deveriamos usar migrations (que não irei tratar aqui, mas a documentação do framework é bem completa sobre isso) e que apenas deveriamos usar schema:update caso estivermos em ambiente de desenvolvimento e cientes do que fazemos (nosso caso) e que caso estejamos cientes, devemos usar --force. 274 | 275 | ![tabela criada](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-criado.png) 276 | 277 | Legal, agora temos a tabela post como podemos ver no print a abaixo: 278 | Mas como posso criar Posts? Bem, para continuar nossa explicação primeiro precisaremos criar um controller para Post, para isso usamos o comando make: 279 | ``` php bin/console make:controller PostController ``` 280 | Em src/Controller/PostController.php vamos criar um novo método: 281 | ```php 282 | /** 283 | * @Route("/post/create", name="post.create") 284 | * @param Request $request 285 | * @return Response 286 | */public function create(Request $request) 287 | { 288 | // cria um novo post com titulo 289 | $post = new Post(); 290 | $post->setTitle('Post Title'); 291 | 292 | // entity manager 293 | $em = $this->getDoctrine()->getManager(); 294 | $em->persist($post); // salva o Objeto Post na tabela post 295 | $em->flush(); 296 | 297 | // return a response 298 | return new Response('O seu post foi criado.'); 299 | } 300 | ``` 301 | Todas as informações no nosso BD serão objetos, por isso, devemos trabalhar instanciando as entidades e colocando em seus atributos as informações que queremos salvar, assim como fizemos com o objeto Post. Porém, nossas entidades não tem nenhum método que faça a persistência em si, para isso utilizamos o objeto EntityManager do Symfony, não devemos instancia-lo diretamente, mas utilizar o método getDoctrine()->getManager() que nosso controlador herda de AbstractController que é um Factory para esse objeto. Tendo esse objeto instanciado então podemos usar a função persist( _objeto_ ) do EntityManage para persistir os dados do nosso objeto e flush() para encerrar/fechar a conexão com o BD. **obs:** Caso a conexão não ser fechada as Queries não serão executadas e por tanto não haverá os dados no BD. E ao acessar http://localhost/sftutorial/public/index.php/post/create veremos ``` O seu post foi criado. ``` e se tentarmos consultar nosso BD veremos que nossa informação estará lá: 302 | 303 | ![consulta a tabela posts](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/consulta-sql.png) 304 | 305 | Agora que inserimos nosso primeiro Post como fazemos para buscar a informação no Banco? Para isso vamos alterar o método index() automaticamente criado pelo CLI para listar todos os posts alterando-o para: 306 | ```php 307 | /** 308 | * @Route("/post", name="post") 309 | * @param PostRepository $repository 310 | * @return Response 311 | */public function index(PostRepository $repository) 312 | { 313 | $posts = $repository->findAll(); 314 | return $this->render('post/index.html.twig', [ 315 | 'controller_name' => 'PostController', 316 | 'posts' => $posts 317 | ]); 318 | } 319 | ``` 320 | Como podemos notar usamos um objeto PostRepository, esse objeto é criado junto com a Entidade Post e fica em src/Repository/PostRepository.php, essa classe possui vários métodos que podemos usar para conseguir buscar nossas informações no BD como: 321 | 322 | - findAll(): Que retorna um array com todos os dados da tabela/entidade. 323 | - find(): Que retorna um objeto pelo id. 324 | - findBy(): Que retorna um array de objetos selecionados por determinada característica. 325 | - findOneBy(): Que retorna um objeto selecionado por determinada característica. 326 | Caso você esteja usando uma IDE que tenha um autocomplete minimamente decente ele irá mostrar esses e outros métodos do repositório com suas devidas assinaturas caso não estja, devia estar. Como vimos, depois de pegar todos os nossos posts estamos passando ele para a view index.html.twig dentro da pasta templates/posts/, essa view é automaticamente criada pelo comando make quando criamos um controller após termos instalado o twig no projeto, vamos alterar essa view para: 327 | ```twig 328 | {% extends 'base.html.twig' %} 329 | 330 | {% block title %}Listagem de posts{% endblock %} 331 | 332 | {% block body %} 333 |

Lista de Posts

334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | {% for post in posts %} 349 | 350 | 351 | 352 | 353 | {% endfor %} 354 | 355 |
IDTitle
IDTitle
{{ post.id }}{{ post.title }}
356 | {% endblock %} 357 | ``` 358 | Para repertimos a criação de posts podemos dar refresh na página /post/create que chama o método create() do nosso PostController e então acessarmos http://localhost/sftutorial/public/index.php/post onde veremos: 359 | 360 | ![listagem de posts](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/tabela-posts.png) 361 | Certo, agora queremos exibir uma página com cada post, como fariamos isso? Vamos lá, primeiro devemos voltar à PostController e adicionar o método show que irá exibir cada página trazendo a informação do post: 362 | ```php 363 | /** 364 | * @Route("/post/show/{id}", name="post.show") 365 | * @param Request $request 366 | * @return Response 367 | */ 368 | public function show(PostRepository $repository, $id) 369 | { 370 | $post = $repository->find($id); 371 | return $this->render('post/show.html.twig', [ 'post' => $post]); 372 | } 373 | ``` 374 | E então devemos alterar o conteúdo do for em posts/index.html.twig para que tenha os links individuais de cada post: 375 | ```twig 376 | {% for post in posts %} 377 | 378 | {{ post.id }} 379 | 380 | {{ post.title }} 381 | 382 | 383 | 384 | {% endfor %} 385 | ``` 386 | E então criarmos a página show.html.twig que irá mostrar o ID e o título do post: 387 | ```twig 388 | {% extends 'base.html.twig' %} 389 | 390 | {% block title %} {{ post.title }}{% endblock %} 391 | 392 | {% block body %} 393 |

ID: {{ post.id }} Titulo: {{ post.title }}

394 | {% endblock %} 395 | ``` 396 | Se tudo der certo a página de listagem de posts deve ficar assim: 397 | ![listagem com links](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/listagem-com-links.png) 398 | e ao clicar no post de ID 1 você verá: 399 | ![post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post.png) 400 | Agora vamos criar uma função para remover um post, para isso vamos voltar novamente ao controller e criar uma função chamada remove(): 401 | ```php 402 | ```/** 403 | * @Route("/post/delete/{id}", name="post.delete") 404 | * @param Request $request 405 | * @return Response 406 | */ 407 | public function remove(PostRepository $repository, $id) 408 | { 409 | $post = $repository->find($id); 410 | 411 | // entity manager 412 | $em = $this->getDoctrine()->getManager(); 413 | $em->remove($post); // remove 414 | $em->flush(); 415 | 416 | return $this->redirect($this->generateUrl('post')); 417 | } 418 | ``` 419 | Agora precisamos adicionar essa ação na nossa listagem para que o usuário possa deletar o post que ele quiser, para isso temos que alterar nossa tabela em post/index.html.twig para: 420 | ```twig 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | {% for post in posts %} 429 | 430 | 431 | {% endfor %} 435 | 436 |
IDTitleActions
IDTitleActions
{{ post.id }} 432 | {{ post.title }} 433 | Delete 434 |
437 | ``` 438 | Se tudo der certo a tabela deve-se parecer com essa: 439 | ![listagem com delete](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/delete.png) 440 | 441 | ### Flash Message 442 | 443 | Você deve ter notado que quando clicamos em um link 'delete' a página simplemente dá refresh sem avisar nada ao usuário. Para adicionarmos uma mensagem temporária de que o post foi deletado com sucesso basta adicionarmos essa linha antes do retorno de remove() no PostController: 444 | ```php 445 | $this->addFlash('success', 'O Post foi deletado'); 446 | ``` 447 | E agora acima da nossa tabela de posts vamos adicionar: 448 | ```twig 449 | {% for message in app.flashes('success') %} 450 |
451 |
452 | {{ message }} 453 |
454 |
455 | {% endfor %} 456 | ``` 457 | Que irá buscar na memória flash pelas mensagens com o nome 'success' e exibir acima da tabela sempre que alguém excluir um item e mostrar temporariamente para o usuário. Caso quisessemos fazer o mesmo com erro bastaria fazer outro for para erros e nele mudar todos os 'success' por 'danger'. 458 | ### Formulários 459 | O Symfony tem várias formas de fazer formulários, vamos utilizar a mais simples: formulários auto-gerados a partir de uma entidade. Para isso precisamos primeiro instalar o criador de formulários: 460 | ``` composer require form validator ``` 461 | Depois de ter esses pacotes instalados no projeto basta darmos o comando: 462 | ``` php bin/console make:form ``` 463 | E ele novamente irá fazer algumas perguntas: 464 | 465 | - The name of the form class (e.g. FierceElephantType): 466 | \> **PostType** 467 | - The name of Entity or fully qualified model class name that the new form will be bound to (empty for none): 468 | \> **Post** 469 | 470 | Esse comando irá criar uma nova classe em src/Form/PostType.php, esse formulário possui dois métodos buildForm() que é reponsavel por construir e renderizar os formulários com os elementos e propriedades corretos e configureOptions que é utilizado para mudar configurações do formulário e no momento apenas possui um $resolver com o método setDefaults() que utiliza todas as configurações padrão exceto por data_class que diz qual entidade está ligada ao formulário. 471 | 472 | Agora precisamos mudar nossa função create() em PostController para ao invés de sempre criar um Post com o mesmo título e persistir mostar um formulário onde vamos preencher o título e então gravar no Banco de Dados. Para isso alteramos a função create para: 473 | ```php 474 | /** 475 | * @Route("/post/create", name="post.create") 476 | * @param Request $request 477 | * @return Response 478 | */ 479 | public function create(Request $request) 480 | { 481 | // cria um novo post com titulo 482 | $post = new Post(); 483 | 484 | // cria um novo formulário usando PostType de modelo que após preenchido 485 | // passa as informações para o objeto $post $form = 486 | $this->createForm(PostType::class, $post); 487 | 488 | // return a response 489 | return $this->render('post/create.html.twig', [ 490 | 'form' => $form->createView() 491 | ]); 492 | } 493 | ``` 494 | Agora precisamos criar uma view que irá renderizar esse formulário. Para isso precisamos criar uma nova view em posts/ chamada create.html.twig conforme mandamos renderizar que será assim: 495 | ```twig 496 | {% extends 'base.html.twig' %} 497 | 498 | {% block title %} Novo Post {% endblock %} 499 | 500 | {% block body %} 501 | {{ form(form) }} 502 | {% endblock %} 503 | ``` 504 | Legal, agora se acesarmos http://localhost/sftutorial/public/index.php/post/create veremos que criou o formulário mas nem adicionou o botão de enviar ou as classes corretas. Para adicionar o botao de enviar vamos voltar em PostType e alterar o método buildForm para: 505 | ```php 506 | public function buildForm(FormBuilderInterface $builder, array $options) 507 | { 508 | $builder 509 | ->add('title') 510 | ->add('save', SubmitType::class) 511 | ; 512 | } 513 | ``` 514 | Existem inúmeros componentes que podem ser adicionados aos formulários. Para saber mais sobre cada um basta consultar a documentação nesse aspecto. Legal agora nosso formulário deve se parecer com isso: 515 | 516 | ![formulario novo post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/fomulario-starter.png) 517 | Certo, agora falta adicionar as classes do Bulma CSS para que fique estiloso para isso vamos adicionar um terceiro parametro em cada add com as propriedades que queremos que os elementos possuam: 518 | ```php 519 | public function buildForm(FormBuilderInterface $builder, array $options) 520 | { 521 | $builder 522 | ->add('title', null, [ 523 | 'attr' => [ 524 | 'class' => 'input' 525 | ], 526 | 'row_attr' => [ 527 | 'class' => 'field' 528 | ] 529 | ]) ->add('save', SubmitType::class, [ 530 | 'attr' => [ 531 | 'class' => 'button is-primary' 532 | ] 533 | ]) ; 534 | } 535 | ``` 536 | E após essa mudança se atualizarmos a página que tem o formulário para a criação de um novo post podemos ver que agora ela se parece assim: 537 | ![formulario-com-bulma](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/form-bulma.png) 538 | 539 | Agora se tentarmos salvar um novo post veremos que ele ainda não está gravando, para isso precisamos voltar ao controller e alterar para: 540 | ```php 541 | /** 542 | * @Route("/post/create", name="post.create") 543 | * @param Request $request 544 | * @return Response 545 | */public function create(Request $request) 546 | { 547 | // cria um novo post com titulo 548 | $post = new Post(); 549 | 550 | // cria um novo formulário usando PostType de modelo que após preenchido 551 | // passa as informações para o objeto $post $form = $this->createForm(PostType::class, $post); 552 | 553 | $form->handleRequest($request); 554 | 555 | if($form->isSubmitted()) { 556 | // entity manager 557 | $em = $this->getDoctrine()->getManager(); 558 | $em->persist($post); // salva o Objeto Post na tabela post 559 | $em->flush(); 560 | 561 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 562 | 563 | return $this->redirect($this->generateUrl('post')); 564 | } 565 | 566 | // return a response 567 | return $this->render('post/create.html.twig', [ 568 | 'form' => $form->createView() 569 | ]); 570 | } 571 | ``` 572 | O que fizemos aqui? Primeiro adicionamos a requisição em handleRequest() que é uma função que irá pegar todos os dados do formulário passados por post e adicionar no nosso objeto $post que foi passado antes ao formulário. Depois adicionamos um if que checa se esse método é chamado em caso de ser uma submissão do nosso formulário, caso seja ele grava o objeto post no Banco e depois redireciona para a lista de posts com uma flash message de sucesso. 573 | ![novo-post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/novo-post.png) 574 | Certo agora vamos adicionar um botão para adicionar um novo post abaixo da nossa lista de posts para isso basta adicionar o seguinte código em template/post/index.html.twig abaixo da nossa tabela: 575 | ```twig 576 |
577 | 578 |
579 | ``` 580 | E com isso teremos: 581 | ![botao novo post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/tela-com-botao.png) 582 | 583 | Uma última coisa que talvez queremos fazer com nosso formulário é **validação**, para isso devemos ir até a nossa entidade em src/Entity/Post.php, para que possamos adicionar validação de formulário precisamos primeiro importar a seguinte classe: 584 | ```php 585 | use Symfony\Component\Validator\Constraints as Assert; 586 | ``` 587 | Então podemos por exemplo dizer que o título no formulário não pode ser enviado em branco, para isso adicionamos a annotation @Assert como importamos com NotNull na propriedade title: 588 | ```php 589 | /** 590 | * @Assert\NotBlank 591 | * @ORM\Column(type="string", length=255) 592 | */ 593 | private $title; 594 | ``` 595 | Agora temos que adicionar a validação no método create do PostController e para isso é muito simples: basta adicionar a condição isValid() no if que checa quando o formulário foi enviado: 596 | ```php 597 | if($form->isSubmitted() && $form->isValid()) { 598 | // entity manager 599 | $em = $this->getDoctrine()->getManager(); 600 | $em->persist($post); // salva o Objeto Post na tabela post 601 | $em->flush(); 602 | 603 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 604 | 605 | return $this->redirect($this->generateUrl('post')); 606 | } 607 | ``` 608 | Pronto, agora se tentarmos enviar o formulário em branco veremos a mensagem 'Preencha este campo' enviada pelo navegador. 609 | 610 | ### Debug 611 | Para podermos fazer o profilling e debug do nosso projeto em Symfony usamos um pacote PHP chamado php-debugbar alterado para Symfony que pode ser instalado utilizando o seguinte comando: 612 | ```composer require web-profiler-bundle``` 613 | E então aparecerá essa barra que irá nos dar várias informações sobre o nosso projeto: 614 | ![php debuggbar no symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/debugbar.png) 615 | ### Segurança 616 | 617 | Assim como o Spring tem o Spring Security, o Symfony também possui um componente chamado Security responsável por autentições, logins e demais formas de autenticação. Como sempre, ele não vem instalado por padrão e precisamos usar o composer para dizer ao Symfony que precisamos dele: 618 | ```composer require security``` 619 | Após instalarmos o componente já podemos fazer nossa tela de login, mas antes precisamos criar um unuário que possa ser autenticado para isso utilizamos o comando make: 620 | ``` php bin/console make:user``` 621 | Após entrarmos com esse comando o console irá fazer algumas perguntas: 622 | 623 | - The name of the security user class (e.g. User) [User]: 624 | \> [Enter] 625 | - Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]: 626 | \> [Enter] 627 | - Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]: 628 | \> **username** 629 | - Does this app need to hash/check user passwords? (yes/no) [yes]: 630 | \> [Enter] 631 | 632 | utilizar o comando make para criar uma autenticação: 633 | ``` php bin/console make:auth``` 634 | Após entrarmos com esse comando o console irá fazer algumas perguntas: 635 | 636 | - What style of authentication do you want? [Empty authenticator]:
637 | [0] Empty authenticator
638 | [1] Login form authenticator 639 | \> **1** 640 | - The class name of the authenticator to create (e.g. AppCustomAuthenticator): 641 | \> **CustomAuthenticator** 642 | - Choose a name for the controller class (e.g. SecurityController) [SecurityController]: 643 | \> [Enter] 644 | - Do you want to generate a '/logout' URL? (yes/no) [yes]: 645 | \> [Enter] 646 | 647 | Legal, agora para sabermos tudo o que foi adicionado para nós podemos utilizar o comando debug para listar todas as rotas ativas na nossa aplicação: 648 | ``` php bin/console debug:router ``` 649 | Vamos perceber que várias rotas com '_' (underline) na frente foram criadas, não precisamos nos preocupar com essas rotas, elas servem para auxiliar o Symfony apenas, o que nos interessa são as rotas /login e /logout que serão responsaveis pela autenticação do usuário. 650 | 651 | Mas antes que possamos fazer login primeiro precisamos fazer a migração da entidade User para nosso Banco de Dados, você lembra o comando para isso? 652 | ``` php bin/console doctrine:schema:update --force ``` 653 | 654 | Legal agora se acessarmos nossa página http://localhost/sftutorial/public/index.php/login veremos que o Symfony já montou uma tela de login básica para nós! 655 | ![login symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/login-basico.png) 656 | Porém se consultarmos através do MySQL a tabela User veremos que ela está vazia, ainda não temos nenhum usuário registrado o que nos impossibilita de fazer login. Como resolvemos isso? Simples, usamos o Symfony para que ele nos gere um controller para registro: 657 | ``` php bin/console make:controller RegistrationController ``` 658 | Em src/Controller/RegistrationController.php vamos apagar o método index e adicionar um método register que irá utilizar o createFormBuilder para criar um formulário simples de registro e passar para a view registration/index.html.twig: 659 | ```php 660 | /** 661 | * @Route("/register", name="register") 662 | * @param Request $request 663 | * @param UserPasswordEncoderInterface $encoder 664 | * @return Response 665 | */ 666 | public function register(Request $request, UserPasswordEncoderInterface $encoder) 667 | { 668 | // cria o formulário de registro 669 | $form = $this->createFormBuilder() 670 | ->add('username') 671 | ->add('password', RepeatedType::class, [ 672 | 'type' => PasswordType::class, 673 | 'required' => true, 674 | 'first_options' => ['label' => 'Password'], 675 | 'second_options' => ['label' => 'Repeat Password'] 676 | ]) 677 | ->add('save', SubmitType::class) 678 | ->getForm() 679 | ; 680 | $form->handleRequest($request); 681 | if($form->isSubmitted() && $form->isValid()) { 682 | // pega os dados do formulário 683 | $data = $form->getData(); 684 | 685 | // cria o objeto User a ser persistido 686 | $user = new User(); 687 | $user->setUsername($data['username']); 688 | $user->setPassword($encoder->encodePassword($user, $data['password'])); 689 | 690 | // persist the data 691 | $em = $this->getDoctrine()->getManager(); 692 | $em->persist($user); 693 | $em->flush(); 694 | 695 | return $this->redirect($this->generateUrl('app_login')); 696 | } 697 | return $this->render('registration/index.html.twig', [ 698 | 'controller_name' => 'RegistrationController', 699 | 'form' => $form->createView() 700 | ]); 701 | } 702 | ``` 703 | E agora precisamos renderizar isso na view, para isso altere o conteúdo de registration/index.html.twig para: 704 | ```twig 705 | {% extends 'base.html.twig' %} 706 | 707 | {% block title %} Register {% endblock %} 708 | 709 | {% block body %} 710 | {{ form(form) }} 711 | {% endblock %} 712 | ``` 713 | E quando acessarmos a página http://localhost/sftutorial/public/index.php/register devemos ver: 714 | ![registration page](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/registration-page.png) 715 | Agora se nos registrarmos seremos redirecionados para a página de login, mas se observarmos no nosso Banco MySQL veremos que nosso usuário está lá com sua senha criptografada pelo UserPasswordEncoderInterface que recebe Depedency Injection de uma classe concreta que faz o hash em Argon2 utilizando a biblioteca libsodium do PHP. 716 | ![sql user](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/sql-user.png) 717 | Mas antes que possamos fazer login com nosso novo usuário precisamos ir em src/Security/CustomAuthenticator.php e alterar a linha que diz: 718 | ```php 719 | // For example : return new RedirectResponse($this->urlGenerator->generate('some_route')); 720 | throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); 721 | ``` 722 | por: 723 | ```php 724 | return new RedirectResponse($this->urlGenerator->generate('post')); 725 | ``` 726 | Essa linha diz para onde o usuário deve ser redirecionado após fazer o login, por padrão quando geramos a autenticação ela vem com TODO - a fazer - e lançando propositalmente uma exceção/erro justamente para que o usuário diga para o Symfony essa informação. Pronto! agora podemos fazer login e após o login ser concluido seremos redirecionados para a listagem de posts. Observe que na barra de debug onde dizia 'anon' agora diz o nome do usuário, isso significa que nosso login funcionou. Para deslogar basta acessar a página /logout e o usuário será deslogado e redirecionado para '/' por padrão. 727 | ![user logged in bar](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/user-logged.png) 728 | Outra coisa que queremos fazer é dizer o que um usuário logado e/ou deslogado pode ou não ver, para isso devemos ir em config/packages/security.yaml e dizer quais rotas um usuário logado ou deslogado pode ver alterando os valores de access_control para: 729 | ```yaml 730 | # Easy way to control access for large sections of your site 731 | # Note: Only the *first* access control that matches will be used 732 | access_control: 733 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 734 | - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY } 735 | - { path: ^/, roles: ROLE_USER } 736 | ``` 737 | Agora apenas usuários autenticados podem ver nossas páginas, caso não esteja autenticado será redirecionado para '/login'. 738 | 739 | Uma última coisa que queremos fazer é exibir o link para login para usuários não cadastrados e logout para os autenticados. Para isso vamos alterar nosso template base.html.twig: 740 | ``` twig 741 | 742 | 743 | 744 | 745 | {% block title %}Welcome!{% endblock %} 746 | 747 | {% block stylesheets %}{% endblock %} 748 | 749 | 750 | 790 |
791 |
792 | {% block body %}{% endblock %} 793 |
794 |
795 | {% block javascripts %}{% endblock %} 796 | 797 | 798 | ``` 799 | Pronto! Agora podemos ver que quando o usuário está autenticado ele vê o botão 'logout' e quando não está vê os botões 'sign in' e 'login'. 800 | ### Relações de tabelas usando Doctrine, parte 1 801 | Atualmente nosso post tem apenas um título e nada mais. O que vamos fazer é adicionar uma imagem e uma categoria a cada post. Para isso teremos que mudar nossa entidade Post, teremos que adicionar um atributo 'image' que irá conter a imagem que faremos upload. Guardar arquivos binários como imagens diretamente no Banco de Dados nunca é uma boa idéia, por isso nossa propriedade será uma string que irá guardar o caminho onde nossa imagem será salva no servidor. 802 | 803 | Primeiro precisamos adicionar a propriedade com a annotation do Doctrine para que ela possa ser mapeada posteriormente para o BD: 804 | ```php 805 | /** 806 | * @ORM\Column(type="string", length=100) 807 | */ 808 | private $image; 809 | ``` 810 | Então devemos criar os getter/setters para que essa propriedade possa ser acessada pelo restante do framework, para isso podemos escreve-los manualmente ou utilizar a linha de comando: 811 | ``` php bin/console make:entity --regenerate``` 812 | A linha de comando irá perguntar o namespace que precisa ser regenerado e então basta dar enter. Pronto, agora temos os getters e setters. 813 | ### Upload de Arquivos 814 | Agora que temos nossa entidade que consegue armazenar o caminho das imagens precisamos fazer o upload de arquivos em si. Para isso precisamos adicionar um input file no nosso formulário de novo post. Vamos mudar o $builder para: 815 | ```php 816 | $builder 817 | ->add('title', null, [ 818 | 'attr' => [ 819 | 'class' => 'input' 820 | ], 821 | 'row_attr' => [ 822 | 'class' => 'field' 823 | ], 824 | 'label_attr' => [ 825 | 'class' => 'label' 826 | ] 827 | ]) ->add('image', FileType::class, [ 828 | 'mapped' => false, 829 | 'row_attr' => [ 830 | 'class' => 'field' 831 | ], 832 | 'label_attr' => [ 833 | 'class' => 'label' 834 | ] 835 | ]) ->add('save', SubmitType::class, [ 836 | 'attr' => [ 837 | 'class' => 'button is-primary' 838 | ] 839 | ]); 840 | ``` 841 | Note que adicionamos 'mapped' como falso, isso siginifica que esse campo não será automaticamente persistido e terá que ser tratado no controller, a documentação do symfony diz que isso é necessário caso quisermos salvar o arquivo em alguma pasta que não a temporária. 842 | 843 | Atualmente nossa tela de novo post deve estar assim: 844 | ![formulario com o campo de imagem](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/formulario-com-imagem.png) 845 | 846 | Ok, como disse antes, agora teremos que tratar o arquivo no controller. Para isso teremos que alterar a função create em PostController 847 | ```php 848 | /** 849 | * @Route("/post/create", name="post.create") 850 | * @param Request $request 851 | * @return Response 852 | */ 853 | public function create(Request $request) 854 | { 855 | // cria um novo post com titulo 856 | $post = new Post(); 857 | 858 | // cria um novo formulário usando PostType de modelo que após preenchido 859 | // passa as informações para o objeto $post 860 | $form = $this->createForm(PostType::class, $post); 861 | 862 | $form->handleRequest($request); 863 | 864 | if($form->isSubmitted() && $form->isValid()) { 865 | $file = $request->files->get('post')['image']; 866 | // entity manager 867 | $em = $this->getDoctrine()->getManager(); 868 | $em->persist($post); 869 | if ($file) { 870 | // cria um nome único para cada imagem 871 | // isso evita conflitos caso 2 tenham mesmo nome 872 | $filename = md5(uniqid()) . '.' . $file->guessClientExtension(); 873 | 874 | // move as imagens, pega o valor de uploads_dir em services.yaml 875 | // e renomeia o arquivo com o valor em $filename 876 | $file->move($this->getParameter('uploads_dir'), $filename); 877 | 878 | // adiciona o caminho ao post para que seja persistido 879 | $post->setImage($filename); 880 | } 881 | $em->flush(); 882 | 883 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 884 | 885 | return $this->redirect($this->generateUrl('post')); 886 | } 887 | 888 | // return a response 889 | return $this->render('post/create.html.twig', [ 890 | 'form' => $form->createView() 891 | ]); 892 | } 893 | ``` 894 | Agora precisamos alterar config/services.yaml para que tenhamos a chave que pegamos com getParameter(), para isso vamos adicionar um valor em parameters apontando o local da pasta em que vamos salvar os arquivos. 895 | ```yaml 896 | parameters: 897 | uploads_dir: '%kernel.project_dir%/public/uploads/' 898 | ``` 899 | Agora precisamos migrar nossos novos campos para o BD para que possa recebe-los: 900 | ``` php bin/console doctrine:schema:update --force``` 901 | Após migrarmos os dados podemos enviar nosso arquivo que será salvo dentro de public/uploads/ e persistido. 902 | ![imagem salva](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/upload-feito.png) 903 | Estamos quase prontos, para finalizarmos o upload de arquivos devemos também exibir a imagem salva quando acessarmos um de nossos posts. Para isso temos que ir no nosso template /post/show.html.twig e adicionar a imagem: 904 | ```twig 905 | {% extends 'base.html.twig' %} 906 | 907 | {% block title %} {{ post.title }}{% endblock %} 908 | 909 | {% block body %} 910 |

ID: {{ post.id }} Titulo: {{ post.title }}

911 | 912 | {% endblock %} 913 | ``` 914 | **Obs:** O caminho '/sftutorial/public/uploads/' é para quem está usando apache sem virtualhost, para quem está usando htacess, emdded server do php ou symfony ou com o virtualhost configurado o caminho é '/uploads/' apenas. 915 | 916 | Se tudo deu certo você deve estar vendo seus novos posts com imagens, parabéns! 917 | ![post com imagem](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-com-imagem.png) 918 | ### Relações de tabelas usando Doctrine, parte 2 919 | Agora faremos uma relação entre duas entidades, sendo uma a categoria e outra post, sendo que uma categoria terá muitos posts enquanto um post terá uma categoria. Para isso primeiro precisamos criar uma entidade chamada de Category com apenas um atributo string name: 920 | ``` php bin/console make:entity ``` 921 | Após entrarmos com esse comando o terminal irá fazer algumas perguntas sobre a entidade que vamos criar e vamos entrar com as seguintes respostas: 922 | - Class name of the entity to create or update (e.g. FierceElephant): 923 | \> **Category** 924 | - New property name (press to stop adding fields): 925 | \> **name** 926 | - Field type (enter ? to see all types) [string]: 927 | \> [Enter] 928 | - Field length [255]: 929 | \> [Enter] 930 | - Can this field be null in the database (nullable) (yes/no) [no]: 931 | \> [Enter] 932 | - Add another property? Enter the property name (or press to stop adding fields): 933 | \> [Enter] 934 | Legal, agora que temos nossa entidade devemos adicionar uma propriedade 'category' na nossa entidade Post com a annotation ORM ManyToOne que define a relação e tem dois parametros: o primeiro é qual a outra entidade - incluindo seu namespace - e o outro é qual propriedade nessa entidade faz referência a essa: 935 | ```php 936 | /** 937 | * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="post") 938 | */ 939 | private $category; 940 | ``` 941 | Agora precisamos adicionar uma propriedade 'post' em Category: 942 | ```php 943 | /** 944 | * @ORM\OneToMany(targetEntity="App\Entity\Post", mappedBy="category") 945 | */ 946 | private $post; 947 | ``` 948 | E agora precisamos regenerar nossas entidades para que elas possuam os getter e setters dessas classses adequados, note que não serão os mesmos de propriedades comuns pois farão os mapeamentos entre ambas entidades: 949 | ``` php bin/console make:entity --regenerate``` 950 | Após isso precisamos migrar nossas mudanças nas entidades para que o banco de dados corresponda a essas mudanças: 951 | ``` php bin/console doctrine:schema:update --force``` 952 | Certo, agora precisamos adicionar um input em nosso formulário que possamos dizer à qual categoria aquele post pertence, para isso novamente vamos mudar o $builder em src/Form/PostType.php: 953 | ```php 954 | $builder 955 | ->add('title', null, [ 956 | 'attr' => [ 957 | 'class' => 'input is-primary' 958 | ], 959 | 'row_attr' => [ 960 | 'class' => 'field' 961 | ], 962 | 'label_attr' => [ 963 | 'class' => 'label' 964 | ] 965 | ]) ->add('image', FileType::class, [ 966 | 'mapped' => false, 967 | 'row_attr' => [ 968 | 'class' => 'field' 969 | ], 970 | 'label_attr' => [ 971 | 'class' => 'label' 972 | ] 973 | ]) ->add('category', EntityType::class, [ 974 | 'class' => Category::class, 975 | 'label' => false, 976 | 'row_attr' => [ 977 | 'class' => 'field select is-rounded is-primary' 978 | ], 979 | 980 | ]) 981 | ->add('save', SubmitType::class, [ 982 | 'attr' => [ 983 | 'class' => 'button is-primary' 984 | ] 985 | ]); 986 | ``` 987 | Certo, agora temos que inserir algumas categorias no banco, para isso você pode usar o gerenciador MySQL de sua preferência - phpMyAdmin, Adminer, Navicat, DBeaver... - eu vou utilizar o integrado na IDE por ser mais prático: 988 | ![insert sql](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/sql.png) 989 | Após inserirmos se tentarmos acessar a página veremos que ela retorna um erro que diz que o objeto não pode ser convertido para string, para que isso seja possivel precisamos implementar um método mágico __toString() na classe Category: 990 | ``` php 991 | public function __toString() 992 | { 993 | return $this->getName(); 994 | } 995 | ``` 996 | Legal, agora tudo deve estar funcionando. A nova tela de cadastro se parecera com esta: 997 | ![formulario novo](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/formulario-novo-bulma.png) 998 | Agora queremos que a categoria apareça na página que exibe as informações do post, para isso vamos alterar o template /post/show.html.twig: 999 | ```twig 1000 | {% extends 'base.html.twig' %} 1001 | 1002 | {% block title %} {{ post.title }}{% endblock %} 1003 | 1004 | {% block body %} 1005 |

ID: {{ post.id }} Titulo: {{ post.title }}

1006 | {{ post.category }} 1007 |
1008 | {% endblock %} 1009 | ``` 1010 | Agora quando acessamos um post ele deve parecer-se com isso: 1011 | ![post com categoria](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-com-categoria.png) 1012 | 1013 | ### Services 1014 | Esse será o último tópico desse tutorial. Basicamente um _Service_ (Serviço) é qualquer Classe do Symfony. Durante esse tutorial usamos diversos serviços e o Symfony é baseado em Serviços (o que poderiamos pensar como módulos). Todos os serviços estão dentro de algo chamado _Container_ que você pode pensar como uma caixa que contêm diversos objetos que precisamos. Para ver todos os serviços que temos em nosso projeto Symfony podemos usar: 1015 | ``` php bin/console debug:container ``` 1016 | Para exemplificar melhor como criar um serviço, vamos transformar a parte de upload de imagens em um serviço que irá tornar nosso código um pouco bagunçado nessa parte em algo mais organizado e efetivo. Para melhor oganizar nossas Classes de serviço vamos criar uma pasta src/Service e dentro vamos criar um arquivo php chamado Uploader.php e dentro dessa classe vamos adicionar nossa função de upload: 1017 | ```php 1018 | class Uploader 1019 | { 1020 | /** 1021 | * @var ContainerInterface 1022 | */ 1023 | private $container; 1024 | 1025 | public function __construct(ContainerInterface $container) 1026 | { 1027 | $this->container = $container; 1028 | } 1029 | public function uploadFile(UploadedFile $file) 1030 | { 1031 | // cria um nome único para cada imagem 1032 | // isso evita conflitos caso 2 tenham mesmo nome 1033 | $filename = md5(uniqid()) . '.' . $file->guessClientExtension(); 1034 | 1035 | // move as imagens, pega o valor de uploads_dir em services.yaml 1036 | // e renomeia o arquivo com o valor em $filename 1037 | $file->move($this->container->getParameter('uploads_dir'), $filename); 1038 | 1039 | return $filename; 1040 | } 1041 | } 1042 | ``` 1043 | e agora vamos alterar nosso método create em PostController para utilizar nosso Service: 1044 | ```php 1045 | /** 1046 | * @Route("/post/create", name="post.create") 1047 | * @param Request $request 1048 | * @return Response 1049 | */public function create(Request $request, Uploader $uploader) 1050 | { 1051 | // cria um novo post com titulo 1052 | $post = new Post(); 1053 | 1054 | // cria um novo formulário usando PostType de modelo que após preenchido 1055 | // passa as informações para o objeto $post $form = $this->createForm(PostType::class, $post); 1056 | 1057 | $form->handleRequest($request); 1058 | 1059 | if($form->isSubmitted() && $form->isValid()) { 1060 | $file = $request->files->get('post')['image']; 1061 | // entity manager 1062 | $em = $this->getDoctrine()->getManager(); 1063 | $em->persist($post); 1064 | if ($file) { 1065 | $filename = $uploader->uploadFile($file); 1066 | 1067 | // adiciona o caminho ao post para que seja persistido 1068 | $post->setImage($filename); 1069 | } 1070 | $em->flush(); 1071 | 1072 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 1073 | 1074 | return $this->redirect($this->generateUrl('post')); 1075 | } 1076 | 1077 | // return a response 1078 | return $this->render('post/create.html.twig', [ 1079 | 'form' => $form->createView() 1080 | ]); 1081 | } 1082 | ``` 1083 | Ótimo se testar agora verá que tudo deve continuar funcionando. Foi um exemplo simples, mas imagine que agora precizassemos fazer uma função que deleta a imagem e que é chamada antes de deletar o post, ou uma que recebe duas imagens deleta a primeira e salva a segunda para atualizar isso tudo ficaria contido em uma única classe com essa responsabilidade e isso é muito **dahora**. 1084 | 1085 | **Parabéns** você sobreviveu até aqui e agradeço muito pelo seu interesse em seguir esse tutorial! Agora você deve saber o básico necessário para criar seus sistemas com Symfony e poderá fazer sites incriveis como o pornhub e Dailymotion. -------------------------------------------------------------------------------- /tutorial/CONTROLLER.md: -------------------------------------------------------------------------------- 1 | ### Ferramenta make e o primeiro Controller 2 | 1. O Symfony é modular e minimalista isso significa que ele vem com quase nada e que precisamos baixar e instalar os pacotes que o framework disponibiliza conforme à necessidade - isso faz parte de sua filosofia que visa ser leve -, a primeira ferramenta que precisamos instalar é a make ela será responsável por gerar a maior parte do código repetitivo para nós, para isso usaremos o composer: 3 | ```composer require`make``` 4 | 2. Antes de conseguirmos criar nosso primeiro controller é necessário instalar o pacote annotations do Symfony que irá adicionar anotações como usamos em Java só que no PHP, para isso use o composer: 5 | ```composer require doctrine/annotations ``` 6 | 3. Após o make e módulo annotations ser instalado podemos usá-lo para gerar nosso primeiro controlador, para isso basta usar o comando no terminal: 7 | ```php bin/console make:controller MainController``` 8 | E isso irá gerar o arquivo **src/Controller/MainController.php** com o seguinte conteúdo: 9 | ```php 10 | json([ 25 | 'message' => 'Welcome to your new controller!', 26 | 'path' => 'src/Controller/MainController.php', 27 | ]); 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /tutorial/DEBUG.md: -------------------------------------------------------------------------------- 1 | ### Debug 2 | Para podermos fazer o profilling e debug do nosso projeto em Symfony usamos um pacote PHP chamado php-debugbar alterado para Symfony que pode ser instalado utilizando o seguinte comando: 3 | ```composer require web-profiler-bundle``` 4 | E então aparecerá essa barra que irá nos dar várias informações sobre o nosso projeto: 5 | ![php debuggbar no symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/debugbar.png) 6 | -------------------------------------------------------------------------------- /tutorial/FLASH.md: -------------------------------------------------------------------------------- 1 | ### Flash Message 2 | 3 | Você deve ter notado que quando clicamos em um link 'delete' a página simplemente dá refresh sem avisar nada ao usuário. Para adicionarmos uma mensagem temporária de que o post foi deletado com sucesso basta adicionarmos essa linha antes do retorno de remove() no PostController: 4 | ```php 5 | $this->addFlash('success', 'O Post foi deletado'); 6 | ``` 7 | E agora acima da nossa tabela de posts vamos adicionar: 8 | ```twig 9 | {% for message in app.flashes('success') %} 10 |
11 |
12 | {{ message }} 13 |
14 |
15 | {% endfor %} 16 | ``` 17 | Que irá buscar na memória flash pelas mensagens com o nome 'success' e exibir acima da tabela sempre que alguém excluir um item e mostrar temporariamente para o usuário. Caso quisessemos fazer o mesmo com erro bastaria fazer outro for para erros e nele mudar todos os 'success' por 'danger'. 18 | -------------------------------------------------------------------------------- /tutorial/FORMULARIO.md: -------------------------------------------------------------------------------- 1 | ### Formulários 2 | O Symfony tem várias formas de fazer formulários, vamos utilizar a mais simples: formulários auto-gerados a partir de uma entidade. Para isso precisamos primeiro instalar o criador de formulários: 3 | ``` composer require form validator ``` 4 | Depois de ter esses pacotes instalados no projeto basta darmos o comando: 5 | ``` php bin/console make:form ``` 6 | E ele novamente irá fazer algumas perguntas: 7 | 8 | - The name of the form class (e.g. FierceElephantType): 9 | \> **PostType** 10 | - The name of Entity or fully qualified model class name that the new form will be bound to (empty for none): 11 | \> **Post** 12 | 13 | Esse comando irá criar uma nova classe em src/Form/PostType.php, esse formulário possui dois métodos buildForm() que é reponsavel por construir e renderizar os formulários com os elementos e propriedades corretos e configureOptions que é utilizado para mudar configurações do formulário e no momento apenas possui um $resolver com o método setDefaults() que utiliza todas as configurações padrão exceto por data_class que diz qual entidade está ligada ao formulário. 14 | 15 | Agora precisamos mudar nossa função create() em PostController para ao invés de sempre criar um Post com o mesmo título e persistir mostar um formulário onde vamos preencher o título e então gravar no Banco de Dados. Para isso alteramos a função create para: 16 | ```php 17 | /** 18 | * @Route("/post/create", name="post.create") 19 | * @param Request $request 20 | * @return Response 21 | */ 22 | public function create(Request $request) 23 | { 24 | // cria um novo post com titulo 25 | $post = new Post(); 26 | 27 | // cria um novo formulário usando PostType de modelo que após preenchido 28 | // passa as informações para o objeto $post $form = 29 | $this->createForm(PostType::class, $post); 30 | 31 | // return a response 32 | return $this->render('post/create.html.twig', [ 33 | 'form' => $form->createView() 34 | ]); 35 | } 36 | ``` 37 | Agora precisamos criar uma view que irá renderizar esse formulário. Para isso precisamos criar uma nova view em posts/ chamada create.html.twig conforme mandamos renderizar que será assim: 38 | ```twig 39 | {% extends 'base.html.twig' %} 40 | 41 | {% block title %} Novo Post {% endblock %} 42 | 43 | {% block body %} 44 | {{ form(form) }} 45 | {% endblock %} 46 | ``` 47 | Legal, agora se acesarmos http://localhost/sftutorial/public/index.php/post/create veremos que criou o formulário mas nem adicionou o botão de enviar ou as classes corretas. Para adicionar o botao de enviar vamos voltar em PostType e alterar o método buildForm para: 48 | ```php 49 | public function buildForm(FormBuilderInterface $builder, array $options) 50 | { 51 | $builder 52 | ->add('title') 53 | ->add('save', SubmitType::class) 54 | ; 55 | } 56 | ``` 57 | Existem inúmeros componentes que podem ser adicionados aos formulários. Para saber mais sobre cada um basta consultar a documentação nesse aspecto. Legal agora nosso formulário deve se parecer com isso: 58 | 59 | ![formulario novo post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/fomulario-starter.png) 60 | Certo, agora falta adicionar as classes do Bulma CSS para que fique estiloso para isso vamos adicionar um terceiro parametro em cada add com as propriedades que queremos que os elementos possuam: 61 | ```php 62 | public function buildForm(FormBuilderInterface $builder, array $options) 63 | { 64 | $builder 65 | ->add('title', null, [ 66 | 'attr' => [ 67 | 'class' => 'input' 68 | ], 69 | 'row_attr' => [ 70 | 'class' => 'field' 71 | ] 72 | ]) ->add('save', SubmitType::class, [ 73 | 'attr' => [ 74 | 'class' => 'button is-primary' 75 | ] 76 | ]) ; 77 | } 78 | ``` 79 | E após essa mudança se atualizarmos a página que tem o formulário para a criação de um novo post podemos ver que agora ela se parece assim: 80 | ![formulario-com-bulma](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/form-bulma.png) 81 | 82 | Agora se tentarmos salvar um novo post veremos que ele ainda não está gravando, para isso precisamos voltar ao controller e alterar para: 83 | ```php 84 | /** 85 | * @Route("/post/create", name="post.create") 86 | * @param Request $request 87 | * @return Response 88 | */public function create(Request $request) 89 | { 90 | // cria um novo post com titulo 91 | $post = new Post(); 92 | 93 | // cria um novo formulário usando PostType de modelo que após preenchido 94 | // passa as informações para o objeto $post $form = $this->createForm(PostType::class, $post); 95 | 96 | $form->handleRequest($request); 97 | 98 | if($form->isSubmitted()) { 99 | // entity manager 100 | $em = $this->getDoctrine()->getManager(); 101 | $em->persist($post); // salva o Objeto Post na tabela post 102 | $em->flush(); 103 | 104 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 105 | 106 | return $this->redirect($this->generateUrl('post')); 107 | } 108 | 109 | // return a response 110 | return $this->render('post/create.html.twig', [ 111 | 'form' => $form->createView() 112 | ]); 113 | } 114 | ``` 115 | O que fizemos aqui? Primeiro adicionamos a requisição em handleRequest() que é uma função que irá pegar todos os dados do formulário passados por post e adicionar no nosso objeto $post que foi passado antes ao formulário. Depois adicionamos um if que checa se esse método é chamado em caso de ser uma submissão do nosso formulário, caso seja ele grava o objeto post no Banco e depois redireciona para a lista de posts com uma flash message de sucesso. 116 | ![novo-post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/novo-post.png) 117 | Certo agora vamos adicionar um botão para adicionar um novo post abaixo da nossa lista de posts para isso basta adicionar o seguinte código em template/post/index.html.twig abaixo da nossa tabela: 118 | ```twig 119 |
120 | 121 |
122 | ``` 123 | E com isso teremos: 124 | ![botao novo post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/tela-com-botao.png) 125 | 126 | Uma última coisa que talvez queremos fazer com nosso formulário é **validação**, para isso devemos ir até a nossa entidade em src/Entity/Post.php, para que possamos adicionar validação de formulário precisamos primeiro importar a seguinte classe: 127 | ```php 128 | use Symfony\Component\Validator\Constraints as Assert; 129 | ``` 130 | Então podemos por exemplo dizer que o título no formulário não pode ser enviado em branco, para isso adicionamos a annotation @Assert como importamos com NotNull na propriedade title: 131 | ```php 132 | /** 133 | * @Assert\NotBlank 134 | * @ORM\Column(type="string", length=255) 135 | */ 136 | private $title; 137 | ``` 138 | Agora temos que adicionar a validação no método create do PostController e para isso é muito simples: basta adicionar a condição isValid() no if que checa quando o formulário foi enviado: 139 | ```php 140 | if($form->isSubmitted() && $form->isValid()) { 141 | // entity manager 142 | $em = $this->getDoctrine()->getManager(); 143 | $em->persist($post); // salva o Objeto Post na tabela post 144 | $em->flush(); 145 | 146 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 147 | 148 | return $this->redirect($this->generateUrl('post')); 149 | } 150 | ``` 151 | Pronto, agora se tentarmos enviar o formulário em branco veremos a mensagem 'Preencha este campo' enviada pelo navegador. 152 | -------------------------------------------------------------------------------- /tutorial/INICIANDO.md: -------------------------------------------------------------------------------- 1 | ### Iniciando um novo projeto no Symfony 2 | 1. Navegue no terminal até a pasta do Apache 3 | ``` bash 4 | $ cd /etc/var/www/ 5 | ``` 6 | 2. Use a ferramenta de CLI do Symfony para criar um novo projeto: 7 | ```bash 8 | $ symfony new 9 | ``` 10 | 3. Acesse a URL http://localhost/nome_do_projeto/public/ e caso veja a página inicial do Symfony: _parabéns seu projeto foi iniciado e está funcionando_! 11 | 12 | ![Página inicial do Symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/welcome.png) 13 | 14 | 4. Agora é necessário importar/abrir o código que foi gerado dentro da pasta que contêm o seu projeto e como somos todos grandinhos e escolhemos nossas ferramentas deixo isso com você. 15 | -------------------------------------------------------------------------------- /tutorial/INSTALACAO.md: -------------------------------------------------------------------------------- 1 | ### Requisitos Mínimos 2 | - Ter o PHP 7.2.5 or maior instalado e as seguintes extensões instaladas e habilitadas no arquivo php.ini: [Ctype](https://www.php.net/book.ctype), [iconv](https://www.php.net/book.iconv), [JSON](https://www.php.net/book.json), [PCRE](https://www.php.net/book.pcre), [Session](https://www.php.net/book.session), [SimpleXML](https://www.php.net/book.simplexml), and [Tokenizer](https://www.php.net/book.tokenizer); 3 | - Ter o Apache e um banco de dados devidamente instalados e configurados; 4 | - Ter o [Composer](https://getcomposer.org/download/), o package manager de php instalado e configurado nas variáveis de ambiente do sistema; 5 | - Instalar a ferramenta [Symfony](https://symfony.com/download), que adiciona diversas ferramentas para desenvolvimento com Symfony. 6 | 7 | Caso o desenvolvedor tenha dúvidas se seu ambiente já atende a todos esses requisitos basta usar o seguinte comando após instalar a ferramenta [Symfony](https://symfony.com/download): 8 | ```bash 9 | $ symfony check:requirements 10 | ``` 11 | -------------------------------------------------------------------------------- /tutorial/ORM.md: -------------------------------------------------------------------------------- 1 | ### ORM 2 | O Symfony usa por padrão o **Doctrine** - que é um projeto separado - como ORM padrão. Como os demais componentes temos que primeiro instalar o ORM para que seja possível utilizá-lo. Para isso utilizaremos o composer: 3 | ```composer require`orm``` 4 | Após instalado irá aparecer a seguinte mensagem: 5 | ``` 6 | * Modify your DATABASE_URL config in .env 7 | * Configure the driver (mysql) and server_version (5.7) in config/packages/doctrine.yaml 8 | ``` 9 | Esse será nosso próximo passo. Se abrirmos a pasta config/packages vamos notar que teremos 2 arquivos novos **doctrine.yaml** e **doctrine_migrations.yaml**. Da mesma forma verá que no arquivo .env será inserido a linha: 10 | ``` 11 | DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7 12 | ``` 13 | Que nada mais é que nossa connection string com o Banco de Dados. Acima haverá um comentário explicando como configurar a string para outros Bancos de Dados além do MySQL. Para que possamos conectar nosso projeto com o banco precisaremos substituir os seguintes valores na URL: 14 | * db_user com o nome do usuário MySQL 15 | * db_password com a senha desse usuário 16 | * 127.0.0.1:3306 com endereço e porta que o Banco está rodando, para localhost não precisa mudar nada aqui. 17 | * db_name pelo nome do banco que será conectado 18 | 19 | Além de instalar o ORM e adicionar as configurações para nós, quando instalamos o Doctrine também instalamos diversos comandos CLI que vão nos ajudar a administrar o Banco de Dados, em seu terminal, digite: 20 | ``` php bin/console doctrine:database:create``` 21 | Vamos notar que nosso banco de dados ainda está vazio, no Doctrine cada tabela será um objeto do nosso sistema, conhecido como entidade, para criarmos nossa primeira entidade devemos usar o comando: 22 | ``` php bin/console make:entity``` 23 | Após entrarmos com esse comando o terminal irá fazer algumas perguntas sobre a entidade que vamos criar e vamos entrar com as seguintes respostas: 24 | - Class name of the entity to create or update (e.g. FierceElephant): 25 | \> **Post** 26 | - New property name (press to stop adding fields): 27 | \> **title** 28 | - Field type (enter ? to see all types) [string]: 29 | \> [Enter] 30 | - Field length [255]: 31 | \> [Enter] 32 | - Can this field be null in the database (nullable) (yes/no) [no]: 33 | \> [Enter] 34 | - Add another property? Enter the property name (or press to stop adding fields): 35 | \> [Enter] 36 | 37 | Pronto! Temos nossa primeira entidade que será Post com o atributo do tipo String title. Dentro da pasta src/Entity encontraremos Post.php que é nossa entidade. Você poderá notar que a Classe está cheia de annotations, elas servem para configurar como esses dados serão transformados em tabela no Banco de Dados (para quem já usou Java é assim que o JPA/Hibernate trabalham também). Para fazermos nossa primeira tabela devemos novamente usar o console do Symfony com o comando: 38 | ``` php bin/console doctrine:schema:update --force ``` 39 | Peraí, como assim --force? Sim, caso tentássemos executar esse comando sem a flag --force o Doctrine nos avisaria que em produção deveriamos usar migrations (que não irei tratar aqui, mas a documentação do framework é bem completa sobre isso) e que apenas deveriamos usar schema:update caso estivermos em ambiente de desenvolvimento e cientes do que fazemos (nosso caso) e que caso estejamos cientes, devemos usar --force. 40 | 41 | ![tabela criada](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-criado.png) 42 | 43 | Legal, agora temos a tabela post como podemos ver no print a abaixo: 44 | Mas como posso criar Posts? Bem, para continuar nossa explicação primeiro precisaremos criar um controller para Post, para isso usamos o comando make: 45 | ``` php bin/console make:controller PostController ``` 46 | Em src/Controller/PostController.php vamos criar um novo método: 47 | ```php 48 | /** 49 | * @Route("/post/create", name="post.create") 50 | * @param Request $request 51 | * @return Response 52 | */public function create(Request $request) 53 | { 54 | // cria um novo post com titulo 55 | $post = new Post(); 56 | $post->setTitle('Post Title'); 57 | 58 | // entity manager 59 | $em = $this->getDoctrine()->getManager(); 60 | $em->persist($post); // salva o Objeto Post na tabela post 61 | $em->flush(); 62 | 63 | // return a response 64 | return new Response('O seu post foi criado.'); 65 | } 66 | ``` 67 | Todas as informações no nosso BD serão objetos, por isso, devemos trabalhar instanciando as entidades e colocando em seus atributos as informações que queremos salvar, assim como fizemos com o objeto Post. Porém, nossas entidades não tem nenhum método que faça a persistência em si, para isso utilizamos o objeto EntityManager do Symfony, não devemos instancia-lo diretamente, mas utilizar o método getDoctrine()->getManager() que nosso controlador herda de AbstractController que é um Factory para esse objeto. Tendo esse objeto instanciado então podemos usar a função persist( _objeto_ ) do EntityManage para persistir os dados do nosso objeto e flush() para encerrar/fechar a conexão com o BD. **obs:** Caso a conexão não ser fechada as Queries não serão executadas e por tanto não haverá os dados no BD. E ao acessar http://localhost/sftutorial/public/index.php/post/create veremos ``` O seu post foi criado. ``` e se tentarmos consultar nosso BD veremos que nossa informação estará lá: 68 | 69 | ![consulta a tabela posts](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/consulta-sql.png) 70 | 71 | Agora que inserimos nosso primeiro Post como fazemos para buscar a informação no Banco? Para isso vamos alterar o método index() automaticamente criado pelo CLI para listar todos os posts alterando-o para: 72 | ```php 73 | /** 74 | * @Route("/post", name="post") 75 | * @param PostRepository $repository 76 | * @return Response 77 | */public function index(PostRepository $repository) 78 | { 79 | $posts = $repository->findAll(); 80 | return $this->render('post/index.html.twig', [ 81 | 'controller_name' => 'PostController', 82 | 'posts' => $posts 83 | ]); 84 | } 85 | ``` 86 | Como podemos notar usamos um objeto PostRepository, esse objeto é criado junto com a Entidade Post e fica em src/Repository/PostRepository.php, essa classe possui vários métodos que podemos usar para conseguir buscar nossas informações no BD como: 87 | 88 | - findAll(): Que retorna um array com todos os dados da tabela/entidade. 89 | - find(): Que retorna um objeto pelo id. 90 | - findBy(): Que retorna um array de objetos selecionados por determinada característica. 91 | - findOneBy(): Que retorna um objeto selecionado por determinada característica. 92 | Caso você esteja usando uma IDE que tenha um autocomplete minimamente decente ele irá mostrar esses e outros métodos do repositório com suas devidas assinaturas caso não estja, devia estar. Como vimos, depois de pegar todos os nossos posts estamos passando ele para a view index.html.twig dentro da pasta templates/posts/, essa view é automaticamente criada pelo comando make quando criamos um controller após termos instalado o twig no projeto, vamos alterar essa view para: 93 | ```twig 94 | {% extends 'base.html.twig' %} 95 | 96 | {% block title %}Listagem de posts{% endblock %} 97 | 98 | {% block body %} 99 |

Lista de Posts

100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | {% for post in posts %} 115 | 116 | 117 | 118 | 119 | {% endfor %} 120 | 121 |
IDTitle
IDTitle
{{ post.id }}{{ post.title }}
122 | {% endblock %} 123 | ``` 124 | Para repertimos a criação de posts podemos dar refresh na página /post/create que chama o método create() do nosso PostController e então acessarmos http://localhost/sftutorial/public/index.php/post onde veremos: 125 | 126 | ![listagem de posts](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/tabela-posts.png) 127 | Certo, agora queremos exibir uma página com cada post, como fariamos isso? Vamos lá, primeiro devemos voltar à PostController e adicionar o método show que irá exibir cada página trazendo a informação do post: 128 | ```php 129 | /** 130 | * @Route("/post/show/{id}", name="post.show") 131 | * @param Request $request 132 | * @return Response 133 | */ 134 | public function show(PostRepository $repository, $id) 135 | { 136 | $post = $repository->find($id); 137 | return $this->render('post/show.html.twig', [ 'post' => $post]); 138 | } 139 | ``` 140 | E então devemos alterar o conteúdo do for em posts/index.html.twig para que tenha os links individuais de cada post: 141 | ```twig 142 | {% for post in posts %} 143 | 144 | {{ post.id }} 145 | 146 | {{ post.title }} 147 | 148 | 149 | 150 | {% endfor %} 151 | ``` 152 | E então criarmos a página show.html.twig que irá mostrar o ID e o título do post: 153 | ```twig 154 | {% extends 'base.html.twig' %} 155 | 156 | {% block title %} {{ post.title }}{% endblock %} 157 | 158 | {% block body %} 159 |

ID: {{ post.id }} Titulo: {{ post.title }}

160 | {% endblock %} 161 | ``` 162 | Se tudo der certo a página de listagem de posts deve ficar assim: 163 | ![listagem com links](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/listagem-com-links.png) 164 | e ao clicar no post de ID 1 você verá: 165 | ![post](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post.png) 166 | Agora vamos criar uma função para remover um post, para isso vamos voltar novamente ao controller e criar uma função chamada remove(): 167 | ```php 168 | ```/** 169 | * @Route("/post/delete/{id}", name="post.delete") 170 | * @param Request $request 171 | * @return Response 172 | */ 173 | public function remove(PostRepository $repository, $id) 174 | { 175 | $post = $repository->find($id); 176 | 177 | // entity manager 178 | $em = $this->getDoctrine()->getManager(); 179 | $em->remove($post); // remove 180 | $em->flush(); 181 | 182 | return $this->redirect($this->generateUrl('post')); 183 | } 184 | ``` 185 | Agora precisamos adicionar essa ação na nossa listagem para que o usuário possa deletar o post que ele quiser, para isso temos que alterar nossa tabela em post/index.html.twig para: 186 | ```twig 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | {% for post in posts %} 195 | 196 | 197 | {% endfor %} 201 | 202 |
IDTitleActions
IDTitleActions
{{ post.id }} 198 | {{ post.title }} 199 | Delete 200 |
203 | ``` 204 | Se tudo der certo a tabela deve-se parecer com essa: 205 | ![listagem com delete](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/delete.png) 206 | -------------------------------------------------------------------------------- /tutorial/RELACOES-1.md: -------------------------------------------------------------------------------- 1 | ### Relações de tabelas usando Doctrine, parte 1 2 | Atualmente nosso post tem apenas um título e nada mais. O que vamos fazer é adicionar uma imagem e uma categoria a cada post. Para isso teremos que mudar nossa entidade Post, teremos que adicionar um atributo 'image' que irá conter a imagem que faremos upload. Guardar arquivos binários como imagens diretamente no Banco de Dados nunca é uma boa idéia, por isso nossa propriedade será uma string que irá guardar o caminho onde nossa imagem será salva no servidor. 3 | 4 | Primeiro precisamos adicionar a propriedade com a annotation do Doctrine para que ela possa ser mapeada posteriormente para o BD: 5 | ```php 6 | /** 7 | * @ORM\Column(type="string", length=100) 8 | */ 9 | private $image; 10 | ``` 11 | Então devemos criar os getter/setters para que essa propriedade possa ser acessada pelo restante do framework, para isso podemos escreve-los manualmente ou utilizar a linha de comando: 12 | ``` php bin/console make:entity --regenerate``` 13 | A linha de comando irá perguntar o namespace que precisa ser regenerado e então basta dar enter. Pronto, agora temos os getters e setters. 14 | -------------------------------------------------------------------------------- /tutorial/RELACOES-2.md: -------------------------------------------------------------------------------- 1 | ### Relações de tabelas usando Doctrine, parte 2 2 | Agora faremos uma relação entre duas entidades, sendo uma a categoria e outra post, sendo que uma categoria terá muitos posts enquanto um post terá uma categoria. Para isso primeiro precisamos criar uma entidade chamada de Category com apenas um atributo string name: 3 | ``` php bin/console make:entity ``` 4 | Após entrarmos com esse comando o terminal irá fazer algumas perguntas sobre a entidade que vamos criar e vamos entrar com as seguintes respostas: 5 | - Class name of the entity to create or update (e.g. FierceElephant): 6 | \> **Category** 7 | - New property name (press to stop adding fields): 8 | \> **name** 9 | - Field type (enter ? to see all types) [string]: 10 | \> [Enter] 11 | - Field length [255]: 12 | \> [Enter] 13 | - Can this field be null in the database (nullable) (yes/no) [no]: 14 | \> [Enter] 15 | - Add another property? Enter the property name (or press to stop adding fields): 16 | \> [Enter] 17 | Legal, agora que temos nossa entidade devemos adicionar uma propriedade 'category' na nossa entidade Post com a annotation ORM ManyToOne que define a relação e tem dois parametros: o primeiro é qual a outra entidade - incluindo seu namespace - e o outro é qual propriedade nessa entidade faz referência a essa: 18 | ```php 19 | /** 20 | * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="post") 21 | */ 22 | private $category; 23 | ``` 24 | Agora precisamos adicionar uma propriedade 'post' em Category: 25 | ```php 26 | /** 27 | * @ORM\OneToMany(targetEntity="App\Entity\Post", mappedBy="category") 28 | */ 29 | private $post; 30 | ``` 31 | E agora precisamos regenerar nossas entidades para que elas possuam os getter e setters dessas classses adequados, note que não serão os mesmos de propriedades comuns pois farão os mapeamentos entre ambas entidades: 32 | ``` php bin/console make:entity --regenerate``` 33 | Após isso precisamos migrar nossas mudanças nas entidades para que o banco de dados corresponda a essas mudanças: 34 | ``` php bin/console doctrine:schema:update --force``` 35 | Certo, agora precisamos adicionar um input em nosso formulário que possamos dizer à qual categoria aquele post pertence, para isso novamente vamos mudar o $builder em src/Form/PostType.php: 36 | ```php 37 | $builder 38 | ->add('title', null, [ 39 | 'attr' => [ 40 | 'class' => 'input is-primary' 41 | ], 42 | 'row_attr' => [ 43 | 'class' => 'field' 44 | ], 45 | 'label_attr' => [ 46 | 'class' => 'label' 47 | ] 48 | ]) ->add('image', FileType::class, [ 49 | 'mapped' => false, 50 | 'row_attr' => [ 51 | 'class' => 'field' 52 | ], 53 | 'label_attr' => [ 54 | 'class' => 'label' 55 | ] 56 | ]) ->add('category', EntityType::class, [ 57 | 'class' => Category::class, 58 | 'label' => false, 59 | 'row_attr' => [ 60 | 'class' => 'field select is-rounded is-primary' 61 | ], 62 | 63 | ]) 64 | ->add('save', SubmitType::class, [ 65 | 'attr' => [ 66 | 'class' => 'button is-primary' 67 | ] 68 | ]); 69 | ``` 70 | Certo, agora temos que inserir algumas categorias no banco, para isso você pode usar o gerenciador MySQL de sua preferência - phpMyAdmin, Adminer, Navicat, DBeaver... - eu vou utilizar o integrado na IDE por ser mais prático: 71 | ![insert sql](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/sql.png) 72 | Após inserirmos se tentarmos acessar a página veremos que ela retorna um erro que diz que o objeto não pode ser convertido para string, para que isso seja possivel precisamos implementar um método mágico __toString() na classe Category: 73 | ``` php 74 | public function __toString() 75 | { 76 | return $this->getName(); 77 | } 78 | ``` 79 | Legal, agora tudo deve estar funcionando. A nova tela de cadastro se parecera com esta: 80 | ![formulario novo](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/formulario-novo-bulma.png) 81 | Agora queremos que a categoria apareça na página que exibe as informações do post, para isso vamos alterar o template /post/show.html.twig: 82 | ```twig 83 | {% extends 'base.html.twig' %} 84 | 85 | {% block title %} {{ post.title }}{% endblock %} 86 | 87 | {% block body %} 88 |

ID: {{ post.id }} Titulo: {{ post.title }}

89 | {{ post.category }} 90 |
91 | {% endblock %} 92 | ``` 93 | Agora quando acessamos um post ele deve parecer-se com isso: 94 | ![post com categoria](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/post-com-categoria.png) 95 | -------------------------------------------------------------------------------- /tutorial/ROTAS.md: -------------------------------------------------------------------------------- 1 | ### Rotas 2 | Existem quatro formas de trabalhar com rotas no Symfony e fica à decisão do programador qual escolher: 3 | 1. **Annotations**: Similar ao Spring Boot/.NET, o roteamento é feito através de uma anotação sobre o método/classe. Como podemos ver no código gerado pelo comando make a classe já veio anotada com a rota que chama o método index() e seu nome. Anotações é o meio padrão de trabalho do Symfony, essa pode ser uma opção bem popular para desenvolvedores que venham desses frameworks, além de adaptar-se melhor ao restante do Workflow do projeto; 4 | 2. **yaml**: Arquivos YAML são arquivos chave-valor assim como o JSON, esse tipo de arquivo é muito utilizado na configuração do Symfony, umas das configurações disponíveis é o arquivo routes.yaml onde podem ser definidas todas as rotas da aplicação, por padrão, esse arquivo vem inteiramente comentado e caso deseje usar YAML para configurar as rotas é só apagar as annotations da classe gerada, descomentar o arquivo route.yaml e preencher o arquivo com o nome da rota como chave e um array - que YAML é declarado por identação - com dois elementos chave-valor o index com o caminho de valor e path que diz qual classe e método será chamado: 5 | ```yaml 6 | main: 7 | path: /main 8 | controller: App\Controller\MainController::index 9 | ``` 10 | 3. **XML**: Similar à outros frameworks enterprise como JSF, para usar basta renomear o arquivo routes.yaml para routes.xml e configurar as chaves e valores com a mesma estrutura esperada no YAML, tendo path e controller dentro do nome da rota. Essa pode ser uma opção interessante ao público enterprise que está migrando de JSF/.NET ou tenham ferramentas que integram melhor com XML como o Eclipse. 11 | ```xml 12 | 13 | 14 | 18 | 19 | 21 | 22 | 23 | ``` 24 | 4. **PHP**: Similar a outros frameworks PHP populares como Codeigniter e Laravel também é possível definir rotas utilizando PHP. Para isso basta renomear o arquivo routes.yaml para routes.php e utilizar um código similar a este: 25 | ```php 26 | use App\Controller\BlogController; 27 | use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; 28 | 29 | return function (RoutingConfigurator $routes) { 30 | $routes->add('main', '/main') 31 | ->controller([MainController::class, 'index']); 32 | }; 33 | ``` 34 | Após configurar as rotas da forma que você achar melhor basta acessar o link http://localhost/sftutorial/public/index.php/main e ver o retorno do método na tela. 35 | 36 | **Obs**: É necessário usar /index.php/main pois o Apache precisará pegar as rotas do arquivo index.php, para ter urls bacanas é necessário configurar o Virtua Hosts no Apache, aqui tem alguns tutoriais de como fazer isso no [Windows](https://www.raphaelfiga.com/desenvolvimento/instalando-configurando-virtualhost-apache-windows/), [Ubuntu](https://www.digitalocean.com/community/tutorials/como-configurar-apache-virtual-hosts-no-ubuntu-14-04-lts-pt) ou no [XAMPP](https://blog.mxcursos.com/criar-virtual-host-com-xampp/). *Lembre-se* que não basta apontar para a pasta do projeto tem que apontar em /public/index.php para funcionar. Caso não queira fazer isso tudo bem bata continuar usando [http://localhost/\/public/index.php/\](#) que está tudo bem. 37 | 38 | ### Recebendo valores através das rotas 39 | Para ilustar como poderiamos receber um slug, id ou nome pela URL vamos criar um novo método chamado custom e anotá-lo conforme. Note que no final da rota adicionamos "{name?}" isso é porque o valor entre chaves vai guardar o que foi passado aqui como valor da propriedade 'name' e vamos colocar '?' para indicar que esse valor é opcional e não irá gerar erro caso falte. Esse método recebe um objeto Request que contêm todas as informações vindas da requisição do usuário e retorna um objeto Response que contêm um HTML que será renderizado no navegador do usuário. 40 | ```php 41 | /** 42 | * @Route("/custom/{name?}", name="custom") 43 | */ 44 | public function custom(Request $request) 45 | { 46 | $name = $request->get('name'); 47 | return new Response('

Olá ' . $name. '!

'); 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /tutorial/SEGURANCA.md: -------------------------------------------------------------------------------- 1 | ### Segurança 2 | 3 | Assim como o Spring tem o Spring Security, o Symfony também possui um componente chamado Security responsável por autentições, logins e demais formas de autenticação. Como sempre, ele não vem instalado por padrão e precisamos usar o composer para dizer ao Symfony que precisamos dele: 4 | ```composer require security``` 5 | Após instalarmos o componente já podemos fazer nossa tela de login, mas antes precisamos criar um unuário que possa ser autenticado para isso utilizamos o comando make: 6 | ``` php bin/console make:user``` 7 | Após entrarmos com esse comando o console irá fazer algumas perguntas: 8 | 9 | - The name of the security user class (e.g. User) [User]: 10 | \> [Enter] 11 | - Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]: 12 | \> [Enter] 13 | - Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]: 14 | \> **username** 15 | - Does this app need to hash/check user passwords? (yes/no) [yes]: 16 | \> [Enter] 17 | 18 | utilizar o comando make para criar uma autenticação: 19 | ``` php bin/console make:auth``` 20 | Após entrarmos com esse comando o console irá fazer algumas perguntas: 21 | 22 | - What style of authentication do you want? [Empty authenticator]:
23 | [0] Empty authenticator
24 | [1] Login form authenticator 25 | \> **1** 26 | - The class name of the authenticator to create (e.g. AppCustomAuthenticator): 27 | \> **CustomAuthenticator** 28 | - Choose a name for the controller class (e.g. SecurityController) [SecurityController]: 29 | \> [Enter] 30 | - Do you want to generate a '/logout' URL? (yes/no) [yes]: 31 | \> [Enter] 32 | 33 | Legal, agora para sabermos tudo o que foi adicionado para nós podemos utilizar o comando debug para listar todas as rotas ativas na nossa aplicação: 34 | ``` php bin/console debug:router ``` 35 | Vamos perceber que várias rotas com '_' (underline) na frente foram criadas, não precisamos nos preocupar com essas rotas, elas servem para auxiliar o Symfony apenas, o que nos interessa são as rotas /login e /logout que serão responsaveis pela autenticação do usuário. 36 | 37 | Mas antes que possamos fazer login primeiro precisamos fazer a migração da entidade User para nosso Banco de Dados, você lembra o comando para isso? 38 | ``` php bin/console doctrine:schema:update --force ``` 39 | 40 | Legal agora se acessarmos nossa página http://localhost/sftutorial/public/index.php/login veremos que o Symfony já montou uma tela de login básica para nós! 41 | ![login symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/login-basico.png) 42 | Porém se consultarmos através do MySQL a tabela User veremos que ela está vazia, ainda não temos nenhum usuário registrado o que nos impossibilita de fazer login. Como resolvemos isso? Simples, usamos o Symfony para que ele nos gere um controller para registro: 43 | ``` php bin/console make:controller RegistrationController ``` 44 | Em src/Controller/RegistrationController.php vamos apagar o método index e adicionar um método register que irá utilizar o createFormBuilder para criar um formulário simples de registro e passar para a view registration/index.html.twig: 45 | ```php 46 | /** 47 | * @Route("/register", name="register") 48 | * @param Request $request 49 | * @param UserPasswordEncoderInterface $encoder 50 | * @return Response 51 | */ 52 | public function register(Request $request, UserPasswordEncoderInterface $encoder) 53 | { 54 | // cria o formulário de registro 55 | $form = $this->createFormBuilder() 56 | ->add('username') 57 | ->add('password', RepeatedType::class, [ 58 | 'type' => PasswordType::class, 59 | 'required' => true, 60 | 'first_options' => ['label' => 'Password'], 61 | 'second_options' => ['label' => 'Repeat Password'] 62 | ]) 63 | ->add('save', SubmitType::class) 64 | ->getForm() 65 | ; 66 | $form->handleRequest($request); 67 | if($form->isSubmitted() && $form->isValid()) { 68 | // pega os dados do formulário 69 | $data = $form->getData(); 70 | 71 | // cria o objeto User a ser persistido 72 | $user = new User(); 73 | $user->setUsername($data['username']); 74 | $user->setPassword($encoder->encodePassword($user, $data['password'])); 75 | 76 | // persist the data 77 | $em = $this->getDoctrine()->getManager(); 78 | $em->persist($user); 79 | $em->flush(); 80 | 81 | return $this->redirect($this->generateUrl('app_login')); 82 | } 83 | return $this->render('registration/index.html.twig', [ 84 | 'controller_name' => 'RegistrationController', 85 | 'form' => $form->createView() 86 | ]); 87 | } 88 | ``` 89 | E agora precisamos renderizar isso na view, para isso altere o conteúdo de registration/index.html.twig para: 90 | ```twig 91 | {% extends 'base.html.twig' %} 92 | 93 | {% block title %} Register {% endblock %} 94 | 95 | {% block body %} 96 | {{ form(form) }} 97 | {% endblock %} 98 | ``` 99 | E quando acessarmos a página http://localhost/sftutorial/public/index.php/register devemos ver: 100 | ![registration page](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/registration-page.png) 101 | Agora se nos registrarmos seremos redirecionados para a página de login, mas se observarmos no nosso Banco MySQL veremos que nosso usuário está lá com sua senha criptografada pelo UserPasswordEncoderInterface que recebe Depedency Injection de uma classe concreta que faz o hash em Argon2 utilizando a biblioteca libsodium do PHP. 102 | ![sql user](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/sql-user.png) 103 | Mas antes que possamos fazer login com nosso novo usuário precisamos ir em src/Security/CustomAuthenticator.php e alterar a linha que diz: 104 | ```php 105 | // For example : return new RedirectResponse($this->urlGenerator->generate('some_route')); 106 | throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); 107 | ``` 108 | por: 109 | ```php 110 | return new RedirectResponse($this->urlGenerator->generate('post')); 111 | ``` 112 | Essa linha diz para onde o usuário deve ser redirecionado após fazer o login, por padrão quando geramos a autenticação ela vem com TODO - a fazer - e lançando propositalmente uma exceção/erro justamente para que o usuário diga para o Symfony essa informação. Pronto! agora podemos fazer login e após o login ser concluido seremos redirecionados para a listagem de posts. Observe que na barra de debug onde dizia 'anon' agora diz o nome do usuário, isso significa que nosso login funcionou. Para deslogar basta acessar a página /logout e o usuário será deslogado e redirecionado para '/' por padrão. 113 | ![user logged in bar](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/user-logged.png) 114 | Outra coisa que queremos fazer é dizer o que um usuário logado e/ou deslogado pode ou não ver, para isso devemos ir em config/packages/security.yaml e dizer quais rotas um usuário logado ou deslogado pode ver alterando os valores de access_control para: 115 | ```yaml 116 | # Easy way to control access for large sections of your site 117 | # Note: Only the *first* access control that matches will be used 118 | access_control: 119 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 120 | - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY } 121 | - { path: ^/, roles: ROLE_USER } 122 | ``` 123 | Agora apenas usuários autenticados podem ver nossas páginas, caso não esteja autenticado será redirecionado para '/login'. 124 | 125 | Uma última coisa que queremos fazer é exibir o link para login para usuários não cadastrados e logout para os autenticados. Para isso vamos alterar nosso template base.html.twig: 126 | ``` twig 127 | 128 | 129 | 130 | 131 | {% block title %}Welcome!{% endblock %} 132 | 133 | {% block stylesheets %}{% endblock %} 134 | 135 | 136 | 176 |
177 |
178 | {% block body %}{% endblock %} 179 |
180 |
181 | {% block javascripts %}{% endblock %} 182 | 183 | 184 | ``` 185 | Pronto! Agora podemos ver que quando o usuário está autenticado ele vê o botão 'logout' e quando não está vê os botões 'sign in' e 'login'. 186 | -------------------------------------------------------------------------------- /tutorial/SERVICES.md: -------------------------------------------------------------------------------- 1 | ### Services 2 | Esse será o último tópico desse tutorial. Basicamente um _Service_ (Serviço) é qualquer Classe do Symfony. Durante esse tutorial usamos diversos serviços e o Symfony é baseado em Serviços (o que poderiamos pensar como módulos). Todos os serviços estão dentro de algo chamado _Container_ que você pode pensar como uma caixa que contêm diversos objetos que precisamos. Para ver todos os serviços que temos em nosso projeto Symfony podemos usar: 3 | ``` php bin/console debug:container ``` 4 | Para exemplificar melhor como criar um serviço, vamos transformar a parte de upload de imagens em um serviço que irá tornar nosso código um pouco bagunçado nessa parte em algo mais organizado e efetivo. Para melhor oganizar nossas Classes de serviço vamos criar uma pasta src/Service e dentro vamos criar um arquivo php chamado Uploader.php e dentro dessa classe vamos adicionar nossa função de upload: 5 | ```php 6 | class Uploader 7 | { 8 | /** 9 | * @var ContainerInterface 10 | */ 11 | private $container; 12 | 13 | public function __construct(ContainerInterface $container) 14 | { 15 | $this->container = $container; 16 | } 17 | public function uploadFile(UploadedFile $file) 18 | { 19 | // cria um nome único para cada imagem 20 | // isso evita conflitos caso 2 tenham mesmo nome 21 | $filename = md5(uniqid()) . '.' . $file->guessClientExtension(); 22 | 23 | // move as imagens, pega o valor de uploads_dir em services.yaml 24 | // e renomeia o arquivo com o valor em $filename 25 | $file->move($this->container->getParameter('uploads_dir'), $filename); 26 | 27 | return $filename; 28 | } 29 | } 30 | ``` 31 | e agora vamos alterar nosso método create em PostController para utilizar nosso Service: 32 | ```php 33 | /** 34 | * @Route("/post/create", name="post.create") 35 | * @param Request $request 36 | * @return Response 37 | */public function create(Request $request, Uploader $uploader) 38 | { 39 | // cria um novo post com titulo 40 | $post = new Post(); 41 | 42 | // cria um novo formulário usando PostType de modelo que após preenchido 43 | // passa as informações para o objeto $post $form = $this->createForm(PostType::class, $post); 44 | 45 | $form->handleRequest($request); 46 | 47 | if($form->isSubmitted() && $form->isValid()) { 48 | $file = $request->files->get('post')['image']; 49 | // entity manager 50 | $em = $this->getDoctrine()->getManager(); 51 | $em->persist($post); 52 | if ($file) { 53 | $filename = $uploader->uploadFile($file); 54 | 55 | // adiciona o caminho ao post para que seja persistido 56 | $post->setImage($filename); 57 | } 58 | $em->flush(); 59 | 60 | $this->addFlash('success', 'O post ' . $post->getTitle() . ' foi criado.' ); 61 | 62 | return $this->redirect($this->generateUrl('post')); 63 | } 64 | 65 | // return a response 66 | return $this->render('post/create.html.twig', [ 67 | 'form' => $form->createView() 68 | ]); 69 | } 70 | ``` 71 | Ótimo se testar agora verá que tudo deve continuar funcionando. Foi um exemplo simples, mas imagine que agora precizassemos fazer uma função que deleta a imagem e que é chamada antes de deletar o post, ou uma que recebe duas imagens deleta a primeira e salva a segunda para atualizar isso tudo ficaria contido em uma única classe com essa responsabilidade e isso é muito **dahora**. 72 | 73 | **Parabéns** você sobreviveu até aqui e agradeço muito pelo seu interesse em seguir esse tutorial! Agora você deve saber o básico necessário para criar seus sistemas com Symfony e poderá fazer sites incriveis como o pornhub e Dailymotion. 74 | -------------------------------------------------------------------------------- /tutorial/TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Criando o template da página 2 | Digamos que quiséssemos adicionar o Bulma CSS CDN no projeto e que todas as páginas exibissem uma navbar. Para isso teríamos que criar um template que diversas páginas usariam, o que reduz drasticamente a quantidade de código que temos que escrever além de facilitar a manutenção do front-end. Dependendo a IDE que você escolheu pode ser que ela tenha criado um arquivo dentro da pasta templates chamado base.html.twig, caso não tenha crie. Iremos criar um template padrão e adicionar o link do CDN no head, o que deve ficar assim: 3 | 4 | ```twig 5 | 6 | 7 | 8 | 9 | {% block title %}Welcome!{% endblock %} 10 | 11 | {% block stylesheets %}{% endblock %} 12 | 13 | 14 | 37 |
38 |
39 | {% block body %}{% endblock %} 40 |
41 |
42 | {% block javascripts %}{% endblock %} 43 | 44 | 45 | ``` 46 | Você deve ter notado os blocos {% %}, então, eles são as partes dinâmicas do nosso template, onde o conteúdo de cada página será inserido. No momento o que nos importa é o **body**. Para dizermos qual template nossa página irá usar passamos a função **extends(** *String arquivo_template* **)** e em seguida as tags block com o conteúdo que queremos inserir em cada bloco. 47 | ```twig 48 | {% extends('base.html.twig') %} 49 | {% block title %} Meu primeiro Twig! {% endblock %} 50 | {% block body %}

Hello {{ name }}!

{% endblock %} 51 | ``` 52 | Para que possamos entender a diferença entre a navegação de páginas vamos alterar nosso método index() na classe **MainController** para: 53 | ```php 54 | /** 55 | * @Route("/main", name="main") 56 | */public function index() 57 | { 58 | return $this->render('home/index.html.twig'); 59 | } 60 | ``` 61 | E dentro de templates/home/ vamos criar um novo arquivo chamado de index.html.twig com o seguinte conteúdo: 62 | ```php 63 | {% extends('base.html.twig') %} 64 | {% block title %} Home {% endblock %} 65 | {% block body %}

Home

{% endblock %} 66 | ``` 67 | O resultado será isso: 68 | **Home Page**: 69 | 70 | ![Página Home](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/home-page.png) 71 | 72 | **Greet Symfonry**: 73 | 74 | ![Página Greet Symfony](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/symfony-page.png) 75 | 76 | **Greet PHP**: 77 | 78 | ![Página Greet PHP](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/php-page.png) 79 | 80 | Eu sei, isso deve estar parecendo muita coisa. E é. Mas vá com calma, aproveite para analisar o código do template e ver o que está acontecendo apesar de parecer dificil não é. Aproveite para tentar fazer alterações e ver o que acontece, por ex: Criar um link para Greet \ certamente ficará mais claro como as coisas funcionam. 81 | -------------------------------------------------------------------------------- /tutorial/TOQUES.md: -------------------------------------------------------------------------------- 1 | 2 | ### Ajustes finais 3 | Certo, agora não tem muito mais o que eu possa te ensinar de Symfony, mas que tal fazermos alguns ajustes no nosso microblog para que ele seja realmente um microblog? 4 | 5 | Primeiro vamos alterar templates/home/index.html.twig para que exiba todos nossos posts com titulo e categoria: 6 | ```twig 7 | {% extends('base.html.twig') %} 8 | {% block title %} Home {% endblock %} 9 | {% block body %} 10 |
11 |

Meu Blog

12 |

Microblog com minhas imagens e fotos preferidas

13 |
14 |
15 | {% for post in posts %} 16 |
17 |
18 |
19 |
20 | 21 |
22 | 23 | {{post.category}} 24 | 25 |
26 | {{ post.title }} 27 |
28 |
{% endfor %} 29 |
30 | {% endblock %} 31 | ``` 32 | Agora vamos alterar o método index em MainController para passar todos os posts para nossa index: 33 | ```php 34 | /** 35 | * @Route("/main", name="main") 36 | */public function index(PostRepository $repository) 37 | { 38 | $posts = $repository->findAll(); 39 | return $this->render('home/index.html.twig', compact('posts')); 40 | } 41 | ``` 42 | Certo, se você tentar acessar http://localhost/sftutorial/public/index.php/main logado deve ver: 43 | ![main page](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/main-page.png) 44 | 45 | Agora vamos deixar essa rota disponível para usuários visitantes em config/packages/security.yaml altere access_control para: 46 | ```yaml 47 | # Easy way to control access for large sections of your site 48 | # Note: Only the *first* access control that matches will be used 49 | access_control: 50 | - { path: ^/main, roles: IS_AUTHENTICATED_ANONYMOUSLY } 51 | - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 52 | - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY } 53 | - { path: ^/, roles: ROLE_USER } 54 | ``` 55 | Certo, lembrando que para fazer com que quando o usuário acessar www.meusite.com.br basta fazer o htaccess ou o vhost apontar para esse endereço. 56 | 57 | Agora que temos nossa linda main page vamos também estilizar nossas telas de login e sign up que ficaram sem estilização pela pressa, primeiro vamos alterar templates/security/login para: 58 | ```twig 59 | {% extends 'base.html.twig' %} 60 | 61 | {% block title %}Log in!{% endblock %} 62 | 63 | {% block body %} 64 |
65 | {% if error %} 66 |
{{ error.messageKey|trans(error.messageData, 'security') }}
67 | {% endif %} 68 | 69 | {% if app.user %} 70 |
71 | You are logged in as {{ app.user.username }}, Logout 72 |
{% endif %} 73 | 74 |

Please sign in

75 |
76 | 77 |
78 | 81 |
82 | 83 |
84 | 85 |
88 | 89 |
90 |
91 | 94 |
95 |
96 |
97 | 100 |
{% endblock %} 101 | ``` 102 | Que deve deixar a página com essa aparência: 103 | ![login](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/login.png) 104 | 105 | Agora precisamos alterar src/Controller/RegistrationController.php alterando a variavel $form para: 106 | ```php 107 | $form = $this->createFormBuilder() 108 | ->add('username', null, [ 109 | 'attr' => [ 110 | 'class' => 'input is-primary' 111 | ], 112 | 'row_attr' => [ 113 | 'class' => 'field' 114 | ], 115 | 'label_attr' => [ 116 | 'class' => 'label' 117 | ] 118 | ]) ->add('password', RepeatedType::class, [ 119 | 'type' => PasswordType::class, 120 | 'required' => true, 121 | 'first_options' => [ 122 | 'label' => 'Password', 123 | 'attr' => [ 124 | 'class' => 'input is-primary' 125 | ], 126 | 'row_attr' => [ 127 | 'class' => 'field' 128 | ], 129 | 'label_attr' => [ 130 | 'class' => 'label' 131 | ] 132 | ], 133 | 'second_options' => [ 134 | 'label' => 'Repeat Password', 135 | 'attr' => [ 136 | 'class' => 'input is-primary' 137 | ], 138 | 'row_attr' => [ 139 | 'class' => 'field' 140 | ], 141 | 'label_attr' => [ 142 | 'class' => 'label' 143 | ] 144 | ], 145 | 146 | ]) 147 | ->add('save', SubmitType::class, [ 148 | 'attr' => [ 149 | 'class' => 'button is-primary' 150 | ] 151 | ]) ->getForm() 152 | ; 153 | ``` 154 | Que deve deixar a tela de registro com essa aparência: 155 | ![register](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/register.png) 156 | 157 | -------------------------------------------------------------------------------- /tutorial/VIEWS.md: -------------------------------------------------------------------------------- 1 | ### Views 2 | Para renderizar as views o Symfony utiliza o template engine [Twig](https://twig.symfony.com/) que ajuda a separar a lógica da apresentação no projeto. O Twig irá renderizar o código php dentro de {{ }} para html muito similar à quando usamos , mas com diversas vantagens, como pré-compilação, cache e escaping. Antes de mais nada precisamos instalar o Twig no projeto, para isso usamos o composer: ```composer require symfony/twig-bundle```. 3 | 4 | Após termos instalado o Twig devemos criar uma pasta com o nome 'templates' dentro da **raíz do nosso projeto**, essa pasta irá conter todos os arquivos de view que criarmos. Agora dentro dessa pasta vamos criar uma outra pasta chamada 'home' que vai conter todos os nossos templates relacionados com a página 'home' e dentro um arquivo chamado custom.html.twig. 5 | 6 | ![Estrutura de pastas](https://github.com/Camilotk/symfony-sisint-ifrs/blob/master/imagens/folders.png) 7 | 8 | Devemos refatorar nosso código custom mudando o retorno que agora não vai ser mais o retorno, mas a função render() herdada de AbstractController que recebe o nome ou caminho do arquivo que será renderizado de dentro da pasta 'templates'. Além disso, precisamos passar a todas as variáveis que a View precisa dentro de um array em que a chave será o nome da variável na view e o valor a variável no controller. 9 | ```php 10 | /** 11 | * @Route("/custom/{name?}", name="custom") 12 | */public function custom(Request $request) 13 | { 14 | $name = $request->get('name'); 15 | return $this->render('home/custom.html.twig', ['name' => $name]); 16 | } 17 | ``` 18 | Ok, agora vamos editar nossa view para exibir os dados como antes, dentro de templates/home/custom.html.twig coloque o conteúdo: 19 | ```html 20 |

Hello {{ name }}!

21 | ``` 22 | Agora acesse a página passando diferentes nomes: 23 | - [http://localhost/\/public/index.php/custom/Symfony](#) 24 | - [http://localhost/\/public/index.php/custom/PHP](#) 25 | - [http://localhost/\/public/index.php/custom/IFRS](#) 26 | --------------------------------------------------------------------------------