Posts

A Set of Static Analyzers for PHP Code

A static analyzer scans the source code for errors and vulnerabilities and for compliance with the coding standard. The article describes how to install, configure and run static analyzers: PHPStan Psalm PHP_CodeSniffer PHP Coding Standards Fixer PHPMD PHPStan Installation composer require --dev phpstan/phpstan Run vendor/bin/phpstan analyze -l 9 src Psalm Installation composer require --dev vimeo/psalm Configuration src/psalm.xml <?xml version="1.0"?> <psalm errorLevel="7" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" findUnusedBaselineEntry="true" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor

PHP SPL Exceptions Use Cases

An exception is used to immediately terminate the operation due to incorrect use of the code or incorrect data transferred for processing, or the inability to transfer data to another system. LogicException (and inherited) is thrown before the operation is executed, due to incorrect code usage. For example, an unknown function is called, a variable is passed with the wrong type, value, or wrong string length. RuntimeException (and inherited) is thrown during the execution of an operation, due to invalid data being passed or the inability to pass data to another system. For example, while processing the configuration file, it turned out that the value of the parameter was incorrect or the connection to the database was not established. Throwing LogicException and RuntimeException results in a HTTP 500 (Internal Server Error) server response because the error is on the server side. SPL Exceptions Class Tree LogicException BadFunctionCallException

How to Use sed Command

The sed command is primarily used to replace text in a file with a regular expression, but it can also be used to search and delete text. For example, there is a php.ini file. Need to replace memory_limit = 128M with memory_limit = 256M . sed command in this case would look like this: sed 's/memory_limit.*$/memory_limit = 256M/' php.ini | grep memory_limit memory_limit = 256M The command above is used as a regular expression check, the changes will not be written to the file, but only the replaced text that is filtered out by the grep command will be displayed. To write to a file, need to add the -i option at the beginning: sed -i 's/memory_limit.*$/memory_limit = 256M/' php.ini

Installing Packages in Symfony using Flex and Packs

Explanation of installing packages in Symfony using Flex and Packs. Flex Package is a ready-to-use functionality that adds new features to simplify development process. In Symfony, packages are called bundles and are installed via Composer. In order for bundle to work, it needs to be attached to framework. This usually requires doing some extra work - adding a bundle class in config/bundles.php and adding a config file in config/packages/*.yaml and possibly some others. In order for bundle to automatically attach after installation, plugin for Composer called Flex was created. Flex after installing bundle executes recipes, that is instructions for automating attachment and configuration of bundle in Symfony. Executed recipes are written to symfony.lock file. There are two recipe repositories: main and contributor . Main repository contains quality recipes for popular bundles. Contributor repository contains recipes for the rest of bundles developed by community. Bund

Fixing Permissions In Docker Container

There are two ways to fix permissions in a docker container. Create User and Group in Container (first way) Dockerfile: FROM ubuntu ARG WWW_USER_UID=1000 ARG WWW_GROUP_GID=1000 RUN addgroup -gid $WWW_GROUP_GID www RUN adduser www \ -uid $WWW_USER_UID \ --disabled-login \ --ingroup www \ --no-create-home \ --quiet \ --system RUN mkdir /app RUN chown www:www /app RUN mkdir /home/www RUN chown www:www /home/www USER www WORKDIR /app WWW_USER_UID and WWW_GROUP_GID must be equal to the user of the host machine, i.e. your current user outside the container. Fix permissions with ACL (second way) Dockerfile: FROM alpine:latest as alpine RUN apk add acl make WORKDIR app CMD ["tail", "-f", "/dev/null"] docker-compose.yaml: version: "3.4" services: alpine: build: context: . target: alpine stop_grace_period: 0s volumes: - .:/app Makefile: fix-permissions: setfacl -dR -m u:$(uid):rwX . setfacl

How umask Function Works in PHP

Umask function subtracts permissions for files and directories on creation. Permission values: 0 = --- no permission 1 = --x execute 2 = -w- write 3 = -wx write and execute 4 = r-- read 5 = r-x read and execute 6 = rw- read and write 7 = rwx read, write and execute <?php // default 0644 = rw-r--r-- // we want 0400 = r-------- // 0777 - 0400 = 0377 umask(0377); // The file will be created with permissions 0400 fopen('test.txt', 'w');

Phpunit File Uploading Test

The test file, in this case test.jpg must be in the tests directory. To prevent the file from being moved from the tests directory after it has been uploaded by the move_uploaded_file() method, it is first copied to the /tmp directory by the createUploadedFile method. Component Filesystem must be installed. <?php namespace App\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\UploadedFile; class FileControllerTest extends WebTestCase { public function testFile(): void { $client = static::createClient(); $filePath = __DIR__.'/test.jpg'; $crawler = $client->request( 'POST', '/file', files: [$this->createUploadedFile($filePath)] ); $this->assertResponseIsSuccessful(); } private function createUploadedFile(string $filePath): UploadedFile

Removing Related Entities in Doctrine

The article shows what options need to set for removing related entities in Doctrine. Doctrine has several options for removing related entities: cascade=["remove"] orphanRemoval=true onDelete="CASCADE | SET NULL" Doctrine wraps INSERT , UPDATE , DELETE SQL statements in a transaction by default. cascade=["remove"] When cascade: ['remove'] is specified, remove will also be executed on the associated entity. Doesn't require creating a migration as there are no changes to the database schema. OneToOne #[ORM\Entity(repositoryClass: ProductRepository::class)] class Product { #[ORM\OneToOne(mappedBy: 'product', cascade: ['remove'])] private ?Photo $photo = null; } #[ORM\Entity(repositoryClass: PhotoRepository::class)] class Photo { #[ORM\OneToOne(inversedBy: 'photo')] #[ORM\JoinColumn(nullable: false)] private ?Product $product = null; } FOREIGN KEY is set on the side of the

Symfony Commands for Loading and Purging Fixtures

Doctrine already has a command to load fixtures, symfony console doctrine:fixtures:load , but does not take into account the reset of sequences and there is no command just to purge fixtures. The article describes Symfony commands: Purge Database, Restart Sequences (PostgreSQL) and Load Fixtures Purge Database Without Loading Fixtures Purge Database, Restart Sequences (PostgreSQL) and Load Fixtures // src/Command/LoadFixturesCommand.php <?php namespace App\Command; use Doctrine\DBAL\Exception; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand( name: 'app:fixtures:load', description: 'Purg

How to Store PostgreSQL Data on Host Machine in Docker

In order not to accidentally lose PostgreSQL data, for example, after running the docker system prune -a --volumes command, they can be stored on the host machine, that is, outside docker. # docker-compose.yml version: "3.4" services: database: image: postgres:${POSTGRES_VERSION:-14}-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-app} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} POSTGRES_USER: ${POSTGRES_USER:-app} volumes: - ./docker/db/data:/var/lib/postgresql/data:rw ports: - 5432:5432 In this case, the docker/db/data directory will store the PostgreSQL data corresponding to the /var/lib/postgresql/data directory in the container. Let's start the container: docker-compose up -d Let's look at the docker/db/data directory: ls -la docker/db/data ls: cannot open directory 'docker/db/data': Permission denied The error occurred because when PostgreSQL starts, it changes the permissions on th

How to Use doctrine:migrations:rollup Command

During the development process, a large number of migrations are created, but rollbacks are not often used or not used at all. Instead of creating another migration, it's better to create one single migration that will include all past database schema changes. That's what the doctrine:migrations:rollup command is for. The doctrine:migrations:rollup command does the following: Check that there is only one migration in the migrations directory Delete executed migrations from the doctrine_migration_versions table Will add a single migration to the doctrine_migration_versions table, but will not execute it A single migration will not be executed because it is expected that all previous migrations were executed earlier, and the migration itself contains the up-to-date database schema. Steps to run doctrine:migrations:rollup command: DEV environment Dump database data using pg_dumpall -a -U USERNAME > dump.sql command Drop all tables and sequences: dr

Doctrine fetch: EXTRA_LAZY, LAZY, EAGER

Fetch means whether a related entity or collection will be additionally fetched. For example, is it necessary to get the Photo entity along with getting the Product entity? There are several fetch strategies: EXTRA_LAZY - related collection will not be fetched until the collection method is called, except for these methods contains($entity) , containsKey($key) , count() , get($key) , slice($offset, $length = null) , add($entity) . Adding an entity in this way will also not cause the collection to be fetched: $collection[] = $entity LAZY - related entity or collection will not be fetched until the entity property or collection method is called EAGER - related entity or collection will always be fetched The EXTRA_LAZY strategy is used for collections, LAZY and EAGER for entities. By default, all relation types use the LAZY strategy. Rules for setting strategies: For relation OneToOne fit EAGER For relation OneToMany , fit EXTRA_LAZY For relation ManyToOne , fi

Doctrine Query Builder Optimization

The article describes how to reduce the number of Doctrine generated SQL queries when using Query Builder. The Product entity, which has the required Brand and Category entities and the optional Photo entity: #[ORM\Entity(repositoryClass: ProductRepository::class)] class Product { #[ORM\ManyToOne()] #[ORM\JoinColumn(nullable: false)] private ?Brand $brand = null; #[ORM\ManyToOne(inversedBy: 'products')] #[ORM\JoinColumn(nullable: false)] private ?Category $category = null; #[ORM\OneToOne()] private ?Photo $photo = null; } Product entity repository: class ProductRepository extends ServiceEntityRepository { public function findByCategoryId(int $categoryId): array { return $this->createQueryBuilder('p') ->andWhere('p.category = :categoryId') ->setParameter('categoryId', $categoryId) ->getQuery() ->getResult() ; } } Gettin

Doctrine Entity Relations

Image
An entity is an identifiable object that is stored in a database. Relation means belonging of one entity to another. For example, a relation between the Order and DeliveryAddress entities means that the order has a delivery address or the delivery address belongs to the order. There are several relation types between entities: OneToOne, OneToMany, ManyToOne, ManyToMany. OneToOne OneToOne means that only one entity belongs to one entity. For example, one order has only one delivery address. The OneToOne relation makes it possible to get the DeliveryAddress entity from the Order entity. #[ORM\Entity(repositoryClass: OrderRepository::class)] #[ORM\Table(name: '`order`')] class Order { #[ORM\OneToOne()] private ?DeliveryAddress $deliveryAddress = null; } #[ORM\Entity(repositoryClass: DeliveryAddressRepository::class)] class DeliveryAddress { // relation is set on the side of the Order entity } CREATE TABLE "order" (id INT NOT NULL, del

Doctrine Cascade Operations

Cascading operations mean that when you perform a persist , remove , merge (deleted), detach , refresh (does not work) operation on an entity, the same operation will also be performed on the associated entity. Persist The persist operation saves the new entity to the database. The persist operation only needs to be performed when a new entity needs to be created, if the entity was obtained from the repository, then persist is not necessary, since the changes are already tracked by the EntityManager, it is enough to just flush . When specifying cascade: ['persist'] , persist will also be executed on the related entity. When cascade: ['persist'] is NOT specified #[ORM\Entity(repositoryClass: ProductRepository::class)] class Product { #[ORM\OneToOne(mappedBy: 'product')] private ?Photo $photo = null; } #[ORM\Entity(repositoryClass: PhotoRepository::class)] class Photo { #[ORM\OneToOne(inversedBy: 'photo')] #[ORM\JoinColumn(nul