use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
class BundleFormTypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [BundleFormType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->remove('someField');
}
}
namespace App\Doctor;
use App\Doctor\Check\CheckInterface;
final class Doctor
{
/**
* @var CheckInterface[]
*/
private iterable $checks;
public function __construct(iterable $checks)
{
$this->checks = $checks;
}
/**
* @return Violation[]|array
*/
public function check(): array
{
$violations = [];
foreach ($this->checks as $check) {
$violations[$check->feature()] = array_merge($violations[$check->feature()] ?? [], $check->violations());
}
return $violations;
}
}
namespace App\Doctor\Check;
use App\Doctor\Violation;
interface CheckInterface
{
public function feature(): string;
/**
* @return Violation[]
*/
public function violations(): array;
}
services:
_instanceof:
App\Doctor\Check\CheckInterface:
tags:
- { name: app.doctor.check }
App\Doctor\Doctor:
arguments:
$checks: !tagged app.doctor.check
public function doctorAction(): JsonResponse
{
return $this->json($this->doctor->check());
}
{
"foo": [], // Ok
"bar": [
{ "message": "Отсутствуют статусы", "treatment": "Добавьте статусы" }
]
}
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class MyObjectDenormalizer implements DenormalizerInterface
{
private ObjectNormalizer $objectNormalizer;
private EntityManagerInterface $entityManager;
public function __construct(ObjectNormalizer $objectNormalizer, EntityManagerInterface $entityManager)
{
$this->objectNormalizer = $objectNormalizer;
$this->entityManager = $entityManager;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if ($id = $data['id'] ?? null) {
$object = $this->entityManager->getRepository($type)->find($id);
$context = [
AbstractObjectNormalizer::OBJECT_TO_POPULATE => $object,
];
unset($data['id']);
}
return $this->objectNormalizer->denormalize($data, $type, $format, $context);
}
public function supportsDenormalization($data, string $type, string $format = null)
{
return $this->objectNormalizer->supportsDenormalization($data, $type, $format);
}
}
/comment/{id}/edit
), т.к. есть возможность подменить id и отредактировать другую сущность (к которой, к примеру, у пользователя доступа нет) use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutRedirectSubscriber implements EventSubscriberInterface
{
private const KEY = 'logout';
private UrlGeneratorInterface $urlGgenerator;
public static function getSubscribedEvents()
{
return [
ExceptionEvent::class => ['onException', 2], // Before \Symfony\Component\Security\Http\Firewall\ExceptionListener
ResponseEvent::class => 'onResponse',
LogoutEvent::class => 'onLogout',
];
}
public function __construct(UrlGeneratorInterface $urlGgenerator)
{
$this->urlGgenerator = $urlGgenerator;
}
public function onException(ExceptionEvent $event): void
{
if (!$event->isMasterRequest()) {
return;
}
$exception = $event->getThrowable();
if (!$exception instanceof AccessDeniedException) {
return;
}
$session = $event->getRequest()->getSession();
if ($session->has(self::KEY)) {
$event->setResponse(new RedirectResponse($this->urlGgenerator->generate('index')));
$event->stopPropagation();
}
}
public function onResponse(ResponseEvent $event): void
{
if (!$event->isMasterRequest()) {
return;
}
if ($event->getResponse()->getStatusCode() >= 300) {
return;
}
$session = $event->getRequest()->getSession();
if ($session->has(self::KEY)) {
$session->remove(self::KEY);
}
}
public function onLogout(LogoutEvent $event): void
{
$event->getRequest()->getSession()->set(self::KEY, true);
}
}
options
и будете проверять их в обработчике LogoutEvent
function json_tree_tags_recursive($data, &$result, $path = '')
{
foreach ($data as $key => $value) {
if ($path !== '') {
$result[$path . $key] = $value;
} elseif (!is_array($value)) {
$result[$key] = $value;
}
if (is_array($value)) {
json_tree_tags_recursive($value, $result, $path . $key . '_');
}
}
}
json_tree_tags_recursive($data, $result);
print_r($result);
Kernel.php
разбирать структуру папок и подключать все динамически там.migrations/
src/
-- Controller/
---- User/
---- ModuleName/
-- Entity/
---- User/
---- ModuleName/
-- User/
--- Dto/
--- Repository/
--- Service/
-- ModuleName/
--- Dto/
--- Repository/
--- Service/
// src/AppBundle/Form/CustomerType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
//....
$builder
->add('phones',CollectionType::class, array(
'by_reference' => false,
// ...
));
//....
}
class Customers implements UserInterface
{
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Phone", mappedBy="customer_id", cascade={"persist", "remove", "merge"})
*/
private $phones; // Это же коллекция, нужно множественное число, чтобы работали adder и remover
public function addPhone(Phone $phone)
{
$phone->setCustomer($this);
$this->phones->add($phone);
}
public function removePhone(Phone $phone)
{
$phone->setCustomer(null);
$this->phones->removeElement($phone);
}
}
// src/AppBundle/Form/PhoneType.php
// $builder->add('customerId', HiddenType::class); // не нужен
bin/console fos:user:create username em@ai.il 'p@55w0rd'
./**
* @psalm-suppress PropertyNotSetInConstructor
*/
class CreateCommand extends AbstractCommand
{
private DocumentManager $documentManager;
private ValidatorInterface $validator;
private PasswordGenerator $passwordGenerator;
private AdministratorMapper $mapper;
public function __construct(
DocumentManager $documentManager,
ValidatorInterface $validator,
PasswordGenerator $passwordGenerator,
AdministratorMapper $mapper
) {
parent::__construct();
$this->documentManager = $documentManager;
$this->validator = $validator;
$this->passwordGenerator = $passwordGenerator;
$this->mapper = $mapper;
}
/**
* @psalm-suppress MissingReturnType
*/
protected function configure()
{
$this
->setName('app:administrator:create')
->addArgument('username', InputArgument::REQUIRED, 'Username')
->addOption('password', 'p', InputOption::VALUE_REQUIRED, 'Password')
->setDescription('Creates administrator')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var string $username */
$username = $input->getArgument('username');
/** @var string|null $plainPassword */
$plainPassword = $input->getOption('password');
if (!$plainPassword) {
$plainPassword = $this->passwordGenerator->generate();
}
$model = new AdministratorModel();
$model->enabled = true;
$model->username = $username;
$model->password = $plainPassword;
$errors = $this->validator->validate($model);
if (\count($errors) > 0) {
$this->io->error('Can\'t create administrator');
$this->printConstraintViolations($errors);
return 1;
}
$administrator = $this->mapper->map($model);
$this->documentManager->persist($administrator);
$this->documentManager->flush();
$this->io->writeln(sprintf('Administrator <info>%s</info> with password <info>%s</info> has been created', $administrator->getUsername(), $plainPassword));
return 0;
}
}
bin/console app:administrator:create vasx3
maker:
root_namespace: 'App\Common'
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
AppСommon:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Common/Entity'
prefix: 'App\Common\Entity'
alias: AppСommon
specs
всегда один и тот же набор объектов и нет динамических параметров, то вложенные формы вполне могут сработать /** @var AuthorizationCheckerInterface $authorizationChecker */
if (!$authorizationChecker->isGranted('ROLE_USER')) {
throw new AccessDeniedException();
}
/** @var AuthorizationCheckerInterface $authorizationChecker */
if (!$authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
throw new AccessDeniedException();
}
{
"require": {
"phpmailer/phpmailer": "^6.0",
"bogdaan/viber-bot-php": "^0.0.12",
"brickpop/php-rest-curl": "^1.0"
},
"repositories": {
"php-rest-curl": {
"type": "package",
"package": {
"name": "brickpop/php-rest-curl",
"version": "1.0.0",
"source": {
"url": "git@github.com:brickpop/php-rest-curl.git",
"type": "git",
"reference": "master"
},
"autoload": {
"files": ["rest.inc.php"]
}
}
}
}
}
use Symfony\Component\HttpFoundation\JsonResponse;
$data = [
'List' => [
0 => [
'id' => 'id0',
],
1 => [
'id' => 'id1',
],
],
];
dump(
new JsonResponse(
json_encode($data, JsonResponse::DEFAULT_ENCODING_OPTIONS | JSON_FORCE_OBJECT),
200,
[],
true
)
);
// or
$response = new JsonResponse($data);
$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | JSON_FORCE_OBJECT);
dump($response->getContent());
use Pagerfanta\Pagerfanta;
class PaginatedCollection
{
private int $page;
private int $size;
private int $total;
private int $pages;
private array $items;
public function __construct(Pagerfanta $pagerfanta)
{
$this->page = $pagerfanta->getCurrentPage();
$this->size = $pagerfanta->getMaxPerPage();
$this->total = $pagerfanta->getNbResults();
$this->pages = $pagerfanta->getNbPages();
$this->items = iterator_to_array($pagerfanta);
}
public function getPage(): int
{
return $this->page;
}
public function getSize(): int
{
return $this->size;
}
public function getTotal(): int
{
return $this->total;
}
public function getPages(): int
{
return $this->pages;
}
public function getItems(): array
{
return $this->items;
}
}
// Repository
public function findPaginatedBySupplierId(int $supplier_id)
{
return new Pagerfanta(DoctrineORMAdapter($this->getSupplierIdQueryBuilder($supplier_id)));
}
private function getSupplierIdQueryBuilder(int $supplier_id)
{
$qb = $this->createQueryBuilder('sj');
$qb = $qb
->select("sj")
->orderBy('sj.datetime', 'ASC')
->andWhere("sj.supplier = :supplier_id")
->setParameter("supplier_id", $supplier_id);
return $qb;
}
// Controller
public function action()
{
// ...
return $this->json($this->paginatedCollection($repository->findPaginatedBySupplierId($id), $request));
}
// AbstractController
protected function paginatedCollection(Pagerfanta $pagination, Request $request, int $size = 20): PaginatedCollection
{
$pagination = $this->paginate($pagination, $request, $size);
return new PaginatedCollection($pagination);
}
protected function paginate(Pagerfanta $pagination, Request $request, int $size = 20): Pagerfanta
{
$pagination->setMaxPerPage($size);
$pagination->setCurrentPage($request->query->getInt('page', 1));
return $pagination;
}
{
"page": 1,
"size": 20,
"total": 76,
"pages": 4,
"items": [...]
}
/**
* @var \App\Entity\IpContract[]
*
* @ORM\OneToMany(targetEntity="App\Entity\IpContract", mappedBy="users", orphanRemoval=true)
*/
private $ipContracts;
return $this->json($user, 200, [], [
AbstractNormalizer::IGNORED_ATTRIBUTES => ['__initializer__', '__cloner__', '__isInitialized__'],
]);
// App\Kernel
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
// ...
$container->addCompilerPass(new class implements CompilerPassInterface {
public function process(ContainerBuilder $container)
{
$container->getDefinition('serializer.normalizer.object')->setArgument(6, [
AbstractNormalizer::IGNORED_ATTRIBUTES => ['__initializer__', '__cloner__', '__isInitialized__'],
]);
}
});
}
abstract class AbstractAdapter extends \CloudCreativity\LaravelJsonApi\Eloquent\AbstractAdapter
{
public function __construct(CursorStrategy $paging)
{
parent::__construct($this->getModelInstance(), $paging);
}
abstract protected function getModelInstance();
// ...
}