PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0
Showing posts with label doctrine-orm. Show all posts
Showing posts with label doctrine-orm. Show all posts

Monday, December 5, 2022

[FIXED] how to access DB from console component application?

 December 05, 2022     doctrine-orm, oci8, php, symfony-2.7     No comments   

Issue

I am trying to build a batch job using command console and need to connect to DB to fetch data.

I have noticed different methods from different sections to access DB. From controller $this->getDoctrine()->getRepository() is used and for services it is $this->getEntityManager()->getConnection()

what is the way to connect to DB object with console component?.


Solution

If You are writing some custom console command I believe that You can just extend it to

Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand

and then just use

$this->getContainer()->get('doctrine')

to be in the right place.



Answered By - Vini
Answer Checked By - Katrina (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Wednesday, November 23, 2022

[FIXED] How to declare typed Collection inside Symfony Entity in PHP 8.1

 November 23, 2022     doctrine-orm, php, phpstan     No comments   

Issue

I receive an error from PHPStan:

Property App\Entity\Product::$productArticles type mapping mismatch: property can contain Doctrine\Common\Collections\Collection but database  
         expects Doctrine\Common\Collections\Collection&iterable<App\Entity\ProductArticle>

My variable declaration:

#[ORM\OneToMany(mappedBy: 'product', targetEntity: ProductArticle::class)]
    /** @var  Collection<int, ProductArticle> */
    private Collection $productArticles;

How can I use both: annotation to declare variable for ORM and PHPDoc comment to declare Collection type?


Solution

PHPDoc needs to be above the attribute.

class Foo
{
    /** @var  Collection<int, ProductArticle> */
    #[ORM\OneToMany(mappedBy: 'product', targetEntity: ProductArticle::class)]
    private Collection $productArticles;
}

This is a bug with the parser library (nikic/php-parser) PHPStan uses. So it has to be this way for now.



Answered By - Can Vural
Answer Checked By - Robin (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Tuesday, October 18, 2022

[FIXED] How to make combination of two columns unique in Symfony 4 using Doctrine?

 October 18, 2022     doctrine, doctrine-orm, php, symfony     No comments   

Issue

I have a table named 'student_assignment' in which I have multiple columns from which I am showing 2 of them below:

Both of these columns are also foreign keys.

StudentId   assignmentId
    10           7           -> allowed
    10           8           -> allowed
    11           7           -> allowed
    11           7           -> not allowed, the combination of 11 7 already exists in table

I have tried this in my entity file, but it does not work.

/**
 * Webkul\CampusConnect\Entity\StudentAssignment
 *
 * @Table(name="student_assignment", 
 *    uniqueConstraints={
 *        @UniqueConstraint(name="assignment_unique", 
 *            columns={"student", "assignment"})
 *    }
 * )
 * @Entity
 */

Please how to implement this using ORM in symfony 4.

I have a link which does ther same but in Mysql. I want the solution for Symfony ORM. enter link description here

Error:

[Semantical Error] The annotation "@Table" in class Webkul\CampusConnect\En tity\StudentAssignment was never imported. Did you maybe forget to add a "u se" statement for this annotation?

Entity:

namespace Webkul\CampusConnect\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Table;

/**
 * Webkul\CampusConnect\Entity\StudentAssignment
 *
 * @ORM\Table(name="student_assignment", 
 *    uniqueConstraints={
 *        @UniqueConstraint(name="assignment_unique", 
 *            columns={"student", "assignment"})
 *    }
 * )
 * @Entity
 */
class StudentAssignment
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Webkul\CampusConnect\Entity\Student", inversedBy="studentAssignments")
     * @ORM\JoinColumn(onDelete="CASCADE")
     */
    private $student;

Solution

You've edited, but you weren't using ORM as an imported alias, that was number 1 (see comments).

Then you missed adding ORM to the inner configuration, e.g. @ORM\UniqueConstraint instead of @UniqueConstraint. Also, the configuration of UniqueConstraint requires the use of the column names, not property.

You've not provided both sides of the join table'esque OtM - MtO relation, but I'll assume it exists. You should have:

namespace Webkul\CampusConnect\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(
 *    name="student_assignment", 
 *    uniqueConstraints={
 *        @ORM\UniqueConstraint(name="assignment_unique", columns={"student_id", "assignment_id"})
 *    }
 * )
 * @Entity
 */
class StudentAssignment
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(name="id", type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Webkul\CampusConnect\Entity\Student", inversedBy="studentAssignments")
     * @ORM\JoinColumn(name="student_id", onDelete="CASCADE")
     */
    private $student;

    /**
     * @ORM\ManyToOne(targetEntity="Webkul\CampusConnect\Entity\Assignment", inversedBy="studentAssignments")
     * @ORM\JoinColumn(name="assignment_id", onDelete="CASCADE")
     */
    private $assignment;

    // ...
}


Answered By - rkeet
Answer Checked By - Marie Seifert (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How can a bundle provide new column type to Doctrine ORM?

 October 18, 2022     doctrine-orm, php, symfony     No comments   

Issue

I would like to provide custom (doctrine orm) column type in a bundle, but I don't know how to register them:

Type::addType('my_col', 'MyColType');
$em->getConnection()->getDatabasePlatform()
   ->registerDoctrineTypeMapping('my_col', 'my_col');

MyBundleClass::boot() looks like a good place, but inside boot(), I can not access container.

Thank you.


Solution

You can use the property container of the Bundle class to retrieve the right entity manager (usually doctrine.orm.default_entity_manager)

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Doctrine\ORM\EntityManager;

class AcmeMyBundle extends Bundle
{
    /**
     * {@inheritdoc}
     */
    public function boot()
    {
        /* @var EntityManager $em */
        $em = $this->container->get('doctrine.orm.default_entity_manager');

        $em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping(...);
    }


Answered By - rolebi
Answer Checked By - Mary Flores (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Wednesday, October 5, 2022

[FIXED] How to cache phpexcel with symfony2 phpexcel bundle

 October 05, 2022     doctrine-orm, phpexcel, symfony     No comments   

Issue

Is there any way to speed up my function to work with larger excel file? There is my code:

$excelService = $this->get('xls.service_xls5');

$excelObj = $this->get('xls.load_xls5')->setReadDataOnly(true)->load('../web/bundles/static/pliki/'.$name); 

$sheet = $excelObj->getSheet(0); 
$highestRow = $sheet->getHighestRow(); 
$highestColumn = $sheet->getHighestColumn(); 

for ($row = 1; $row <= $highestRow; $row++){ 

    $nazwaKlienta = $sheet->getCellByColumnAndRow(0,$row)->getValue();
    $ulica = $sheet->getCellByColumnAndRow(1,$row)->getValue();
    $miasto = $sheet->getCellByColumnAndRow(2,$row)->getValue();
    $kod = $sheet->getCellByColumnAndRow(3,$row)->getValue();
    $notatka = $sheet->getCellByColumnAndRow(4,$row)->getValue();
    $kolor = $sheet->getCellByColumnAndRow(5,$row)->getValue();

    if ($row == $highestRow) {
        $excelObj->disconnectWorksheets(); 
        unset($excelObj); 
    }

    $em = $this->getDoctrine()->getManager();
    $klient = new Klient();
    $klient->setNazwa($nazwaKlienta);
    (...)

    $em->persist($klient);
    $em->flush();
    $klientId = $klient->getId();
    $punkt = new Punkty();
    $punkt->setIdKlienta($klient);
    $em->persist($punkt);
    $em->flush();
}

This code open an existing excel file and read all data and write it to database. It works fine with data amount about 150-200 but i need to work with data about 2000 rows. How can i cache it with phpexcelBundle or maybe there is another way?


Solution

A cache mechanism could help you:

Is there $name into the db? if yes don't do the job just take the value from the db.

You could also use APC setting an expiration time of the given $name.

please have a look to:

rationally-boost-your-symfony2-application-with-caching-tips-and-monitoring

slide 27.



Answered By - liuggio
Answer Checked By - Cary Denson (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Wednesday, August 31, 2022

[FIXED] How to set up a Symfony Standard Edition application without vendor, but with PEAR

 August 31, 2022     doctrine-orm, pear, symfony, zend-server     No comments   

Issue

I'm following the basic description of Symfony Standard Edition on how to set up a new application with Symfony 2.

The thing is, this and all other guides explains that I need to have a vendors directory, where I should place third part libraries, such as Doctrine, Swiftmailer and Symfony itself.

However, Zend Server PEAR already comes with almost all of those libraries. As you know, I can even update my Symfony and Doctrine versions with the pear update command.

The question is: how can I set up that basic application to effectively use my PEAR libraries and ignore the vendors directory?

This is my version of app/autoload.php:

use Symfony\Component\ClassLoader\UniversalClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;

$loader = new UniversalClassLoader();
$loader->registerNamespaces(array(
    'Symfony'          => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
    'Sensio'           => __DIR__.'/../vendor/bundles',
    'JMS'              => __DIR__.'/../vendor/bundles',
    'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
    'Doctrine\\DBAL'   => __DIR__.'/../vendor/doctrine-dbal/lib',
    'Doctrine'         => __DIR__.'/../vendor/doctrine/lib',
    'Monolog'          => __DIR__.'/../vendor/monolog/src',
    'Assetic'          => __DIR__.'/../vendor/assetic/src',
    'Metadata'         => __DIR__.'/../vendor/metadata/src',
));
$loader->registerPrefixes(array(
    'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib',
    'Twig_'            => __DIR__.'/../vendor/twig/lib',
));

// intl
if (!function_exists('intl_get_error_code')) {
    require_once __DIR__.'/../vendor/symfony/src/Symfony/Component/Locale/Resources/stubs/functions.php';

    $loader->registerPrefixFallbacks(array(__DIR__.'/../vendor/symfony/src/Symfony/Component/Locale/Resources/stubs'));
}

$loader->registerNamespaceFallbacks(array(
    __DIR__.'/../src',
));
$loader->register();

AnnotationRegistry::registerLoader(function($class) use ($loader) {
    $loader->loadClass($class);
    return class_exists($class, false);
});
AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');

// Swiftmailer needs a special autoloader to allow
// the lazy loading of the init file (which is expensive)
require_once __DIR__.'/../vendor/swiftmailer/lib/classes/Swift.php';
Swift::registerAutoload(__DIR__.'/../vendor/swiftmailer/lib/swift_init.php');

It's clear that the autoloader is being configured to load the libraries from the vendor directory. I wanna use the libraries that comes with the pear package, however. How would that be implemented?


Solution

Just edit app/autoload.php to point to your pear delivered packages. Be careful that you use the proper versions. Probably be safer to just use the packages delivered with Symfony and then add paths to any additional libraries.



Answered By - Cerad
Answer Checked By - Dawn Plyler (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Monday, August 1, 2022

[FIXED] How to disable query logging in console while load Doctrine fixtures?

 August 01, 2022     doctrine, doctrine-orm, php, symfony     No comments   

Issue

I have a fixtures that loads a huge amount of data and all the time I run into this error:

Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 16777224 bytes) in /var/www/html/platform-cm/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php on line 65

[Symfony\Component\Debug\Exception\OutOfMemoryException] Error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 16777224 bytes)

After research a bit I found this post where I read that logging could be the cause of the issue because AppKernel is instantiated with debug set to true by default and then the SQL commands get stored in memory for each iteration.

The first attempt without disable the debug at AppKernel was run the command as:

doctrine:fixtures:load --no-debug

But I didn't get luck since the same error still.

The second attempt was disable the debug at config_dev.yml but this is not recommended since I am getting ride of every logs but didn't work neither.

monolog:
    handlers:
        main:
            type:   stream
            path:   "%kernel.logs_dir%/%kernel.environment%.log"
            level:  debug
#        console:
#            type:   console
#            bubble: false
#            verbosity_levels:
#                VERBOSITY_VERBOSE: INFO
#                VERBOSITY_VERY_VERBOSE: DEBUG
#            channels: ["!doctrine"]
#        console_very_verbose:
#            type:   console
#            bubble: false
#            verbosity_levels:
#                VERBOSITY_VERBOSE: NOTICE
#                VERBOSITY_VERY_VERBOSE: NOTICE
#                VERBOSITY_DEBUG: DEBUG
#            channels: ["doctrine"]

So, this is how my fixture looks like:

class LoadMsisdn extends AbstractFixture implements OrderedFixtureInterface
{
    public function getOrder()
    {
        return 13;
    }

    public function load(ObjectManager $manager)
    {
        $content = file_get_contents('number.txt');
        $numbers = explode(',', $content);
        shuffle($numbers);

        foreach ($numbers as $key => $number) {
            $msisdn = new Msisdn();
            $msisdn->setMsisdn($number);
            $msisdn->setBlocked((rand(1, 1000) % 10) < 7);
            $msisdn->setOperator($this->getReference('operator-' . rand(45, 47)));

            $this->addReference('msisdn-' . $key, $msisdn);
            $manager->persist($msisdn);
        }

        $manager->flush();
    }
}

How do I disable the logger if I need to do it from EntityManager as shown in a answer on the same post?

$em->getConnection()->getConfiguration()->setSQLLogger(null);

Solution

The object manager that is being passed into the load method is an instance of the the entity manager (Doctrine\Common\Persistence\ObjectManager is just an interface that the entity/document/etc managers implement).

This mean that you can use the same command as in your question to nullify the SQL logger like..

$manager->getConnection()->getConfiguration()->setSQLLogger(null);

One thing to note is that the default logging setting for a DBAL connection is %kernel.debug% meaning that, unless you have overridden it in your config, the logging should only happen in the dev environment. I can see you have tried using the --no-debug option but I can only assume that, as the logger is set during the container compilation, that it doesn't unset it to the container not being rebuilt.


Symfony 3/4/5 approach in test case

final class SomeTestCase extends KernelTestCase
{
    protected function setUp(): void
    {
        $this->bootKernel('...');

        // @see https://stackoverflow.com/a/35222045/1348344
        // disable Doctrine logs in tests output
        $entityManager = self::$container->get(EntityManagerInterface::class);
        $entityManager->getConfiguration();
        $connection = $entityManager->getConnection();

        /** @var Configuration $configuration */
        $configuration = $connection->getConfiguration();
        $configuration->setSQLLogger(null);
    }
}


Answered By - qooplmao
Answer Checked By - Candace Johnson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Friday, July 22, 2022

[FIXED] How to get image preview in symfony collection images

 July 22, 2022     doctrine-orm, html, jquery, php-5.4, symfony-2.3     No comments   

Issue

I am working with SonataAdminBundle in a symfony2 project in which my admin class is as given below :

class ApartmentsAdmin extends Admin
 {
     protected function configureFormFields(FormMapper $formMapper)
      {
         ->add('images', 'collection', array(
                'type' => new ImageType(),
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => true,
                'mapped' => true,
                'label' => false,
                'required' => false,
                'label' => 'Apartment Images'
              ));
           }
       }

It works fine ; but my issue is that , on edit page i want to let see preview of each images included in collection of images. In documentation of "SonataAdminBundle" image preview is given for one individual image , but how is it possible to make preview for collection images ..

Please suggest me..

Thanks a lot ..


Solution

Finally i am able to do this through "custom Form Type Extension" for which documentation is given on the link "http://symfony.com/doc/current/cookbook/form/create_form_type_extension.html" .



Answered By - Ajeet Varma
Answer Checked By - Pedro (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Thursday, May 12, 2022

[FIXED] How to change default doctrine entity scheme?

 May 12, 2022     doctrine-orm, mysql, php, symfony     No comments   

Issue

The default one to one relation scheme is

CREATE TABLE Product (
    id INT AUTO_INCREMENT NOT NULL,
    shipping_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipping (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);

But I need to use this scheme but with it some modification ON DELETE SET NULL ON UPDATE SET NULL;:

CREATE TABLE Product (
    id INT AUTO_INCREMENT NOT NULL,
    shipping_id INT DEFAULT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipping (
    id INT AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id) ON DELETE SET NULL ON UPDATE SET NULL;

Can I create this scheme with doctrine entity configuration?

P.S. I has two entities: Vendor and Image

BW\ShopBundle\Entity\Vendor:
    type: entity
    table: vendors
    id:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
    oneToOne:
        image:
            targetEntity: BW\FileBundle\Entity\Image
            cascade: [persist, remove]

and

BW\FileBundle\Entity\Image:
    type: entity
    table: images
    id:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
    fields:
        filename:
            type: string
            length: 255
            nullable: true

And I use a oneToOne Unidirectional relation.

So when I remove Image object, I has an error, becouse I had Unidirectional relation (And it's important to know that I can't to change relation to bidirectional)


Solution

Have you tried the Orphan Removal ?

From the offcial documentation :

There is another concept of cascading that is relevant only when removing entities from collections. If an Entity of type A contains references to privately owned Entities B then if the reference from A to B is removed the entity B should also be removed, because it is not used anymore.

OrphanRemoval works with one-to-one, one-to-many and many-to-many associations

You chould use the orphanRemoval=true option like this :

@OneToOne(targetEntity="Image", orphanRemoval=true)


Answered By - blackbishop
Answer Checked By - Timothy Miller (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to override Doctrine's field association mappings in subclasses when using PHP 8 attributes?

 May 12, 2022     attributes, doctrine, doctrine-orm, php, symfony     No comments   

Issue

How can I define a Doctrine property in a parent class and override the association in a class which extends the parent class? When using annotation, this was implemented by using AssociationOverride, however, I don't think they are available when using PHP 8 attributes

Why I want to:

I have a class AbstractTenantEntity whose purpose is to restrict access to data to a given Tenant (i.e. account, owner, etc) that owns the data, and any entity which extends this class will have tenant_id inserted into the database when created and all other requests will add the tenant_id to the WHERE clause. Tenant typically does not have collections of the various entities which extend AbstractTenantEntity, but a few do. When using annotations, I handled it by applying Doctrine's AssociationOverride annotation to the extended classes which should have a collection in Tenant, but I don't know how to accomplish this when using PHP 8 attributes?


My attempt described below was unsuccessful as I incorrectly thought that the annotation class would magically work with attributes if modified appropriately, but now I see other code must be able to apply the appropriate logic based on the attributes. As such, I abandoned this approach and just made the properties protected and duplicated them in the concrete class.

My attempt:

Tenant entity

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

#[Entity()]
class Tenant
{
    #[Id, Column(type: "integer")]
    #[GeneratedValue]
    private ?int $id = null;

    #[OneToMany(targetEntity: Asset::class, mappedBy: 'tenant')]
    private array|Collection|ArrayCollection $assets;

    // Other properties and typical getters and setters
}

AbstractTenantEntity entity

use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;

abstract class AbstractTenantEntity implements TenantInterface
{
    /**
     * inversedBy performed in child where required
     */
    #[ManyToOne(targetEntity: Tenant::class)]
    #[JoinColumn(nullable: false)]
    protected ?Tenant $tenant = null;

    // Typical getters and setters
}

This is the part which has me stuck. When using annotation, my code would be as follows:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\AssociationOverrides({
 *     @ORM\AssociationOverride(name="tenant", inversedBy="assets")
 * })
 */
class Asset extends AbstractTenantEntity
{
    // Various properties and typical getters and setters
}

But AssociationOverrides hasn't been modified to work with attributes, so based on the official class, I created my own class similar to the others which Doctrine has updated:

namespace App\Mapping;

use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\ORM\Mapping\Annotation;

/**
 * This annotation is used to override association mapping of property for an entity relationship.
 *
 * @Annotation
 * @NamedArgumentConstructor()
 * @Target("ANNOTATION")
 */
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AssociationOverride implements Annotation
{
    /**
     * The name of the relationship property whose mapping is being overridden.
     *
     * @var string
     */
    public $name;

    /**
     * The join column that is being mapped to the persistent attribute.
     *
     * @var array<\Doctrine\ORM\Mapping\JoinColumn>
     */
    public $joinColumns;

    /**
     * The join table that maps the relationship.
     *
     * @var \Doctrine\ORM\Mapping\JoinTable
     */
    public $joinTable;

    /**
     * The name of the association-field on the inverse-side.
     *
     * @var string
     */
    public $inversedBy;

    /**
     * The fetching strategy to use for the association.
     *
     * @var string
     * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
     */
    public $fetch;

    public function __construct(
        ?string $name = null,
        ?array  $joinColumns = null,
        ?string $joinTable = null,
        ?string $inversedBy = null,
        ?string $fetch = null
    ) {
        $this->name    = $name;
        $this->joinColumns = $joinColumns;
        $this->joinTable = $joinTable;
        $this->inversedBy = $inversedBy;
        $this->fetch = $fetch;
        //$this->debug('__construct',);
    }

    private function debug(string $message, string $file='test.json', ?int $options = null)
    {
        $content = file_exists($file)?json_decode(file_get_contents($file), true):[];
        $content[] = ['message'=>$message, 'object_vars'=>get_object_vars($this), 'debug_backtrace'=>debug_backtrace($options)];
        file_put_contents($file, json_encode($content, JSON_PRETTY_PRINT));
    }
}

When validating the mapping, Doctrine complains that target-entity does not contain the required inversedBy. I've spent some time going through the Doctrine source code but have not made much progress.

Does my current approach have merit and if so please fill in the gaps. If not, however, how would you recommend meeting this need?


Solution

It has been resolved by this pr: https://github.com/doctrine/orm/pull/9241

ps: PHP 8.1 is required

#[AttributeOverrides([
new AttributeOverride(
    name: "id",
    column: new Column(name: "guest_id", type: "integer", length: 140)
),
new AttributeOverride(
    name: "name",
    column: new Column(name: "guest_name", nullable: false, unique: true, length: 240)
)]
)]


Answered By - Ben Yan
Answer Checked By - Katrina (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to fetch entities in db while using doctrine fixtures?

 May 12, 2022     doctrine, doctrine-orm, php, symfony     No comments   

Issue

I have been the ContainerAwareInterface on my LoadAdvert class so I can call and use the EntityManager to retrieve users in the database.

However, when executing php bin/console doctrine:fixtures:load, the entity manager retrieves only an empty array, as if there was no user in the db.

<?php
// src/OC/PlatformBundle/DataFixtures/ORM/LoadCategory.php

namespace OC\PlatformBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use OC\PlatformBundle\Entity\Advert;
use OC\UserBundle\Entity\User;

   class LoadAdvert implements FixtureInterface, ContainerAwareInterface
    {
      /**
       * @var ContainerInterface
       */
      private $container;

      public function setContainer(ContainerInterface $container = null)
      {
        $this->container = $container;
      }

      // Dans l'argument de la méthode load, l'objet $manager est l'EntityManager
      public function load(ObjectManager $manager)
      {
        $em = $this->container->get('doctrine.orm.entity_manager');
        $author = $em->getRepository('OCUserBundle:User')->findAll();

        die(var_dump($author));

Solution

Here it is how I do it. The main thing is to set reference for user objects that you create and then call it in other fixture file and assign them to related objects. Also its important the order of execution of fixture data. You cant load related object before you load users.

namespace AcmeBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LoadUserData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
    /** @var  ContainerInterface */
    private $container;

    /**
     * Load data fixtures with the passed EntityManager
     *
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        $userManager = $this->container->get('fos_user.user_manager');

        $user1 = $userManager->createUser();
        $user1->setUsername('username1');
        $user1->setPlainPassword('123');
        $user1->setEmail('example1@gmail.com');
        $user1->setEnabled(true);


        $user2 = $userManager->createUser();
        $user2->setUsername('username2');
        $user2->setPlainPassword('123');
        $user1->setEmail('example2@gmail.com');
        $user2->setEnabled(true);

        $manager->persist($user1);
        $manager->persist($user2);

        $this->setReference('user1', $user1);
        $this->setReference('user2', $user2);

        $manager->flush();
    }

    /**
     * Get the order of this fixture
     *
     * @return integer
     */
    public function getOrder()
    {
        return 1;
    }

    /**
     * Sets the Container. Need it to create new User from fos_user.user_manager service
     *
     * @param ContainerInterface|null $container A ContainerInterface instance or null
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }
}

Then in other fixture file you dont call user as you did from database, you do it like this:

 $author = $this->getReference('user1');

 $article = new Article();
 $article->addAuthor($author);

and then you add this $author object to related object.

Keep in mind to implement OrderedFixtureInterface in this article fixture and set getOrder() to be higher number than the one for users.



Answered By - Lord Zed
Answer Checked By - Senaida (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Wednesday, May 11, 2022

[FIXED] How to use inherited classes with API-Platform

 May 11, 2022     api-platform.com, doctrine-orm, jms-serializer, rest, symfony     No comments   

Issue

I wish to use API-Platform to perform CRUD operations on object hierarchy classes. I found little written when using inherited classes with API-Platform and some but not much more when used with Symfony's serializer, and am looking for better direction on what needs to be implemented differently specifically for inherited classes.

Let's say I have Dog, Cat, and Mouse inherited from Animal where Animal is abstract (see below). These entities have been created using bin/console make:entity, and have only been modified to extend the parent class (as well as their respective repositories) and to have Api-Platform annotation added.

How should groups be used with inherited classes? Should each of the child classes (i.e. Dog, Cat, Mouse) have their own group or should just the parent animal group be used? When using the animal group for all, some routes respond with The total number of joined relations has exceeded the specified maximum. ..., and when mixed, sometimes get Association name expected, 'miceEaten' is not an association.. Will these groups also allow ApiPropertys on the parent apply to the child entities (i.e. Animal::weight has a default openapi_context example value of 1000)?

API-Platform does not discuss CTI or STI and the only relevant reference I found in the documentation was regarding MappedSuperclass. Need a MappedSuperclass be used in addition to CLI or STI? Note that I tried applying MappedSuperclass to Animal, but received an error as expected.

Based on this post as well as others, it appears that the preferred RESTful implementation is to use a single endpoint /animals instead of individual /dogs, /cats, and /mice. Agree? How could this be implemented with API-Platform? If the @ApiResource() annotation is applied only to Animal, I get this single desired URL but don't get the child properties for Dog, Cat, and Mouse in the OpenAPI Swagger documentation nor the actual request. If the @ApiResource() annotation is applied only to Dog, Cat, and Mouse, then there is no way to get a combined collection of all animals and I have multiple endpoints. Need it be applied to all three? It appears that OpenApi's key words oneOf, allOf, and anyOf might provide a solution as described by this stackoverflow answer as well as this Open-Api specification. Does Api-Platform support this and if so how?

Animal

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use App\Repository\AnimalRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={"get", "post"},
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"animal:read", "dog:read", "cat:read", "mouse:read"}},
 *     denormalizationContext={"groups"={"animal:write", "dog:write", "cat:write", "mouse:write"}}
 * )
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string", length=32)
 * @ORM\DiscriminatorMap({"dog" = "Dog", "cat" = "Cat", "mouse" = "Mouse"})
 * @ORM\Entity(repositoryClass=AnimalRepository::class)
 */
abstract class Animal
{
    /**
     * @Groups({"animal:read"})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @Groups({"animal:read", "animal:write"})
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @Groups({"animal:read", "animal:write"})
     * @ORM\Column(type="string", length=255)
     */
    private $sex;

    /**
     * @Groups({"animal:read", "animal:write"})
     * @ORM\Column(type="integer")
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"=1000
     *         }
     *     }
     * )
     */
    private $weight;

    /**
     * @Groups({"animal:read", "animal:write"})
     * @ORM\Column(type="date")
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"="2020/1/1"
     *         }
     *     }
     * )
     */
    private $birthday;

    /**
     * @Groups({"animal:read", "animal:write"})
     * @ORM\Column(type="string", length=255)
     */
    private $color;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getSex(): ?string
    {
        return $this->sex;
    }

    public function setSex(string $sex): self
    {
        $this->sex = $sex;

        return $this;
    }

    public function getWeight(): ?int
    {
        return $this->weight;
    }

    public function setWeight(int $weight): self
    {
        $this->weight = $weight;

        return $this;
    }

    public function getBirthday(): ?\DateTimeInterface
    {
        return $this->birthday;
    }

    public function setBirthday(\DateTimeInterface $birthday): self
    {
        $this->birthday = $birthday;

        return $this;
    }

    public function getColor(): ?string
    {
        return $this->color;
    }

    public function setColor(string $color): self
    {
        $this->color = $color;

        return $this;
    }
}

Dog

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use App\Repository\DogRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={"get", "post"},
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"dog:read"}},
 *     denormalizationContext={"groups"={"dog:write"}}
 * )
 * @ORM\Entity(repositoryClass=DogRepository::class)
 */
class Dog extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"dog:read", "dog:write"})
     */
    private $playsFetch;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"dog:read", "dog:write"})
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"="red"
     *         }
     *     }
     * )
     */
    private $doghouseColor;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToMany(targetEntity=Cat::class, mappedBy="dogsChasedBy")
     * @MaxDepth(2)
     * @Groups({"dog:read", "dog:write"})
     */
    private $catsChased;

    public function __construct()
    {
        $this->catsChased = new ArrayCollection();
    }

    public function getPlaysFetch(): ?bool
    {
        return $this->playsFetch;
    }

    public function setPlaysFetch(bool $playsFetch): self
    {
        $this->playsFetch = $playsFetch;

        return $this;
    }

    public function getDoghouseColor(): ?string
    {
        return $this->doghouseColor;
    }

    public function setDoghouseColor(string $doghouseColor): self
    {
        $this->doghouseColor = $doghouseColor;

        return $this;
    }

    /**
     * @return Collection|Cat[]
     */
    public function getCatsChased(): Collection
    {
        return $this->catsChased;
    }

    public function addCatsChased(Cat $catsChased): self
    {
        if (!$this->catsChased->contains($catsChased)) {
            $this->catsChased[] = $catsChased;
            $catsChased->addDogsChasedBy($this);
        }

        return $this;
    }

    public function removeCatsChased(Cat $catsChased): self
    {
        if ($this->catsChased->removeElement($catsChased)) {
            $catsChased->removeDogsChasedBy($this);
        }

        return $this;
    }
}

Cat

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use App\Repository\CatRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={"get", "post"},
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"cat:read"}},
 *     denormalizationContext={"groups"={"cat:write"}}
 * )
 * @ORM\Entity(repositoryClass=CatRepository::class)
 */
class Cat extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"cat:read", "cat:write"})
     */
    private $likesToPurr;

    /**
     * #@ApiSubresource()
     * @ORM\OneToMany(targetEntity=Mouse::class, mappedBy="ateByCat")
     * @MaxDepth(2)
     * @Groups({"cat:read", "cat:write"})
     */
    private $miceEaten;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToMany(targetEntity=Dog::class, inversedBy="catsChased")
     * @MaxDepth(2)
     * @Groups({"cat:read", "cat:write"})
     */
    private $dogsChasedBy;

    public function __construct()
    {
        $this->miceEaten = new ArrayCollection();
        $this->dogsChasedBy = new ArrayCollection();
    }

    public function getLikesToPurr(): ?bool
    {
        return $this->likesToPurr;
    }

    public function setLikesToPurr(bool $likesToPurr): self
    {
        $this->likesToPurr = $likesToPurr;

        return $this;
    }

    /**
     * @return Collection|Mouse[]
     */
    public function getMiceEaten(): Collection
    {
        return $this->miceEaten;
    }

    public function addMiceEaten(Mouse $miceEaten): self
    {
        if (!$this->miceEaten->contains($miceEaten)) {
            $this->miceEaten[] = $miceEaten;
            $miceEaten->setAteByCat($this);
        }

        return $this;
    }

    public function removeMiceEaten(Mouse $miceEaten): self
    {
        if ($this->miceEaten->removeElement($miceEaten)) {
            // set the owning side to null (unless already changed)
            if ($miceEaten->getAteByCat() === $this) {
                $miceEaten->setAteByCat(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|Dog[]
     */
    public function getDogsChasedBy(): Collection
    {
        return $this->dogsChasedBy;
    }

    public function addDogsChasedBy(Dog $dogsChasedBy): self
    {
        if (!$this->dogsChasedBy->contains($dogsChasedBy)) {
            $this->dogsChasedBy[] = $dogsChasedBy;
        }

        return $this;
    }

    public function removeDogsChasedBy(Dog $dogsChasedBy): self
    {
        $this->dogsChasedBy->removeElement($dogsChasedBy);

        return $this;
    }
}

Mouse

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use App\Repository\MouseRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={"get", "post"},
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"mouse:read"}},
 *     denormalizationContext={"groups"={"mouse:write"}}
 * )
 * @ORM\Entity(repositoryClass=MouseRepository::class)
 */
class Mouse extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"mouse:read", "mouse:write"})
     */
    private $likesCheese;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToOne(targetEntity=Cat::class, inversedBy="miceEaten")
     * @MaxDepth(2)
     * @Groups({"mouse:read", "mouse:write"})
     */
    private $ateByCat;

    public function getLikesCheese(): ?bool
    {
        return $this->likesCheese;
    }

    public function setLikesCheese(bool $likesCheese): self
    {
        $this->likesCheese = $likesCheese;

        return $this;
    }

    public function getAteByCat(): ?Cat
    {
        return $this->ateByCat;
    }

    public function setAteByCat(?Cat $ateByCat): self
    {
        $this->ateByCat = $ateByCat;

        return $this;
    }
}

Supplementary information for MetaClass's answer

Below is my approach to repositories and the key takeaway is the most specific class sets the entity in the constructor.

class AnimalRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry, ?string $class=null)
    {
        parent::__construct($registry, $class??Animal::class);
    }
}
class DogRepository extends AnimalRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Dog::class);
    }
}
// Cat and Mouse Repository similar

I would have liked to follow "the common preference for REST in general to use a single endpoint /animals", but understand your rational "for choosing individual ones for /dogs, /cats, and /mice". To overcome your reasons, I also considered making Animal concrete and using composition for polymorphism so that Animal would have some sort of animal type object. I suppose eventually Doctrine inheritance would still be needed to allow Animal to have a one-to-one relationship with this object, but the only properties would be the PK ID and discriminator. I will likely give up this pursuit.

Not sure whether I agree or not with your approach of not using denormalizationContext, but will take your approach unless circumstances change and I need more flexibility.

I do not understand your use of the label. At first I thought it was some unique identifier or maybe some means to expose the discriminator, but don't think the case. Please elaborate.

Regarding "To avoid repeating the definitions of those properties in each concrete subclass i addes some groups using yaml", my approach was to make properites for the abstract Animal class protected instead of private so that PHP can use reflection, and used groups "animal:read" in abstract Animal and groups "mouse:read", etc in the individual concrete classes, and got the results I desired.

Yes, see your point about limiting results for a list versus a detail.

I originally thought that @MaxDepth would solve the recursive issues, but couldn't get it working. What did work, however, was using @ApiProperty(readableLink=false).

I found some cases where the API-Platform generated swagger specification displayed anyOf in SwaggerUI, but agree API-Platform does not seem to really support oneOf, allOf, or anyOf. Somehow, however, will implementing this be needed? For instance, animal ID was in some other table, documentation would need to oneOf Cat, Dog, or Mouse, no? Or is this long list of types resulting from each combination of serialization groups used instead?


Solution

I don't think a reputable source is available on this subject but i do have long experience with frameworks, abstract user interfaces and php and created MetaClass Tutorial Api Platform so i will try to answer your question myself.

The tutorial aims to cover the common ground of most CRUD and Search apps for both an api platform api and a react client generated with the api platform client generator. The tutorial does not cover inheritance and polymorphism because i do not think it occurs in many CRUD and Search apps but it adresses many aspects that do, for an overview see the list of chapters in the readme of the master branch. Api Platform offers a lot of generic functionality for the api of such apps out of the box that only needs to be configured for specific resources and operations. In the react branches this led to recurring patterns and refactoring into common components and eventually to an extended react client generator to accompany the tutorial. The scheme of serialization groups in this answer is a bit more generic because my understanding of the subject has improved over time.

Your classes worked out of the box on Api Platform 2.6 except for the repository classes that where not included. I removed them from the annotation as right now none of their specific methods seem to be called. You can allways add them again when your need them.

Against the common preference for REST in general to use a single endpoint /animals i chose for individual ones for /dogs, /cats, and /mice because:

  1. Api Platform identifies instances of resource classes by iri's that refer to these specific endpoints and inludes them as values of @id whenever these instances are serialized. The client generater, and i suppose the admin client too, depend on these endpoints to work for crud operations,
  2. With Api Platform specific post operations work out of the box with doctrine orm. An endpoint /animals would require a custom Denormalizer that can decide which concrete class to instantiate.
  3. With serialization groups specific end points give more control over serializations. Without that is it hard to get serialization compatible with the way it is done in chapter 4 of the tutorial,
  4. In many of the extension points of Api Platform it is easy to make things work for a spefic resource and all examples in the docs make use of that. Making them specific for the actual concrete subclass of the object at hand is undocumented and may not allways be possible.

I only include the /animals get collection operation because that allows the client to retrieve, search and sort a polymophic collection of animals in a single request.

In line with chapter 4 of the tutorial i removed the write annotation groups. Api Platforms deserialization already allows the client to only include those properties with post, put and patch that hold data and are meant be set, so the only purpose of deserialization groups can be to disallow certain properties to be set through (certain operations of) the api or to allow the creation of related objects through nested documents. When i tried to add a new cat by posting it as value of $ateByCat of a mouse i got error "Nested documents for attribute "ateByCat" are not allowed. Use IRIs instead." The same happened with adding one through Dog::$catsChased, so security by operation with certain roles granted does not seem to be compromised without write annotation groups. Seems like a sound default to me.

I added a ::getLabel method to Animal to represent each by a single string (annotated as http://schema.org/name). Basic CRUD and Search clients primarily show a single type of entities to the user and represent related entities this way. Having a specific schema.org/name property is more convenient for the client and making it a derived property is more flexible then then adding different properties depending on the type of entity. The label property is the only property that is added to the "related" group. This group is added to the normalization context of each type so that for the "get" operations of Cat, Doc and Mouse it is the only property serialized for related objects:

{
  "@context": "/contexts/Cat",
  "@id": "/cats/1",
  "@type": "Cat",
  "likesToPurr": true,
  "miceEaten": [
    {
      "@id": "/mice/3",
      "@type": "Mouse",
      "label": "2021-01-13"
    }
  ],
  "dogsChasedBy": [
    {
      "@id": "/dogs/2",
      "@type": "Dog",
      "label": "Bella"
    }
  ],
  "name": "Felix",
  "sex": "m",
  "weight": 12,
  "birthday": "2020-03-13T00:00:00+00:00",
  "color": "grey",
  "label": "Felix"
}

To get this result i had to make the serializaton groups of inherited properties specific to the concrete subclasses. To avoid repeating the definitions of those properties in each concrete subclass i addes some groups using yaml (added at the bottom of this answer). To make them work a added the following to api/config/packages/framework.yaml:

serializer:
    mapping:
        paths: ['%kernel.project_dir%/config/serialization']

The yaml configuratons blend in nicely with the annotations and only override those from the Animal class.

In line with chapter 4 of the tutorial i also added list groups for a more limited set of properties to be included in the result of get collection operations. When collections of entities are presented to the user the amount of information can soon become overkill and/or cloth up the screen, even with pagination. If the puprpose of the client(s) is clear to the api developer, making a selection in the api will speed up data transfer, especially if to-many relationships are left out. This results in serialization of a collection of mice like this:

{
  "@context": "/contexts/Mouse",
  "@id": "/mice",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/mice/3",
      "@type": "Mouse",
      "ateByCat": {
        "@id": "/cats/1",
        "@type": "Cat",
        "label": "Felix"
      },
      "label": "2021-01-13",
      "name": "mimi",
      "birthday": "2021-01-13T00:00:00+00:00",
      "color": "grey"
    }
  ],
  "hydra:totalItems": 1
}

The configuration of the serialization of get /animals is kind of a compromise. If i include the list groups of all subclasses:

 *     collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"cat:list", "dog:list", "mouse:list", "related"}}
 *         },
 *     },

i get a nice polymorhic response, but related objects also containing all properties of the list group of their types instead of only the label:

{
  "@context": "/contexts/Animal",
  "@id": "/animals",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/cats/1",
      "@type": "Cat",
      "likesToPurr": true,
      "name": "Felix",
      "birthday": "2020-03-13T00:00:00+00:00",
      "color": "grey",
      "label": "Felix"
    },
    {
      "@id": "/dogs/2",
      "@type": "Dog",
      "playsFetch": true,
      "name": "Bella",
      "birthday": "2019-03-13T00:00:00+00:00",
      "color": "brown",
      "label": "Bella"
    },
    {
      "@id": "/mice/3",
      "@type": "Mouse",
      "ateByCat": {
        "@id": "/cats/1",
        "@type": "Cat",
        "likesToPurr": true,
        "name": "Felix",
        "birthday": "2020-03-13T00:00:00+00:00",
        "color": "grey",
        "label": "Felix"
      },
      "label": "2021-01-13",
      "name": "mimi",
      "birthday": "2021-01-13T00:00:00+00:00",
      "color": "grey"
    }
  ],
  "hydra:totalItems": 3
}

This is nice for the example at hand, but with more relationships it can get a litte large so for a generic compromise i only include "animal:list" and "referred", resulting in a smaller response:

{
  "@context": "/contexts/Animal",
  "@id": "/animals",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/cats/1",
      "@type": "Cat",
      "name": "Felix",
      "color": "grey",
      "label": "Felix"
    },
    {
      "@id": "/dogs/2",
      "@type": "Dog",
      "name": "Bella",
      "color": "brown",
      "label": "Bella"
    },
    {
      "@id": "/mice/3",
      "@type": "Mouse",
      "ateByCat": {
        "@id": "/cats/1",
        "@type": "Cat",
        "name": "Felix",
        "color": "grey",
        "label": "Felix"
      },
      "label": "2021-01-13",
      "name": "mimi",
      "color": "grey"
    }
  ],
  "hydra:totalItems": 3
}

As you can see polymorhism is still possible (ateByCat) and the problem does get smaller but it does not disappear. The problem can not be solved with serialization groups because seen from the serialization context the relationship Cat eats Mouse is recursive. A better solution could be to decorate api_platform.serializer.context_builder to add a custom callback for the properties of the to-one recursive relationships, but the problem of serializing recursive relations is not specific to inheritance and therefore out of the scope of this question so for now i do not elaborate on this solution.

Api Platform 2.6 does not support oneOf, allOf, or anyOf. Instead it produces quite a long list of types resulting from each combination of serialazation groups used, each with all included properties in a flat list. The resulting json IMHO is too large to include here so i only include the list of type names:

Animal-animal.list_related
Animal.jsonld-animal.list_related
Cat
Cat-cat.list_related
Cat-cat.read_cat.list_related
Cat-dog.read_dog.list_related
Cat-mouse.list_related
Cat-mouse.read_mouse.list_related
Cat.jsonld
Cat.jsonld-cat.list_related
Cat.jsonld-cat.read_cat.list_related
Cat.jsonld-dog.read_dog.list_related
Cat.jsonld-mouse.list_related
Cat.jsonld-mouse.read_mouse.list_related
Dog
Dog-cat.read_cat.list_related
Dog-dog.list_related
Dog-dog.read_dog.list_related
Dog.jsonld
Dog.jsonld-cat.read_cat.list_related
Dog.jsonld-dog.list_related
Dog.jsonld-dog.read_dog.list_related
Greeting
Greeting.jsonld
Mouse
Mouse-cat.read_cat.list_related
Mouse-mouse.list_related
Mouse-mouse.read_mouse.list_related
Mouse.jsonld
Mouse.jsonld-cat.read_cat.list_related
Mouse.jsonld-mouse.list_related
Mouse.jsonld-mouse.read_mouse.list_related 

If you paste the code below in corresponding files in the api platform standard edition and make the described configuration you should be able to retrieve the entire openapi scheme from https://localhost/docs.json

Code

<?php
// api/src/Entity/Animal.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"animal:list", "related"}}
 *         },
 *     },
 *     itemOperations={},
 * )
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string", length=32)
 * @ORM\DiscriminatorMap({"dog" = "Dog", "cat" = "Cat", "mouse" = "Mouse"})
 * @ORM\Entity()
 */
abstract class Animal
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"animal:list"})
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $sex;

    /**
     * @ORM\Column(type="integer")
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"=1000
     *         }
     *     }
     * )
     */
    private $weight;

    /**
     * @ORM\Column(type="date")
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"="2020/1/1"
     *         }
     *     }
     * )
     */
    private $birthday;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"animal:list"})
     */
    private $color;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getSex(): ?string
    {
        return $this->sex;
    }

    public function setSex(string $sex): self
    {
        $this->sex = $sex;

        return $this;
    }

    public function getWeight(): ?int
    {
        return $this->weight;
    }

    public function setWeight(int $weight): self
    {
        $this->weight = $weight;

        return $this;
    }

    public function getBirthday(): ?\DateTimeInterface
    {
        return $this->birthday;
    }

    public function setBirthday(\DateTimeInterface $birthday): self
    {
        $this->birthday = $birthday;

        return $this;
    }

    public function getColor(): ?string
    {
        return $this->color;
    }

    public function setColor(string $color): self
    {
        $this->color = $color;

        return $this;
    }

    /**
     * Represent the entity to the user in a single string
     * @return string
     * @ApiProperty(iri="http://schema.org/name")
     * @Groups({"related"})
     */
    function getLabel() {
        return $this->getName();
    }

}

<?php
// api/src/Entity/Cat.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"cat:list", "related"}}
 *         },
 *         "post"
 *     },
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"cat:read", "cat:list", "related"}}
 * )
 * @ORM\Entity()
 */
class Cat extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"cat:list"})
     */
    private $likesToPurr;

    /**
     * #@ApiSubresource()
     * @ORM\OneToMany(targetEntity=Mouse::class, mappedBy="ateByCat")
     * @MaxDepth(2)
     * @Groups({"cat:read"})
     */
    private $miceEaten;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToMany(targetEntity=Dog::class, inversedBy="catsChased")
     * @MaxDepth(2)
     * @Groups({"cat:read"})
     */
    private $dogsChasedBy;

    public function __construct()
    {
        $this->miceEaten = new ArrayCollection();
        $this->dogsChasedBy = new ArrayCollection();
    }

    public function getLikesToPurr(): ?bool
    {
        return $this->likesToPurr;
    }

    public function setLikesToPurr(bool $likesToPurr): self
    {
        $this->likesToPurr = $likesToPurr;

        return $this;
    }

    /**
     * @return Collection|Mouse[]
     */
    public function getMiceEaten(): Collection
    {
        return $this->miceEaten;
    }

    public function addMiceEaten(Mouse $miceEaten): self
    {
        if (!$this->miceEaten->contains($miceEaten)) {
            $this->miceEaten[] = $miceEaten;
            $miceEaten->setAteByCat($this);
        }

        return $this;
    }

    public function removeMiceEaten(Mouse $miceEaten): self
    {
        if ($this->miceEaten->removeElement($miceEaten)) {
            // set the owning side to null (unless already changed)
            if ($miceEaten->getAteByCat() === $this) {
                $miceEaten->setAteByCat(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|Dog[]
     */
    public function getDogsChasedBy(): Collection
    {
        return $this->dogsChasedBy;
    }

    public function addDogsChasedBy(Dog $dogsChasedBy): self
    {
        if (!$this->dogsChasedBy->contains($dogsChasedBy)) {
            $this->dogsChasedBy[] = $dogsChasedBy;
        }

        return $this;
    }

    public function removeDogsChasedBy(Dog $dogsChasedBy): self
    {
        $this->dogsChasedBy->removeElement($dogsChasedBy);

        return $this;
    }
}

<?php
// api/src/Entity/Dog.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"dog:list", "related"}}
 *         },
 *         "post"
 *     },
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"dog:read", "dog:list", "related"}},
 * )
 * @ORM\Entity()
 */
class Dog extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"dog:list"})
     */
    private $playsFetch;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"dog:read"})
     * @ApiProperty(
     *     attributes={
     *         "openapi_context"={
     *             "example"="red"
     *         }
     *     }
     * )
     */
    private $doghouseColor;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToMany(targetEntity=Cat::class, mappedBy="dogsChasedBy")
     * @MaxDepth(2)
     * @Groups({"dog:read"})
     */
    private $catsChased;

    public function __construct()
    {
        $this->catsChased = new ArrayCollection();
    }

    public function getPlaysFetch(): ?bool
    {
        return $this->playsFetch;
    }

    public function setPlaysFetch(bool $playsFetch): self
    {
        $this->playsFetch = $playsFetch;

        return $this;
    }

    public function getDoghouseColor(): ?string
    {
        return $this->doghouseColor;
    }

    public function setDoghouseColor(string $doghouseColor): self
    {
        $this->doghouseColor = $doghouseColor;

        return $this;
    }

    /**
     * @return Collection|Cat[]
     */
    public function getCatsChased(): Collection
    {
        return $this->catsChased;
    }

    public function addCatsChased(Cat $catsChased): self
    {
        if (!$this->catsChased->contains($catsChased)) {
            $this->catsChased[] = $catsChased;
            $catsChased->addDogsChasedBy($this);
        }

        return $this;
    }

    public function removeCatsChased(Cat $catsChased): self
    {
        if ($this->catsChased->removeElement($catsChased)) {
            $catsChased->removeDogsChasedBy($this);
        }

        return $this;
    }
}

<?php
// api/src/Entity/Mouse.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"mouse:list", "related"}}
 *         },
 *         "post"
 *     },
 *     itemOperations={"get", "put", "patch", "delete"},
 *     normalizationContext={"groups"={"mouse:read", "mouse:list", "related"}},
 * )
 * @ORM\Entity()
 */
class Mouse extends Animal
{
    /**
     * @ORM\Column(type="boolean")
     * @Groups({"mouse:read"})
     */
    private $likesCheese;

    /**
     * #@ApiSubresource()
     * @ORM\ManyToOne(targetEntity=Cat::class, inversedBy="miceEaten")
     * @MaxDepth(2)
     * @Groups({"mouse:list", "animal:list"})
     */
    private $ateByCat;

    public function getLikesCheese(): ?bool
    {
        return $this->likesCheese;
    }

    public function setLikesCheese(bool $likesCheese): self
    {
        $this->likesCheese = $likesCheese;

        return $this;
    }

    public function getAteByCat(): ?Cat
    {
        return $this->ateByCat;
    }

    public function setAteByCat(?Cat $ateByCat): self
    {
        $this->ateByCat = $ateByCat;

        return $this;
    }

    /**
     * Represent the entity to the user in a single string
     * @return string
     * @ApiProperty(iri="http://schema.org/name")
     * @Groups({"related"})
     */
    function getLabel() {
        return $this->getBirthday()->format('Y-m-d');
    }
}

# api/config/serialization/Cat.yaml
App\Entity\Cat:
    attributes:
        name:
            groups: ['cat:list']
        sex:
            groups: ['cat:read']
        weight:
            groups: ['cat:read']
        birthday:
            groups: ['cat:list']
        color:
            groups: ['cat:list']

# api/config/serialization/Dog.yaml
App\Entity\Dog:
    attributes:
        name:
            groups: ['dog:list']
        sex:
            groups: ['dog:read']
        weight:
            groups: ['dog:read']
        birthday:
            groups: ['dog:list']
        color:
            groups: ['dog:list']

# api/config/serialization/Mouse.yaml
App\Entity\Mouse:
    attributes:
        name:
            groups: ['mouse:list']
        sex:
            groups: ['mouse:read']
        weight:
            groups: ['mouse:read']
        birthday:
            groups: ['mouse:list']
        color:
            groups: ['mouse:list']

In reaction to the supplementary information

With respect to the use of the label see chapter 4 of the tutorial (readmes of both branches). The method ::getLabel also brings encapsulation: The representation may be modified without changing the api.

With respect to oneOf, allOf, or anyOf: the long list of types Apip generates is ugly, but i guess it will be work for clients that want to automatically validate property values and abstract user interfaces like the admin client. For designing/scaffolding a client and for customizing an abstract user interface they might be troublesome, so it would be nice if Api Platform would automatically use them appropriately, but for most development teams i don't think an investment into improving the OpenApi docs factory will be earned back. In other words, adapting the clients manually will usually be less work. So for now i do not spend any time on this.

More problematic is that in the JsonLD docs properties of the types from operations specified with "output"= get merged into the type of the resource itself. But this is not related to inheritance.



Answered By - MetaClass
Answer Checked By - Gilberto Lyons (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to remove parameters from query builder?

 May 11, 2022     doctrine-orm, mysql, symfony     No comments   

Issue

I have a problem with my query builder. I am using symfony. What i want to acomplish: I have a given query builder, and i want to count all rows based on this query. So i worked on following solution:

$aliases = $queryBuilder->getRootAliases();
$alias = array_values($aliases)[0];

$cloneQueryBuilder = clone $queryBuilder;
$from = $cloneQueryBuilder->getDQLPart('from');

$cloneQueryBuilder->resetDQLParts();

$newQueryBuilder = $cloneQueryBuilder
    ->select('count(' . $alias . '.id)')
    ->add('from', $from[0]);

$this->total = $newQueryBuilder->getQuery()->getSingleScalarResult();

However im getting an exception : Too many parameters: the query defines 0 parameters and you bound 1 Does anyone knows how to solve it ?


Solution

Calling setParameters should overwrite all existing parameters.



Answered By - Joshua
Answer Checked By - Willingham (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to compare datetime field from Doctrine2 with a date?

 May 11, 2022     datetime, doctrine-orm, symfony     No comments   

Issue

I want to get the items which were created today with by a QueryBuilder from Doctrine2. I want to compare the createdAt(Datetime) field with today parameter(Date). Is it possible to do that in one query?

$qb = $this->createQueryBuilder('i');
$qb->innerJoin('i.type', 'it');
$qb->andWhere('it.name = :type');
$qb->andWhere('i.createdAt < :today');
// i.createdAt == datetime and :today parameter is a date

Solution

one idea is to extract from the date: the year, month and day. And then

$qb->select('p')
   ->where('YEAR(p.postDate) = :year')
   ->andWhere('MONTH(p.postDate) = :month')
   ->andWhere('DAY(p.postDate) = :day');

$qb->setParameter('year', $year)
   ->setParameter('month', $month)
   ->setParameter('day', $day);

MONTH DAY, and YEAR you take out the DoctrineExtensions from

e.g.

DoctrineExtensions

This works for me. You only need the files: day.php, month.php and year.php.....

You get the month e.g.:

    $datetime = new \DateTime("now");
    $month = $datetime->format('m');
    echo $month;

Copy day.php, month.php and year.php to your bundle Xy\TestBundle\Dql Register the new functions in app\config.yml with

doctrine:


orm:
    auto_generate_proxy_classes: %kernel.debug%
    entity_managers:
        default:
            auto_mapping: true
            dql:
                datetime_functions:
                    month: Xy\TestBundle\Dql\Month
                    year: Xy\TestBundle\Dql\Year
                    day: Xy\TestBundle\Dql\Day


Answered By - stwe
Answer Checked By - David Goodson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to disable a doctrine filter in a param converter

 May 11, 2022     doctrine-extensions, doctrine-orm, symfony     No comments   

Issue

I'm using the doctrine softdeleteable extension on a project and have my controller action set up as such.

/**
 * @Route("address/{id}/")
 * @Method("GET")
 * @ParamConverter("address", class="MyBundle:Address")
 * @Security("is_granted('view', address)")
 */
public function getAddressAction(Address $address)
{

This works great as it returns NotFound if the object is deleted, however I want to grant access to users with ROLE_ADMIN to be able to see soft deleted content.

Does there already exist a way to get the param converter to disable the filter or am I going to have to create my own custom param converter?


Solution

There are no existing ways to do it, but I've solved this problem by creating my own annotation, that disables softdeleteable filter before ParamConverter does its job.

AcmeBundle/Annotation/IgnoreSoftDelete.php:

namespace AcmeBundle\Annotation;

use Doctrine\Common\Annotations\Annotation;

/**
 * @Annotation
 * @Target({"CLASS", "METHOD"})
 */
class IgnoreSoftDelete extends Annotation { }

AcmeBundle/EventListener/AnnotationListener.php:

namespace AcmeBundle\EventListener;

use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class AnnotationListener {

    protected $reader;

    public function __construct(Reader $reader) {
        $this->reader = $reader;
    }

    public function onKernelController(FilterControllerEvent $event) {
        if (!is_array($controller = $event->getController())) {
            return;
        }

        list($controller, $method, ) = $controller;

        $this->ignoreSoftDeleteAnnotation($controller, $method);
    }

    private function readAnnotation($controller, $method, $annotation) {
        $classReflection = new \ReflectionClass(ClassUtils::getClass($controller));
        $classAnnotation = $this->reader->getClassAnnotation($classReflection, $annotation);

        $objectReflection = new \ReflectionObject($controller);
        $methodReflection = $objectReflection->getMethod($method);
        $methodAnnotation = $this->reader->getMethodAnnotation($methodReflection, $annotation);

        if (!$classAnnotation && !$methodAnnotation) {
            return false;
        }

        return [$classAnnotation, $classReflection, $methodAnnotation, $methodReflection];
    }

    private function ignoreSoftDeleteAnnotation($controller, $method) {
        static $class = 'AcmeBundle\Annotation\IgnoreSoftDelete';

        if ($this->readAnnotation($controller, $method, $class)) {
            $em = $controller->get('doctrine.orm.entity_manager');
            $em->getFilters()->disable('softdeleteable');
        }
    }

}

AcmeBundle/Resources/config/services.yml:

services:
    acme.annotation_listener:
        class: AcmeBundle\EventListener\AnnotationListener
        arguments: [@annotation_reader]
        tags:
            - { name: kernel.event_listener, event: kernel.controller }

AcmeBundle/Controller/DefaultController.php:

namespace AcmeBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use AcmeBundle\Annotation\IgnoreSoftDelete;
use AcmeBundle\Entity\User;

class DefaultController extends Controller {

    /**
     * @Route("/{id}")
     * @IgnoreSoftDelete
     * @Template
     */
    public function indexAction(User $user) {
        return ['user' => $user];
    }

}

Annotation can be applied to individual action methods and to entire controller classes.



Answered By - VisioN
Answer Checked By - Candace Johnson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to get doctrine2 table alias?

 May 11, 2022     doctrine-orm, symfony     No comments   

Issue

I want to create a method in my a Doctrine2 repository class that takes a QueryBuilder and adds some extra clauses, one of which is an inner join.

How can I find out the table alias that was used to instantiate the querybuilder? Is this something discoverable or should it be a convention across the codebase (and therefore a potential source of bugs)?

My client code is:

public function getPasswordAction($id)
{
    $user = $this->get('security.context')->getToken()->getUser();

    $repository = $this->getDoctrine()
        ->getRepository('TenKPwLockerBundle:Password');

    $query = $repository->createQueryBuilder('p')
        ->where('id = :id')
        ->setParameter('id', $id);

    $query = $repository->userCanReadRestriction($query, $user);
    ...

and my repository class contains:

public function userCanReadRestriction(\Doctrine\ORM\QueryBuilder $builder, \TenK\UserBundle\Entity\User $user)
{
                             // where can I get 'p' from?
    return $builder->innerJoin('p.shares', 's')
        ->where('createdBy = :creator')
        ->orWhere('s.toUser = :toId')
        ->setParameters(array('creator' => $user, 'toUser' => $user));
}

In fact, in the above code, how can I confirm that the QueryBuilder is working with the Password Entity at all?


Solution

You can retrive the select part of your QueryBuilder by calling the getDqlPart('select') method.

More information here.

Then, look how doctrine does to parse select part here.

You can probably do the same to know if the table associated to your repository is called and what is its alias.



Answered By - Olivier Dolbeau
Answer Checked By - Dawn Plyler (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] How to properly set "0000-00-00 00:00:00" as a DateTime in PHP

 May 11, 2022     datetime, doctrine-orm, php, symfony     No comments   

Issue

I have a column viewedAt which is a DATETIME and accept NULL values. It's a software restriction to set that column on each new record as 0000-00-00 00:00:00 so I go through the easy way using Symfony and Doctrine as show below:

$entEmail = new Email();
$entEmail->setViewedAt(new \DateTime('0000-00-00 00:00:00'));

But surprise PHP change that date to this -0001-11-30 00:00:00 and SQL mode in MySQL server is set to STRICT so query fails. I have read a lof of topics here as this, this and some other but didn't answer my doubt at all. I have made a few test with PHP alone and I got almost the same result:

$date = new DateTime("0000-00-00 00:00:00", new DateTimeZone('America/New_York'));
echo $date->format('Y-m-d h:i:s');

// outputs
// -0001-11-30 12:00:00

Even with MySQL mode set to STRICT I did test the following query and it works as image below shows:

INSERT INTO emails(`emails_category`, `deliveredAt`, `viewedAt`, `data`, `createdAt`, `updatedAt`, `reps_id`, `targets_id`) VALUES ("sent","2015-10-29 06:08:25","0000-00-00 00:00:00",null,"2015-10-29 06:08:25","2015-10-29 06:08:25","005800000058eYcAAI","0018000001GO8omAAD")

enter image description here

So viewedAt is accepting 0000-00-00 00:00:00 that value even if is not valid (I think it's kind of NULL or so)

How I can fix this? Is there any work around? What did you suggest me on this specific case where this is a software requirement and can't be changed?

I'm using Symfony 2.7.6, Doctrine 2.5.2 and PHP 5.5.30


Solution

Your architecture is wrong to begin with. The problem is not setting the date itself, which is so obviously invalid that both MySQL and PHP are right to reject it, as there is no year 0 and no day 0 of a month 0, and the output you see is just the correction to a sort-of-valid date (it's 1 year, 1 month and 1 day before 01/01/01). But you're also just missing the point that Doctrine abstracts this away if you just do it right:

$entEmail = new Email();
$entEmail->setViewedAt(null);

Doctrine will now happily put NULL in the database column as it should be.



Answered By - Niels Keurentjes
Answer Checked By - David Marino (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Older Posts Home

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
All Comments
Atom
All Comments

Copyright © PHPFixing