Symfony 4 - Set DateTime - symfony

so I've been following this Database and the Doctrine tutorial: https://symfony.com/doc/current/doctrine.html
the only difference is that I added a created_ts field to it (among some other fields, but they work fine so no need to go into them).
I used the make:entity command to generate my class and the method for setting my created_ts got generated like this:
public function setCreatedTs(\DateTimeInterface $created_ts): self
{
$this->created_ts = $created_ts;
return $this;
}
So in my /index page I went to save a new entity using:
$category->setCreatedTs(\DateTimeInterface::class, $date);
I had a funny feeling this would error and I was right:
Type error: Argument 1 passed to App\Entity\Category::setCreatedTs() must implement interface DateTimeInterface, string given
but I'm not sure how to implement the DateTimeInterface inside the function.. I tried googling but it shows a lot of Symfony2 posts, a few I tried to no avail.
How do I set a datetime value in my entity from the ->set method?
(if there is already an answer, please link. #symfonyScrub)
update
# tried doing this:
$dateImmutable = \DateTime::createFromFormat('Y-m-d H:i:s', strtotime('now')); # also tried using \DateTimeImmutable
$category->setCategoryName('PHP');
$category->setCategoryBio('This is a category for PHP');
$category->setApproved(1);
$category->setGuruId(1);
$category->setCreatedTs($dateImmutable); # changes error from about a string to bool

If your date is the current date, you can just do this:
$category->setCreatedTs(new \DateTime())
Your first error was caused by the strtotime function that returns a timestamp but the \DateTime constructor was expecting a Y-m-d H:i:s format.
That's why instead of creating a valid \DateTime, it returned false.
Even if it's unnecessary in this case, you should have done something like this to create a \DateTime based on a timestamp:
$date = new \DateTime('#'.strtotime('now'));

Related

Entity expected object returned symfony

I'm trying to get an item from the database and pass it to a new item to push to the database.
$post = $entityManager->getRepository('App:Post')
->find($id);
$comment->setPost($post)
the setPost looks like the following:
public function setPost(Post $post): self
{
$this->post = $post;
return $this;
}
and the $post variable:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $post;
But when i try to set the post like setPost($post) it gives me the following error:
Expected parameter of type '\App\Entity\Post', 'object' provided
I assume, that the error you see is from your integrated developent environment (IDE), for example eclipse, vs code, phpstorm, and others. But the code - when actually executed - should work.
Now, the error most likely stems from a static code analysis running in the background of said IDE, which will look at the statement and trying to analyze according to the called methods, accessed properties etc. of which type your variables are.
So, let's do this slowly (and you can probably hover over the $vars and ->methods() do verify. The line I'm interested in is
$post = $entityManager->getRepository('App:Post')
->find($id);
so $entityManager is of type EntityManagerInterface, which has a getRepository method with one required parameter of type string ('App:Post' in your case), and it will return an object of type ObjectRepository, which has a method find which requires one parameter (mixed, don't ask), and returns ?object which means, an object or null. So, $post is of type object (best case, or null, in which case it would fail!). Now, the next line obviously expects a parameter of type Post and not of type object, thus the warning/notice/error.
Now, static code analysis is quite helpful up to a certain level, but it isn't infallible because it has limitations. It doesn't know what runtime will actually return, it just assumes that the type hints found in the code (of doctrine) are sufficiently specific - which they aren't in your case.
the easy fix
add a doc string to tell static code analysis what the variable $post's type actually is:
/** #var Post $post */
$post = $entityManager->getRepository('App:Post')
->find($id);
this explicitly tells the static analysis tool, that $post is of type Post, maybe you have to write App\Entity\Post or even \App\Entity\Post.
the hard fix
Alternatively, you could implement your own PostRepository (doctrine provides some help) and define a function like function findById($id) :Post - which would explicitly tell static code analysis, what the return type is when you call it in your code (injected in your function via dependency injection: PostRepostory $postRepository):
$post = $postRepository->findById($id);
If you're using lots and lots of different entities, this is a very verbose solution but depending on your project it might be worth it, since you explicitly name the dependencies instead of injecting the very unspecific (as we have seen) EntityManagerInterface. Using the EntityManagerInterface might make testing HELL (imho!).

Symfony: Query Builder: Catchable Fatal Error: Object of class DateTime could not be converted to string

I am trying to do a query in Symfony 3 to select the Commentaries of a Wish (Object Wishcom) with the contributionType included (Object ContributionType) thanks to a JOIN.
When running the webpage I get a:
Error: Method Doctrine\ORM\Query\Expr\Func::__toString() must not throw an >exception, caught Symfony\Component\Debug\Exception\ContextErrorException: >Catchable Fatal Error: Object of class DateTime could not be converted to >string
In the forums I could get that the datetime has to be converted with format. The thing is that I am not manipulating directly the date with my query although I know there is one DateTime attribute in my Wishcom object.
Should I select specifically the date and format it ? In that case were should it be done ? Or does the error come from something else ?
It seems the error comes from the where statement and the function ToString from Expr not being able to convert the date. I don't know what I should do.
$queryBuilder->where($queryBuilder->expr()->in('w.wish', $wish));
Here is my call in the controller:
$arraywishcom=$em->getRepository(Wishcom::class)->getWishcomWithContributionType($wish);
And my repository:
<?php
namespace Shaker\JRQBundle\Repository;
use Shaker\JRQBundle\Entity\Wish;
/**
* WishcomRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class WishcomRepository extends \Doctrine\ORM\EntityRepository
{
public function getWishcomWithContributionType(Wish $wish) {
$queryBuilder = $this
->createQueryBuilder('w')
->leftJoin('w.contributiontype', 'contributiontype')
->addSelect('contributiontype')
;
$queryBuilder->where($queryBuilder->expr()->in('w.wish', $wish));
return $queryBuilder
->getQuery()
->getResult()
;
}
}
I have found a different way of doing the query and it works.
Instead of the where I had with expr(), which was the problem, I did the following:
$queryBuilder->where('w.wish = :wish')->setParameter('wish', $wish);
But I still do not know why I got the error in the first place.

Akeneo 2.2.8 : How can I get the original attribute data in the akeneo.storage.pre_save event?

I'm using Akeneo 2.2.8 and I'm trying to use the akeneo.storage.pre_save-event to compare the original product data with the new data provided. I do this by subscribing to the akeneo.storage.pre_save-event:
In event_subscribers.yml:
parameters:
vendor.bundle.event_subscriber.product_save.class: Vendor\Bundle\CustomBundle\EventSubscriber\ProductSaveSubscriber
services:
vendor.bundle.event_subscriber.product_save:
class: '%vendor.bundle.event_subscriber.product_save.class%'
arguments:
- '#pim_catalog.repository.product'
tags:
- { name: kernel.event_listener, event: akeneo.storage.pre_save, method: onPreSave, priority: 255 }
In ProductSaveSubscriber.php:
/**
* #var ProductRepositoryInterface
*/
protected $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function onPreSave(GenericEvent $event)
{
/** #var Product $subject */
$subject = $event->getSubject();
if ($subject instanceof Product) {
$originalProduct = $this->productRepository->findOneByIdentifier($subject->getIdentifier());
foreach ($subject->getAttributes() as $attribute) {
if ($attribute->getReadOnly()) {
echo "{$attribute->getCode()} : {$subject->getValue($attribute->getCode())}\n";
echo "{$attribute->getCode()} : {$originalProduct->getValue($attribute->getCode())}\n";
}
}
}
}
Now when I run this code, I expect the second echo-statement to give the original data (since I've loaded that anew). However, the original product I load from the repository also has the new data.
Another thing to note here is that if I add a die()-statement, the data is not stored in the database. So it seems that the repository returns the in-memory model or something like that.
Can anyone point me in the right direction? Or am I using the wrong approach to compare newly-entered data with already existing data?
Now when I run this code, I expect the second echo-statement to give
the original data (since I've loaded that anew). However, the original
product I load from the repository also has the new data.
This might be because the object is referenced in doctrine's unit of work. So when you use the repository to fetch what you think is the original object, might actually be the same object you've updated.
Another thing to note here is that if I add a die()-statement, the
data is not stored in the database. So it seems that the repository
returns the in-memory model or something like that.
That's because since your subscriber is listening on the PRE_SAVE event, the updated product has not been flushed in the database yet. Saving a product goes this way:
PRE_SAVE event thrown
COMMIT / FLUSH
POST_SAVE event thrown
So if you call die during the PRE_SAVE event, the COMMIT / FLUSH won't be called.
Can anyone point me in the right direction? Or am I using the wrong
approach to compare newly-entered data with already existing data?
I don't know your particular use case but you might want to use a Query function (see https://github.com/akeneo/pim-community-dev/blob/2.2/src/Pim/Bundle/CatalogBundle/Doctrine/ORM/Query/FindAttributesForFamily.php). It's purpose is to directly fetch in the database the data you need (it will be the original value since the product hasn't been flushed in DB on PRE_SAVE)
I hope this helps.

Add data when running Symfony migrations

I have a Symfony project that is using the DoctrineMigrations bundle, and I have a really simple question: When I run a migration (e.g., when I'm pushing an update to production), how can I insert data to the database?
For example: I have an Entity which is the type of an add. The entity is:
private $addType; // String
private $type1; // Boolean
private $type2; // Boolean
private $type3; // Boolean
I add another field ($type4), and I want to add a new record to the database, with this values:
$addType = 'Type number 4';
$type1 = false;
$type2 = false;
$type3 = false;
$type4 = true;
How can this be done with DoctrineMigrations? Is it possible?
Using the Entity Manager as suggested in another answer is not a good idea, as it leads to troubles later.
In the first migration, I created a table with users and populated some users via $em->persist($user); which seemed fine at the beginning.
But after a month, I added a phone column to my User model. And Doctrine generates INSERT statements with this column within the first migration, which fails due to the non-existing column phone. Of course it doesn't exist yet in the first migration. So it is better to go with pure SQL INSERTs.
I just asked a related related question.
It is possible to use the migrations bundle to add data to the database. If you add a new property and use the doctrine mapping then the
php app/console doctrine:migrations:diff
command will generate a new migration file. You can just put your insert statements inside this file using the syntax:
$this->addSql('INSERT INTO your_table (name) VALUES ("foo")');
Make sure you put it after the auto-generated schema changes though. If you want to separate your schema changes and your data changes then you can use
php app/console doctrine:migrations:generate
to create an empty migrations file to put your insert statements in.
Like I said in my related question, this is one way to do it, but it requires manually creating these if you want to change this data in the database.
Edit:
Since this answer seems to get a few views I think it's worth adding that to more clearly separate the data changes from the schema changes there is a postUp method that can be overridden and that will be called after the up method.
https://www.doctrine-project.org/projects/doctrine-migrations/en/3.0/reference/migration-classes.html#postup
I've "found" the correct way to solve my problem (insert data after running migrations, using my entity classes).
Here is: https://stackoverflow.com/a/25960400
The idea is to declare the migration as ContainerAware, and then, from the postUp function, call the DI to get the EntityManager. It's really easy, and you can use all your entities and repositories.
// ...
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... migration content
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
}
}
when you make the new field you need to enter this annotation "options={"default":1}" and it should work.
/**
* #var boolean
* #ORM\Column(name="type4", type="boolean", options={"default":1})
*/
private $type4 = true;
Took me some time to figure this out :)
It does, if you know how to format the array;
$this->connection->insert('user', ['id' => 1, 'gender' => 'Male']);
this is good solution for me. Just use bin/console make:migration and when migration is generated just edit if and add "DEFAULT TRUE":
$this->addSql('ALTER TABLE event ADD active TINYINT(1) NOT NULL DEFAULT TRUE');
It doesn't sound a good idea to fill date in migration, not its responsibility, symfony has a way of doing that. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

Getting multiple rows using ParamConverter

Hi Im trying to use ParamConverter to get multiple rows from DB but profiler show query with limi 1. Is it possible to get it like that
/**
* #Route("/localization/{code}", name="pkt-index")
* #ParamConverter("localizations", class="PriceBundle:Localization")
*/
after entering localization/0003 I should get more than 100 rows.
EDIT:
I have used repository_method option
and
/*
* #Route("/localization/{id}", name="pkt-index")
* #ParamConverter("localizations", class="PriceBundle:Localization", options={
* "repository_method": "getByLocalizationCode"
* })
*/
but funny thing is that when I change {id} in route it does not work it throws and exception
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
even if variable exists in entity class, if variable dont exist it throws
Unable to guess how to get a Doctrine instance from the request information.
EXPLANATION
when I change {id} in route it does not work it throws and exception
SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
Here I think symfony treads id like primary key and as parameter to repository method it pass string when I changed this id to something else it pass array
Example
/**
* #Route("/localization/{id}", name="pkt-index")
*/
pass string to method
/**
* #Route("/localization/{code}/{name}", name="pkt-index")
*/
pass array to method
array(
'code' => 003
'name' => localization_name
)
and last
/**
* #Route("/localization/{id}/{name}", name="pkt-index")
*/
will pass string id omit the name
Hope this sounds reasonable.
forgottenbas's answer isn't completely right. #ParamConverter will first try to find one entity by id ...
... then try to match the route variables against db columns to find an entity ...
but essentially it will only convert one entity at a time.
If you would still like to use a paramconverter you would need to write a custom one.
or just use a one-liner inside your controller action:
/**
* #Route("/localization/{code}", name="pkt-index")
*/
public function yourAction($code)
{
$localizations = $this->getDoctrine()->getRepository("YourBundle:Localization")->findBy(array("code" => $code));
// ...
ParamConverter currently can only extract id from request and find one entity from db. Look at
DoctrineParamConverter code. But you can specify your own param converter with some extra logic.

Resources