Gedmo Slug from relation - symfony

I use Doctrine ORM and Gedmo\Slug and i have class with related entitty job with title field and i want generate slug by job title. I'm configured SlugHandler like this:
/**
*
* #Gedmo\Slug(handlers={
* #Gedmo\SlugHandler(class="Gedmo\Sluggable\Handler\RelativeSlugHandler", options={
* #Gedmo\SlugHandlerOption(name="relationField", value="job"),
* #Gedmo\SlugHandlerOption(name="relationSlugField", value="title"),
* #Gedmo\SlugHandlerOption(name="separator", value="-"),
* })
* }, fields={"slug"})
* #ORM\Column(type="string", unique=true, nullable=true)
*/
private $slug = '';
/**
* #var Job
*
* #ORM\OneToOne(targetEntity="Job", inversedBy="estimation")
* #ORM\JoinColumn(name="job_id", referencedColumnName="id", nullable = false)
*/
private $job;
But in slug field setting title value exactly the same as the title field value (without lowercase, separators and with spaces).
I don't understand what's the matter

If you suddenly encounter a similar problem, you need set option urilize in true:
/**
*
* #Gedmo\Slug(handlers={
* #Gedmo\SlugHandler(class="Gedmo\Sluggable\Handler\RelativeSlugHandler", options={
* #Gedmo\SlugHandlerOption(name="relationField", value="job"),
* #Gedmo\SlugHandlerOption(name="relationSlugField", value="title"),
* #Gedmo\SlugHandlerOption(name="separator", value="-"),
* #Gedmo\SlugHandlerOption(name="urilize", value="true"),
* })
* }, fields={"slug"})
* #ORM\Column(type="string", unique=true, nullable=true)
*/
private $slug;

Related

Persist create new entity while already exists (Ont-To-Many Many-To-One relation)

I have an Angular4 app working with Symfony3/Doctrine2 Rest Api.
Both in Angular and Symfony, I have those entities :
Table
TableNode
Node
The relation between Table and Node is :
Table (OneToMany) TableNode (ManyToOne) Node
What is a "ManyToMany" relation with attributes.
In the Angular app, I create a new Table (form a TableModel that has exactly the same properties that the Table entity in the Symfony app).
This Table contains several Node entities that come from the Api (so they already exists in my database).
What I want is to create a new Table that contains new TableNode entities and each TableNode should contain existing Node entities.
When I want to save my table within the db, I call my Api through a Put action :
/**
* PUT Route annotation
* #Put("/tables")
*/
public function putTableAction(Request $request)
{
$em = $this->getDoctrine()->getManager('psi_db');
$serializer = $this->container->get('jms_serializer');
$dataJson = $request->query->get('table');
$table = $serializer->deserialize($dataJson, Table::class, 'json');
// Here, my $table has no id (that's ok), the TableNode subentity has no id (ok) and my Node subentity already have an id (because they come from the db)
$em->persist($table);
// Here, my $table has a new id (ok), my TableNode has a new id (ok) BUT my Node subentity have a NEW id, so it will be duplicated
$em->flush();
$view = $this->view();
$view->setData($table);
return $this->handleView($view);
}
I tried to use $em->merge($table) instead of $em->persist($table) and my node subentities keep there own id (so they may not be duplicated within the flush) BUT the table and tableNode have no id (null) and are not persisted.
The only solution I found is to loop through the TableNode entities, retrieve the Node entity from the database and do a tableNode->setNode :
$tns = $table->getTableNodes();
foreach ($tns as $tn) {
$nodeId = $tn->getNode()->getId();
$dbNode = $nodeRepo->find($nodeId);
$tn->setNode($dbNode);
}
But it's not a good solution because I make a db search within a loop and a table could contains more than a hundred of TableNode/Node so it might take a lot of resources.
Does anyone have a cleaner solution ?
Thanks.
edit : add classes
Table :
/**
* Table_
* Doctrine "Table" is a reserved name, so we call it Table_
*
* #ORM\Table(name="psi_table")
* #ORM\Entity(repositoryClass="AppBundle\Repository\Table_Repository")
*
* #ExclusionPolicy("all")
*/
class Table_
{
public function __construct()
{
$this->tNodes = new ArrayCollection();
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Expose
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*
* #Expose
*/
private $name;
/**
* #var \stdClass
*
* #ORM\Column(name="author", type="object", nullable=true)
*
* #Expose
*/
private $author;
/**
* #var \stdClass
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\TableNode", mappedBy="table", cascade={"persist"})
*
* #Expose
* #Type("ArrayCollection<AppBundle\Entity\TableNode>")
* #SerializedName("tNodes")
*/
private $tNodes;
}
TableNode :
/**
* TableNode
*
* #ORM\Table(name="psi_table_node")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TableNodeRepository")
*
* #ExclusionPolicy("all")
*/
class TableNode
{
public function __construct($table = null, $node = null, $position = null)
{
if($table) $this->table = $table;
if($node) $this->node = $node;
if($position) $this->position = $position;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Expose
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="position", type="integer")
*
* #Expose
*/
private $position;
/**
* #var string
*
* #ORM\Column(name="groupSocio", type="string", nullable=true)
*
* #Expose
* #SerializedName("groupSocio")
*/
private $groupSocio;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Table_", inversedBy="tNodes", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*
* #Expose
* #Type("AppBundle\Entity\Table_")
*/
private $table;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Node", inversedBy="tables", cascade={"persist", "merge"})
* #ORM\JoinColumn(nullable=false)
*
* #Expose
* #Type("AppBundle\Entity\Node")
*/
private $node;
}
Node :
/**
* TableNode
*
* #ORM\Table(name="psi_table_node")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TableNodeRepository")
*
* #ExclusionPolicy("all")
*/
class TableNode
{
public function __construct($table = null, $node = null, $position = null)
{
if($table) $this->table = $table;
if($node) $this->node = $node;
if($position) $this->position = $position;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Expose
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="position", type="integer")
*
* #Expose
*/
private $position;
/**
* #var string
*
* #ORM\Column(name="groupSocio", type="string", nullable=true)
*
* #Expose
* #SerializedName("groupSocio")
*/
private $groupSocio;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Table_", inversedBy="tNodes", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*
* #Expose
* #Type("AppBundle\Entity\Table_")
*/
private $table;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Node", inversedBy="tables", cascade={"persist", "merge"})
* #ORM\JoinColumn(nullable=false)
*
* #Expose
* #Type("AppBundle\Entity\Node")
*/
private $node;
}
Submitted data (example) :
{"tNodes":[{"id":0,"position":0,"groupSocio":"group1","node":{"id":683,"frontId":"1502726228584","level":"synusy","repository":"baseveg","name":"A synusy from my Angular app!","geoJson":{"type":"FeatureCollection","features":[{"type":"Feature","properties":[],"geometry":{"type":"Point","coordinates":[-10.0634765625,42.0982224112]}}]},"lft":1,"lvl":0,"rgt":2,"children":[{"id":684,"frontId":"1502726228586","level":"idiotaxon","repository":"baseflor","name":"poa annua","coef":"1","geoJson":{"type":"FeatureCollection","features":[{"type":"Feature","properties":[],"geometry":{"type":"Point","coordinates":[-10.0634765625,42.0982224112]}}]},"lft":1,"lvl":0,"rgt":2,"validations":[{"id":171,"repository":"baseflor","repositoryIdTaxo":"7075","repositoryIdNomen":"50284","inputName":"poa annua","validatedName":"Poa annua L."}]}],"validations":[]}}]}
The purpose putTableAction is to:
create new instances of Table_
create new instance of TableNode
do nothing with Node
It means that:
1.You do not need to submit any details of Node. Id field is enough:
{"tNodes":[{"id":0,"position":0,"groupSocio":"group1","nodeId": 683}]}
2.You can add one more field to TableNode, called $nodeId, and map it with "node" field in DB. The purpose of this field is to simplify deserialization, in all other places you can use $node field.
/**
* #var integer
*
* #ORM\Column(name="node", type="integer")
*
* #Expose
*/
private $nodeId;

Symfony entity validation verify foreign key exists

I would like to be able to use entity validator constraints to verify if the foreign key book_id is valid, please see below:
Book.php
/**
* Book
*
* #ORM\Table("book")
* #ORM\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\BookRepository")
*/
class Book
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string")
* #Assert\Length(
* max = 250,
* maxMessage = "Name cannot be longer than {{ limit }} characters",
* groups={"create","update"}
* )
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="Loan", mappedBy="book", fetch="LAZY")
*/
protected $loan;
}
Loan.php
/**
* Loan
*
* #ORM\Table("loan")
* #ORM\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\LoanRepository")
*/
class Loan
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
* #ORM\Column(name="book_id", type="integer")
*/
protected $book_id;
/**
* #var string
* #ORM\Column(name="name", type="string")
* #Assert\Length(
* max = 500,
* maxMessage = "Person cannot be longer than {{ limit }} characters",
* groups={"create","update"}
* )
*/
private $person;
/**
* #ORM\OneToOne(targetEntity="Book", inversedBy="loan")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
}
Here is how I am currently validating the loan entity
$loan = new Loan();
$loan->setPerson($person);
$loan->setBookId($id);
/** #var ConstraintViolation $error */
foreach ($this->get('validator')->validate($loan,null,['create'])->getIterator() as $index => $error) {
$errorMessages[] = $error->getMessage();
}
I figured maybe I can add a custom validator like this to the loan entity:
/**
* #Assert\IsTrue(message = "The book does not exist")
* #return bool
*/
public function isBookLegal(BookRepository $bookRepository)
{
return !$bookRepository->fetchById($this->book_id);
}
But I end up with the follow exception:
Type error: Too few arguments to function
AppBundle\Entity\Loan::isBookLegal(), 0 passed and exactly 1 expected
First of all, you should not have both $book_id and $book in your Loan entity. You should remove $book_id, which is enough for your entity relationship.
Then, all you need to do is add an #Assert\NotBlank() on $book:
use Symfony\Component\Validator\Constraints as Assert;
...
/**
* #ORM\OneToOne(targetEntity="Book", inversedBy="loan")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $book;
I'm not sure what code you are using to get all of your loans, but as Edwin states that's not really good form. You want want something more like:
foreach ($loans as $loan) {
$errors = $this->get('validator')->validate($loan);
// do something here if there is an error
}
The assertion function you wrote isn't going to work because you can't pass in a value to your isBookLegal() function there, nor can you ever use the database connection/repository from within your Entity class.
I'm not really sure what you are trying to accomplish without greater context here. Doctrine is already going to validate your $book member because of your #ORM\OneToOne annotation in the first place. You don't have to perform any additional validation. I'm guessing you are trying to pass in a value directly to $book_id, which is incorrect. You should only be passing already-valid $book entities to your Loan class, via $loan->setBook(Book $book);

Symfony Doctrine update many to one

I have two entities:
1)
/**
* Post
*
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Image", cascade={"persist"}, fetch="EAGER")
* #ORM\JoinColumn(name="image", referencedColumnName="id", nullable=true)
*/
private $image;
}
and 2)
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ImageRepository")
*/
class Image
{
/**
* #var int
*
* #ORM\Column(name="id", type="string", length=40)
* #ORM\Id
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="small", type="blob", nullable=true)
*/
private $small;
/**
* #var string
*
* #ORM\Column(name="medium", type="blob", nullable=true)
*/
private $medium;
/**
* #var string
*
* #ORM\Column(name="large", type="blob", nullable=true)
*/
private $large;
...
}
Both entities have getters and setters.
The controller sets the post to update
$requestFile = $request->files->get('image');
$em = $this->getDoctrine()->getManager();
$record = $em->getRepository('AppBundle:Post')->find($id);
if (!$record) {
throw $this->createNotFoundException(
'No product found for id '.$record
);
}
$record->setTitle($request->request->get('title'));
$record->constructAlias($request->request->get('title'));
...
if ($request->files->get('image')) {
// check if image already exists
$imgGeneratedId = sha1_file($request->files->get('image')->getPathName());
$imageRepo = $this->getDoctrine()->getRepository('AppBundle:Image');
$isPresent = $imageRepo->find($imgGeneratedId);
$image = $isPresent;
if (!$isPresent) {
$image = new Image();
$image->setId($imgGeneratedId);
$image->setLarge(file_get_contents($request->files->get('image')->getPathName()));
$mediumImage =
...
$em->persist($image);
}
$record->setImage($em->getReference('AppBundle:Image', $image->getId()));
}
$em->persist($record);
$em->flush();
Please ignore any syntax errors or other kinds; there are none.
The problem is that even if image already exists it is trying to create one which causes error in database. So, in order to avoid this I used getReference, but this causes other issues.
So, how can I force doctrine, not to insert an image if already exists and just set the article image with the image id?
Thanks.

DoctrineExtensions Uploadable on entity update doesn't keep current value

I am using Uploadable extension and very happy with that.
I have entity with one field as an Uploadable (photo), and another field is annotation for that photo (annotation). When I first create entity I choose the file, and put annotation and everything works okay, but when I want to update just annotation it loses the stored path of the previously uploaded photo. Is there a way to keep old values if null coming for that field?
This is my entity.
/**
* Photo
*
* #ORM\Table()
* #ORM\Entity
* #Gedmo\Uploadable(
* path="up/photo",
* allowOverwrite=false,
* appendNumber=true,
* allowedTypes="image/jpeg,image/pjpeg,image/png,image/x-png"
* )
*/
class Photo
{
/**
* #var array
*
* #Gedmo\Translatable
* #ORM\Column(name="annotation", type="string", length=255, nullable=true)
*/
private $annotation;
/**
* #var string
*
* #Gedmo\UploadableFilePath
* #Assert\File(
* mimeTypes={"image/jpeg", "image/pjpeg", "image/png", "image/x-png"}
* )
* #ORM\Column(name="photo", type="string", length=255)
*/
private $photo;
And this is my Controller part:
if ($entity->getPhoto()) {
$uploadableManager = $this->get('stof_doctrine_extensions.uploadable.manager');
$uploadableManager->markEntityToUpload($entity, $entity->getPhoto());
}
You can change setter on your entity:
public function setPhoto($photo) {
if (!$photo) {return $this;}
$this->photo = $photo;
return $this;

Doctrine Blameable: on="change" doesn't work while on="create" and "update" do

I'm trying to combine Doctrine's Extensions Blameable and Softdeleteable: when I execute $em->remove($myEntity);, I want to get the fields deleted and deletedBy updated accordingly.
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #Gedmo\SoftDeleteable(fieldName="deleted", timeAware=false)
*/
[...]
/**
* DateTime of softdeletion
* #var \DateTime
*
* #ORM\Column(name="deleted", type="datetime", nullable=true)
* #Assert\DateTime()
*/
private $deleted;
/**
* Softdeleted by
* #var MyProject\UserBundle\Entity\User $deletedBy
*
* #Gedmo\Blameable(on="change", field="deleted")
* #ORM\ManyToOne(targetEntity="MyProject\UserBundle\Entity\User")
* #ORM\JoinColumn()
*/
private $deletedBy;
I have a similar configuration for created/createdBy (with Blameable(on="create")) and updated/updatedBy (with Blameable(on="update")).
Even weirder, if I replace the code above by the one below, the field deletedBy is correctly updated:
/**
* Softdeleted by
* #var MyProject\UserBundle\Entity\User $deletedBy
*
* #Gedmo\Blameable(on="update")
* #ORM\ManyToOne(targetEntity="MyProject\UserBundle\Entity\User")
* #ORM\JoinColumn()
*/
private $deletedBy;
So it seems that it is only the Blameable(on="change", field="deleted") part which doesn't work, and I have no idea why...

Resources