PHP code example of wappcode / pdss-utilities

1. Go to this page and download the library: Download wappcode/pdss-utilities library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

wappcode / pdss-utilities example snippets


use PDSSUtilities\AbstractEntityModel;

#[ORM\Entity]
class Product extends AbstractEntityModel
{
    #[ORM\Column(type: 'string')]
    private string $name;
    
    // Los campos id, created y updated están heredados
}

use PDSSUtilities\AbstractEntityModelUlid;

#[ORM\Entity]
class User extends AbstractEntityModelUlid
{
    #[ORM\Column(type: 'string')]
    private string $email;
}

use PDSSUtilities\AbstractEntityModelKsuid;

#[ORM\Entity]
class Order extends AbstractEntityModelKsuid
{
    #[ORM\Column(type: 'decimal')]
    private float $total;
}

use PDSSUtilities\AbstractEntityModelUuidV4;

#[ORM\Entity]
class Invoice extends AbstractEntityModelUuidV4
{
    #[ORM\Column(type: 'string')]
    private string $number;
}

public function getId(): ?string|?int;
public function getCreated(): DateTimeImmutable;
public function getUpdated(): DateTimeImmutable;
public function __toString(): string;
protected function setUpdated(): self; // Para actualización manual

use PDSSUtilities\AbstractEntityModelUlid;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\AttributeOverride(
    name: 'id',
    column: new ORM\Column(name: 'product_id', type: 'string', length: 26)
)]
class Product extends AbstractEntityModelUlid
{
    // La columna 'id' ahora se llama 'product_id' en la base de datos
}

#[ORM\Entity]
#[ORM\AttributeOverride(
    name: 'id',
    column: new ORM\Column(name: 'id', type: 'string', length: 50)
)]
class CustomEntity extends AbstractEntityModelUlid
{
    // El ID ahora permite hasta 50 caracteres
}

#[ORM\Entity]
#[ORM\AttributeOverrides([
    new ORM\AttributeOverride(
        name: 'created',
        column: new ORM\Column(name: 'created_at', type: 'datetimetz_immutable')
    ),
    new ORM\AttributeOverride(
        name: 'updated',
        column: new ORM\Column(name: 'updated_at', type: 'datetimetz_immutable')
    )
])]
class Article extends AbstractEntityModelUlid
{
    // Las columnas ahora se llaman 'created_at' y 'updated_at'
}

#[ORM\Entity]
#[ORM\AttributeOverride(
    name: 'id',
    column: new ORM\Column(name: 'id', type: 'bigint')
)]
class LargeTable extends AbstractEntityModel
{
    // El ID ahora es BIGINT en lugar de INTEGER
}

use PDSSUtilities\QueryFilter;

$filter = [
    [
        "groupLogic" => "AND",
        "conditionsLogic" => "AND",
        "conditions" => [
            [
                "filterOperator" => "like",
                "value" => ["single" => "John"],
                "property" => "name"
            ],
            [
                "filterOperator" => ">=",
                "value" => ["single" => 18],
                "property" => "age"
            ]
        ]
    ]
];

$qb = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u');

$qb = QueryFilter::addFilters($qb, $filter);

$filter = [
    [
        "groupLogic" => "AND",
        "conditionsLogic" => "OR",
        "conditions" => [
            [
                "filterOperator" => "=",
                "value" => ["single" => "active"],
                "property" => "status",
                "onJoinedProperty" => "profile"  // Aplicar filtro en tabla relacionada
            ]
        ]
    ]
];

$filter = [
    [
        "groupLogic" => "AND",
        "conditionsLogic" => "AND",
        "conditions" => [
            [
                "filterOperator" => "=",
                "value" => ["single" => "admin"],
                "property" => "role"
            ]
        ],
        "compoundConditions" => [
            [
                "conditionsLogic" => "OR",
                "conditions" => [
                    [
                        "filterOperator" => "like",
                        "value" => ["single" => "%@example.com"],
                        "property" => "email"
                    ],
                    [
                        "filterOperator" => "like",
                        "value" => ["single" => "%@test.com"],
                        "property" => "email"
                    ]
                ],
                "compoundConditions" => []
            ]
        ]
    ]
];

use PDSSUtilities\QueryJoins;

$joins = [
    [
        "joinType" => "LEFT",
        "alias" => "profile",
        "property" => "profile"
    ],
    [
        "joinType" => "INNER",
        "alias" => "orders",
        "property" => "orders"
    ],
    [
        "joinType" => "LEFT",
        "alias" => "items",
        "property" => "items",
        "joinedProperty" => "orders"  // Join desde otro join
    ]
];

$qb = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u');

$qb = QueryJoins::addJoins($qb, $joins);
// Resultado: FROM User u LEFT JOIN u.profile profile INNER JOIN u.orders orders LEFT JOIN orders.items items

use PDSSUtilities\QuerySelect;

$select = [
    'properties' => ['id', 'name', 'email'],  // Propiedades de la entidad raíz
    'joins' => [
        [
            'joinedAlias' => 'profile',
            'properties' => ['id', 'bio', 'avatar']
        ],
        [
            'joinedAlias' => 'orders',
            'properties' => ['id', 'total', 'status']
        ]
    ]
];

$selectValues = QuerySelect::createDoctrineSelectValue('u', $select);
// Resultado: ['partial u.{id,name,email}', 'partial profile.{id,bio,avatar}', 'partial orders.{id,total,status}']

$qb->select($selectValues);

$select = [
    'properties' => [],  // Selecciona todo de la raíz
    'joins' => [
        [
            'joinedAlias' => 'profile',
            'properties' => []  // Selecciona todo del join
        ]
    ]
];

$selectValues = QuerySelect::createDoctrineSelectValue('u', $select);
// Resultado: ['u', 'profile']

use PDSSUtilities\QuerySort;

$orderBy = [
    [
        "direction" => "desc",
        "property" => "created"
    ],
    [
        "direction" => "asc",
        "property" => "name"
    ]
];

$qb = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u');

$qb = QuerySort::addOrderBy($qb, $orderBy);
// Resultado: ORDER BY u.created DESC, u.name ASC

$orderBy = [
    [
        "direction" => "desc",
        "property" => "createdAt",
        "onJoinedProperty" => "orders"  // Ordenar por campo de tabla relacionada
    ]
];

$orderByJson = '[{"direction":"desc","property":"created"}]';
$orderBy = QuerySort::standardizeRequestParams($orderByJson);
$qb = QuerySort::addOrderBy($qb, $orderBy);

// Controlador recibiendo datos POST
$requestData = json_decode(file_get_contents('php://input'), true);

$filters = $requestData['filters'] ?? [];
$joins = $requestData['joins'] ?? [];
$select = $requestData['select'] ?? [];
$orderBy = $requestData['orderBy'] ?? [];

// Aplicar a QueryBuilder
$qb = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u');

$qb = QueryJoins::addJoins($qb, $joins);
$qb = QueryFilter::addFilters($qb, $filters);
$qb = QuerySort::addOrderBy($qb, $orderBy);

// Doctrine convierte esto:
$qb->where('u.name = :name')->setParameter('name', $userInput);

// En un prepared statement:
// SELECT * FROM users WHERE name = ? 
// Binding: ['John']

class QueryValidator
{
    private const ALLOWED_PROPERTIES = [
        'User' => ['id', 'name', 'email', 'created', 'updated'],
        'Profile' => ['id', 'bio', 'avatar'],
        'Order' => ['id', 'total', 'status', 'created']
    ];
    
    public static function validateProperty(string $entity, string $property): bool
    {
        return in_array($property, self::ALLOWED_PROPERTIES[$entity] ?? [], true);
    }
    
    public static function validateFilters(array $filters, string $entity): void
    {
        foreach ($filters as $group) {
            foreach ($group['conditions'] ?? [] as $condition) {
                if (!self::validateProperty($entity, $condition['property'])) {
                    throw new \InvalidArgumentException("Propiedad no permitida: {$condition['property']}");
                }
            }
        }
    }
}

// Uso:
try {
    QueryValidator::validateFilters($filters, 'User');
    $qb = QueryFilter::addFilters($qb, $filters);
} catch (\InvalidArgumentException $e) {
    // Manejar error de validación
}

class QueryValidator
{
    private const ALLOWED_JOINS = [
        'User' => ['profile', 'orders', 'roles'],
        'Order' => ['items', 'user'],
    ];
    
    public static function validateJoins(array $joins, string $entity): void
    {
        foreach ($joins as $join) {
            $property = $join['property'];
            if (!in_array($property, self::ALLOWED_JOINS[$entity] ?? [], true)) {
                throw new \InvalidArgumentException("Join no permitido: {$property}");
            }
        }
    }
}

class QueryValidator
{
    private const ALLOWED_OPERATORS = [
        'public' => ['=', 'like', '>', '<', '>=', '<=', 'in'],
        'admin' => ['=', '!=', 'like', 'not_like', '>', '<', '>=', '<=', 'in', 'not_in', 'between', 'is_null', 'is_not_null']
    ];
    
    public static function validateOperator(string $operator, string $userRole): bool
    {
        $allowed = self::ALLOWED_OPERATORS[$userRole] ?? self::ALLOWED_OPERATORS['public'];
        return in_array(strtolower($operator), $allowed, true);
    }
}

class QueryValidator
{
    public static function validateFilterValue(array $condition): void
    {
        $property = $condition['property'];
        $value = $condition['value']['single'] ?? $condition['value']['many'] ?? null;
        
        // Ejemplo: validar que 'age' sea numérico
        if ($property === 'age' && !is_numeric($value)) {
            throw new \InvalidArgumentException("El valor de 'age' debe ser numérico");
        }
        
        // Ejemplo: validar formato de email
        if ($property === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Formato de email inválido");
        }
    }
}

class QueryValidator
{
    private const MAX_JOIN_DEPTH = 3;
    private const MAX_FILTER_GROUPS = 5;
    private const MAX_CONDITIONS_PER_GROUP = 10;
    
    public static function validateComplexity(array $filters, array $joins): void
    {
        if (count($joins) > self::MAX_JOIN_DEPTH) {
            throw new \InvalidArgumentException("Demasiados joins");
        }
        
        if (count($filters) > self::MAX_FILTER_GROUPS) {
            throw new \InvalidArgumentException("Demasiados grupos de filtros");
        }
        
        foreach ($filters as $group) {
            if (count($group['conditions'] ?? []) > self::MAX_CONDITIONS_PER_GROUP) {
                throw new \InvalidArgumentException("Demasiadas condiciones por grupo");
            }
        }
    }
}

// Controlador con validación completa
class UserController
{
    public function search(Request $request, EntityManagerInterface $em): Response
    {
        // 1. Obtener datos del request
        $filters = $request->request->get('filters', []);
        $joins = $request->request->get('joins', []);
        $orderBy = $request->request->get('orderBy', []);
        $select = $request->request->get('select', []);
        
        try {
            // 2. Validar datos de entrada
            QueryValidator::validateComplexity($filters, $joins);
            QueryValidator::validateFilters($filters, 'User');
            QueryValidator::validateJoins($joins, 'User');
            
            // 3. Construir query
            $qb = $em->createQueryBuilder()
                ->select('u')
                ->from(User::class, 'u');
            
            $qb = QueryJoins::addJoins($qb, $joins);
            $qb = QueryFilter::addFilters($qb, $filters);
            $qb = QuerySort::addOrderBy($qb, $orderBy);
            
            // 4. Aplicar límites
            $qb->setMaxResults(100); // Limitar resultados
            
            // 5. Ejecutar
            $results = $qb->getQuery()->getResult();
            
            return new JsonResponse($results);
            
        } catch (\InvalidArgumentException $e) {
            return new JsonResponse(['error' => $e->getMessage()], 400);
        }
    }
}

use PDSSUtilities\AbstractEntityModelUlid;
use PDSSUtilities\QueryFilter;
use PDSSUtilities\QueryJoins;
use PDSSUtilities\QuerySelect;
use PDSSUtilities\QuerySort;

// 1. Definir entidad
#[ORM\Entity]
class User extends AbstractEntityModelUlid
{
    #[ORM\Column(type: 'string')]
    private string $name;
    
    #[ORM\OneToOne(targetEntity: Profile::class)]
    private Profile $profile;
    
    #[ORM\OneToMany(targetEntity: Order::class, mappedBy: 'user')]
    private Collection $orders;
}

// 2. Construir query dinámica
$qb = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u');

// Agregar joins
$joins = [
    ["alias" => "profile", "property" => "profile"],
    ["alias" => "orders", "property" => "orders"]
];
$qb = QueryJoins::addJoins($qb, $joins);

// Agregar filtros
$filter = [
    [
        "groupLogic" => "AND",
        "conditionsLogic" => "AND",
        "conditions" => [
            ["filterOperator" => "like", "value" => ["single" => "%John%"], "property" => "name"]
        ]
    ]
];
$qb = QueryFilter::addFilters($qb, $filter);

// Agregar ordenamiento
$orderBy = [
    ["direction" => "desc", "property" => "created"]
];
$qb = QuerySort::addOrderBy($qb, $orderBy);

// Selección parcial
$select = [
    'properties' => ['id', 'name', 'created'],
    'joins' => [
        ['joinedAlias' => 'profile', 'properties' => ['id', 'bio']]
    ]
];
$selectValues = QuerySelect::createDoctrineSelectValue('u', $select);
$qb->select($selectValues);

// Ejecutar query
$results = $qb->getQuery()->getResult();