Building relationship entity with Neo4J PHP OGM EntityManager - php

I am trying to build an entity object for my relationship in Neo4j database with GraphAware Neo4j PHP OGM library using this simple method:
public function getRelationshipEntity($entityId) {
$repo = $this->entityManager->getRepository( Entity\Relationship\Fired::class );
return $repo->findOneById($entityId);
}
Here we have the entity classes, relationship first:
namespace Entity\Relationship;
use GraphAware\Neo4j\OGM\Annotations as OGM;
use Entity\Issue;
use Entity\Event;
/**
* #OGM\RelationshipEntity(type="FIRED")
*/
class Fired {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #OGM\StartNode(targetEntity="Entity\Event")
*/
protected $event;
/**
* #OGM\EndNode(targetEntity="Entity\Issue")
*/
protected $issue;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $time;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $eventName;
}
Then, start node:
namespace Entity;
use GraphAware\Neo4j\OGM\Annotations as OGM;
/**
* #OGM\Node(label="Event")
*/
class Event {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $name;
}
..and end node:
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use GraphAware\Neo4j\OGM\Annotations as OGM;
/**
* #OGM\Node(label="Issue")
*/
class Issue {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #OGM\Property(type="string")
*/
protected $key;
/**
* #OGM\Property(type="string")
*/
protected $created;
/**
* #OGM\Property(type="string")
*/
protected $updated;
/**
* #OGM\Relationship(type="FIRED", direction="INCOMING", relationshipEntity="Entity\Relationship\Fired", collection=true)
* #var ArrayCollection
*/
protected $eventFires;
public function __construct($key) {
$this->key = $key;
$this->eventFires = new ArrayCollection();
}
public function __wakeup() {
$this->__construct($this->key);
}
/**
* #return ArrayCollection
*/
public function getEventFires() {
return $this->eventFires;
}
public function addEventFire(Entity\Relationship\Fired $eventFired) {
$this->eventFires->add($eventFired);
}
public function removeEventFire(Entity\Relationship\Fired $eventFired) {
$this->eventFires->removeElement($eventFired);
}
}
Apparently, what works really well for node entites, triggers the following error for relationships:
Fatal error: Call to undefined method GraphAware\Neo4j\OGM\Metadata\RelationshipEntityMetadata::hasCustomRepository() in /vendor/graphaware/neo4j-php-ogm/src/EntityManager.php
Any suggestion how I could workaround this? I even tried using EntityManager::createQuery() the following way:
public function getRelationships($eventName) {
$query = $this->entityManager->createQuery('MATCH (e:Event)-[f:FIRED{eventName: {eventName}}]->() RETURN e,f ORDER BY f.time');
$query->addEntityMapping('e', 'Entity\Event' );
$query->addEntityMapping('f', 'Entity\Relationship\Fired' );
$query->setParameter( 'eventName', $eventName);
return $query->execute();
}
But, apparently, addEntityMapping() doesn't work for relationship entities either! (It might be a feature though, not a bug):
Catchable fatal error: Argument 1 passed to GraphAware\Neo4j\OGM\Hydrator\EntityHydrator::hydrateNode() must implement interface GraphAware\Common\Type\Node, instance of GraphAware\Bolt\Result\Type\Relationship given, called in /vendor/graphaware/neo4j-php-ogm/src/Query.php on line 145 and defined in /vendor/graphaware/neo4j-php-ogm/src/Hydrator/EntityHydrator.php on line 232
So, I ended up that I can easily define and store relationship entities in Neo4J with this library but not sure how I could retrieve it easily with EntityManager, in the similar way I can do so with nodes.
Any help would be much appreciated!
As requested in comment below, these are GraphAware packages that I am using:
graphaware/neo4j-bolt 1.9.1 Neo4j Bolt Binary Protocol PHP Driver
graphaware/neo4j-common 3.4.0 Common Utilities library for Neo4j
graphaware/neo4j-php-client 4.8.0 Neo4j-PHP-Client is the most advanced PHP Client for Neo4j
graphaware/neo4j-php-ogm 1.0.0-RC6 PHP Object Graph Mapper for Neo4j

Related

Inheritance relationship with interfaces in symfony/doctrine

I have entities like this :
Request.php (parent)
/**
* #ORM\Entity(repositoryClass=RequestRepository::class)
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "requestA" = "RequestA",
* "requestB" = "RequestB"
* })
*/
abstract class Request
{
/*...*/
}
RequestA.php (child A)
/**
* #ORM\Entity(repositoryClass=DemenagementRepository::class)
*/
class RequestA extends Request implements AddressEntityInterface
{
/**
* #ORM\OneToOne(targetEntity=Address::class, inversedBy="request")
* #ORM\JoinColumn(nullable=false)
*/
private $address;
// +others...
}
RequestB.php (child B)
/**
* #ORM\Entity(repositoryClass=DemenagementRepository::class)
*/
class RequestB extends Request implements AddressEntityInterface
{
/**
* #ORM\OneToOne(targetEntity=Address::class, inversedBy="request")
* #ORM\JoinColumn(nullable=false)
*/
private $address;
// +others...
}
AddressEntityInterface.php
interface AddressEntityInterface
{
public function getAddress(): ?Address;
public function setAddress(Address $address): self;
}
Address.php
/**
* #ORM\Entity(repositoryClass=AddressRepository::class)
*/
class Address
{
/**
* #ORM\OneToOne(targetEntity=??????, mappedBy="address")
*/
private $request;
public function getRequest() { /* ... */ }
}
I want to use getRequest() revert relationship how can i do for make targetEntity dynamically ?
Thanks
I believe you need polymorphic relationships or as they like to call them from docrine 'Inheritance Mapping'
I leave you some sites to view on which you can find everything you need
Official Documentation (Doctrine)
Stackoverflow Practical example
Youtube tutorial
Stackoverflow, discussion <-
personally I advise you to read all this so you get a concrete idea of ​​what you need
I hope my little routing will help you.

Add extra option to LoggableListener

I use Loggable to backup changes in Entities.
The default AbstractLogEntry does not have enough columns for my needs.
Thats why i extended the class and added extra getters and setters.
See the code below
/**
* EmployeeBackup
*
* #ORM\Table(name="employee_backup")
* #ORM\Entity(repositoryClass="Gedmo\Loggable\Entity\Repository\LogEntryRepository")
*
*/
class EmployeeBackup extends AbstractLogEntry
{
/**
* #var int
*
* #ORM\Column(name="division_id", type="integer", unique=true)
*/
private $divisionId;
/**
* #return int
*/
public function getDivisionId(): int
{
return $this->divisionId;
}
/**
* #param string $divisionId
*/
public function setDivisionId(string $divisionId): void
{
$this->divisionId = $divisionId;
}
}
The extension is using the class above. So it works.
But now i need to set the divisionId when a new version is stored.
I tried the code below
$loggable = new LoggableListener();
$loggable->setDivision($division);
$evm->addEventSubscriber($loggable);
And this is what i get:
Attempted to call an undefined method named "setDivision" of class "Gedmo\Loggable\LoggableListener".
And thats true because LoggableListener does not have a setDivision function. My question is: Do i need to override the listener and if so, how do i do that?
Thanks ;)

Sylius\Component\Shipping\Model\ShippingMethodTranslation must be compatible with Sylius\Component\Shipping\Model\ShippingMethod::createTranslation():

I'm following the tutorial : "How to customize a translatable Model?" in Sylius doc.
When I run the command : php bin/console doctrine:migrations:diff I got this error :
Fatal error: Declaration of AppBundle\Entity\ShippingMethod::createTranslation(): Sylius\Component\Shipping\Model\ShippingMethodTranslation must be compatible with Sylius\Component\Shipping\Model\ShippingMethod::createTranslation(): Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface in C:\wamp64\www\acme7\src\AppBundle\Entity\ShippingMethod.php on line 8
Here is my class :
<?php
namespace AppBundle\Entity;
use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;
use Sylius\Component\Shipping\Model\ShippingMethodTranslation;
class ShippingMethod extends BaseShippingMethod
{
/**
* #var string
*/
private $estimatedDeliveryTime;
/**
* #return string
*/
public function getEstimatedDeliveryTime(): string
{
return $this->estimatedDeliveryTime;
}
/**
* #param string $estimatedDeliveryTime
*/
public function setEstimatedDeliveryTime(string $estimatedDeliveryTime): void
{
$this->estimatedDeliveryTime = $estimatedDeliveryTime;
}
/**
* {#inheritdoc}
*/
protected function createTranslation(): ShippingMethodTranslation
{
return new ShippingMethodTranslation();
}
}
Any idea on how to solved this ?
I got some help form the slack of sylius.
As I'm using Sylius v1.0.4
I had to replace :
use Sylius\Component\Shipping\Model\ShippingMethodTranslation;
by
use Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface;
and
/**
* {#inheritdoc}
*/
protected function createTranslation(): ShippingMethodTranslation
{
return new ShippingMethodTranslation();
}
by
/**
* {#inheritdoc}
*/
protected function createTranslation(): ShippingMethodTranslationInterface {
return new ShippingMethodTranslation();
}

Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLite

I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}

How to do a ManytoMany or ManyToOne Doctrine Neo4j

I'm learning how to work with Neo4j and Doctrine OGM, and I'm having problems with my source code. I don't know how to use manytomany because I'm just starting learn. When I save I see:
Catchable fatal error: Argument 1 passed to Entity\Empresas::setTelefone() must be an instance of Entity\Entity\Telefones, string given, called in /Applications/MAMP/htdocs/neo4j/n4j/save.php on line 17 and defined in /Applications/MAMP/htdocs/neo4j/n4j/Empresas.php on line 50
My Empresas.php entity
namespace Entity;
use HireVoice\Neo4j\Annotation as OGM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* All entity classes must be declared as such.
*
* #OGM\Entity(labels="Empresas")
*/
class Empresas
{
/**
* The internal node ID from Neo4j must be stored. Thus an Auto field is required
* #OGM\Auto
*/
protected $id;
/**
* #OGM\Property
* #OGM\Index
*/
protected $nome;
/**
* #OGM\Property
*/
protected $keywords;
/**
* #OGM\ManyToOne(relation="tem_telefone")
*/
protected $telefone;
function getID(){
return $this->id;
}
function setNome($nome){
$this->nome = $nome;
}
function setKeywords($keywords){
$this->keywords = $keywords;
}
public function getTelefone() {
return $this->telefone;
}
public function setTelefone(Entity\Telefones $telefone) {
$this->telefone = $telefone;
}
}`
My Telefones.php Entity
<?php
namespace Entity;
use HireVoice\Neo4j\Annotation as OGM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* All entity classes must be declared as such.
*
* #OGM\Entity(labels="Empresas")
*/
class Telefones
{
/**
* The internal node ID from Neo4j must be stored. Thus an Auto field is required
* #OGM\Auto
*/
protected $id;
/**
* #OGM\Property
* #OGM\Index
*/
protected $telefone;
function getID(){
return $this->id;
}
}
And my Save.php Entity
<?php
require 'bootstrap.php';
require 'Empresas.php';
require 'Telefones.php';
$repo = $em->getRepository('Entity\\Empresas');
$empresa_container = $em->find('Entity\\Empresas', "22");
$telefones = new Entity\Telefones();
$empresa = new Entity\Empresas;
$empresa->setNome("nome");
$empresa->setKeywords("keywords");
$empresa->setTelefone("telefone");
$em->persist($telefones);
$em->persist($empresa);
$em->flush();
echo $empresa->getId();
Error

Categories