Symfony - rlip/java GitHub Wiki

Komendy i debugowanie

dd($tracks);
dump($tracks);
// in twing:
{{ dump(tracks) }}
{{ dump() }}


//lista tras
symfony console debug:router
symfony console router:match /api/songs/11 --method=POST

// lista serwisów
symfony console debug:autowiring

// funkcje twig
symfony console debug:twig
// nazwy parametrów konfiguracyjnych w yml
php bin/console debug:config twig
// przykładowa konfiguracja
php bin/console config:dump twig

// maker bundle
composer require --dev symfony/maker-bundle

php bin/console list make
php bin/console make:controller --help

// konsola
composer require --dev symfony/profiler-pack

//taki helper do parametrów
composer require sensio/framework-extra-bundle

Zawsze można wejść na domena\_profiler żeby zobaczyć profiler

Nowy projekt

symfony new licenseserv //create project
composer require symfony/twig-pack //teplates
composer require symfony/debugger
composer require symfony/asset
composer require symfony/http-client

Controller

class VinylController extends AbstractController
{
    #[Route('/api/songs/{id<\d+>}', name: 'api_songs_get_one', methods: ['GET'])]
    public function getSong(int $id, LoggerInterface $logger): Response
    {
        $song = [
            'id' => $id,
            'name' => 'Waterfalls',
            'url' => 'https://symfonycasts.s3.amazonaws.com/sample.mp3',
        ];

        $logger->info('Returning API response for song {song}', [
            'song' => $id,
        ]);

        return $this->json($song);
    }
----------------
    #[Route('/browse/{genre}', name: 'app_browse')]
    public function browse($genre = null): Response
    {
        $genre = $genre ? u($genre)->title() : null;
        return $this->render('vinyl/browse.html.twig', [
            'genre' => $genre,
        ]); // zamiast render można użyć serwisu Environment twig i zwrócić new Response($twig->render(...))
    }

Params

///config/settings.yml
parameters: // tu dodajemy
    profiles: '/uploads/profiles/'
    profiles_directory: '%kernel.project_dir%/public%profiles%' // tu użymamy profiles z linijki wyżej
//// odczyt w kontolearze:
$this->getParameter('profiles_directory'),

w config/packages/twig też można użyć paraetru z services:

twig:
    globals:
        profiles: '%profiles'

Forms

    #[Route('/micro-post/add', name: 'app_micro_post_add', priority: 2)]
    public function add(Request $request, MicroPostRepository $posts): Response
    {
// można zadeklarować formulary inline, ale zalecane jest zrobić to w osobnym pliku
     //   $form = $this->createFormBuilder($post)
     //       ->add('title')
     //       ->add('text')
     //      ->getForm();

        $form = $this->createForm(
            MicroPostType::class,
            new MicroPost()
        );

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $post = $form->getData();
            $post->setCreated(new \DateTime());
            $posts->save($post, true);

            $this->addFlash('success', 'Your micropost has been added');
            return $this->redirectToRoute('app_micro_post');
        }

        return $this->render(
            'micro_post/add.html.twig',
            [
                'form' => $form
            ]
        );
    }
--------- From/MicroPostType.php
class MicroPostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('text', TextareaType::class)
            ->add('extraPrivacy');
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => MicroPost::class,
        ]);
    }
}

Webpack & Bootstrap

composer require encore
yarn install

yarn add bootstrap --dev  
yarn add @fontsource/roboto-condensed --dev
yarn add @fortawesome/fontawesome-free --dev

// do assets/styles/app.css
  @import '~bootstrap';
  @import '~@fortawesome/fontawesome-free/css/all.css';
  @import '~@fontsource/roboto-condensed';

Cache

#[Route('/browse/{slug}', name: 'app_browse')]
--
public function browse(HttpClientInterface $httpClient, CacheInterface $cache, string $slug = null): Response
{

  $mixes = $cache->get('mixes_data', function(CacheItemInterface $cacheItem) use ($httpClient) {
    $cacheItem->expiresAfter(5);
    $response = $httpClient->request('GET', 'https://raw.githubusercontent.com/SymfonyCasts/vinyl-mixes/main/mixes.json');
   
    return $response->toArray();
  });

Czyszczenie

php bin/console cache:pool:clear cache.app
php bin/console cache:pool:list

MakerBundle

composer require --dev symfony/maker-bundle

php bin/console list make
php bin/console make:controller --help

Doctrine

composer require symfony/orm-pack

symfony console make:entity
symfony console make:migration
symfony console make:migration:status

symfony console doctrine:migration:migrate
symfony console doctrine:migration:migrate prev
symfony console doctrine:migration:migrate status

Można zrobić funkcję która zwraca queryBuildiera. Żeby dodstać sql to qb->getQuery()->getSql()

fixtures

composer require --dev orm-fixtures
symfony console doctrine:fixtures:load

 $micropost1 = new MicroPost();
 $micropost1->setTitle("Welcome to Poland");
 $micropost1->setTest("Welcome to Poland");
 $micropost1->setCreated(new \DateTime());
 $manager->persist($micropost1);
 $manager->flush();

SensioFrameworkExtraBundle

composer require sensio/framework-extra-bundle

Dodaje rózne mapowiania: https://symfony.com/bundles/SensioFrameworkExtraBundle/current/annotations/converters.html

    #[Route('/micro-post/{id}', name: 'app_micro_post_show')]
    public function show(MicroPost $microPost): Response
    {

flash messages

            $this->addFlash('success', 'Your micropost has been added');
      

        {% for message in app.flashes('success') %}
            <div class="rounded-md p-2 border-green-300 bg-green-50 border dark:border-green-600 dark:bg-green-700 dark:text-white mb-4">{{ message }}</div>
        {% endfor %}

Styling

tailindcss.com

#Validacja

composer require symfony/validator

// twig.yaml
twig:
    default_path: 
    form_themes:
        - 'form/fields.html.twig'

// framework.yaml
framework:
    form:
        legacy_error_messages: false

// w entity:
use Symfony\Component\Validator\Constraints as Assert;
----------
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
  {% if app.user.follows is defined and not app.user.follows.contains(userToFollow) %}
  <a href="{{ path('app_follow', { id: userToFollow.id }) }}"
    class="rounded-md border p-2 border-gray-300 text-gray-600 hover:bg-gray-300 dark:text-gray-300 dark:hover:bg-gray-500 dark:border-gray-600">
    Follow
  </a>

Relacje i Urzytkownicy

composer require security
symfony console make:user
symfony console make:migration
symfony console doctrine:migration:migrate

//tworzymy userProfile
symfony console make:entity

//żeby zrobić relację dajemy tym pola i tam np. oneToOne

# Własne komendy
```php
symfony console make:command
//zaleca sie dodać nazwę z jakimś przedrostkiem np. app:create-user
//edycja w Command/CreateUserCommand.php
//odpalenie:
symfony console app:create-user

//można w trakcie komendy zadawać pytania, żeby nie zapisywać np. hasła w histori

Security

Trzeba mieć user entity, login controller, ustawić w security config:

class LoginController extends AbstractController
{
    #[Route('/login', name: 'app_login')]
    public function index(AuthenticationUtils $utils): Response
    {
        $lastUserName = $utils->getLastUsername();
        $error = $utils->getLastAuthenticationError();
        return $this->render('login/index.html.twig', [
            'lastUsername' => $lastUserName,
            'error' => $error
        ]);
    }

    #[Route('/logout', name: 'app_logout')]
    public function logout()
    {
        // Do nothing Symfony do the logout
    }
}
//// security.yml

    firewalls:
        main:
            lazy: true
            provider: app_user_provider
            form_login:
                login_path: app_login
                check_path: app_login
            logout:
                path: app_logout
                target: app_login
//można też tam dodać hierarhię ról:
    role_hierarchy:
        ROLE_ADMIN: [ROLE_EDITOR] # can do everything what ROLE_EDITOR can do
        ROLE_EDITOR: [ROLE_COMMENTER] # can do everything what ROLE_COMMENTER can do
        ROLE_COMMENTER: [ROLE_USER]
//////
// potem można ograniczać dostęp za pomocą annotacji, albo funkcji
#[IsGranted('IS_AUTHENTICATED_FULLY')] 
    public function add(Request $request, MicroPostRepository $posts): Response
    {
//        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
//albo w security.yml ograniczyć an raz do całej ścieżki
    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }

// w twig goraniczamy tak:
{% if is_granted('IS_AUTHENTICATED_FULLY') %}

Voters

symfony console make:voter

//////
class MicroPostVoter extends Voter
{
    public function __construct(private Security $security)
    {
    }
// jeśli funkcja `supports(..)` zwróci true to wykona się `voteOnAttribute()`
    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [MicroPost::EDIT, MicroPost::VIEW])
            && $subject instanceof MicroPost;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        /** @var User $user */
        $user = $token->getUser();
        $isAuth = $user instanceof UserInterface;

        if ($this->security->isGranted('ROLE_ADMIN')) {  //to też można używć w innych miejscach
            return true;
        }

        return match ($attribute) {
            MicroPost::EDIT => ($isAuth && $subject->getAuthor()->getId() == $user->getId())
                || ($this->security->isGranted('ROLE_EDITOR')),
            MicroPost::VIEW => true,
            default => false,
        };
    }
}
/////////////
// później w kontrolerze można zrobić
    #[Route('/micro-post/{post}/edit', name: 'app_micro_post_edit', priority: 2)]
    #[IsGranted(MicroPost::EDIT, 'post')] // tu nazwa paramertu
    public function edit(MicroPost $post, Request $request, MicroPostRepository $posts): Response
    
// można też użyć wewnątrz metody:
$this->denyAccessUnlessGranted(MicroPost::EDIT, $post);

Registration

symfony console make:registration-form
composer require symfonycasts/verify-email-bundle
⚠️ **GitHub.com Fallback** ⚠️