I installed SeoBundle and configured the bundle to build a sitemap (docs).
AppKernel.php:
new Sonata\SeoBundle\SonataSeoBundle(),
new Symfony\Cmf\Bundle\CoreBundle\CmfCoreBundle(),
new Symfony\Cmf\Bundle\SeoBundle\CmfSeoBundle(),
Full bundle configurations (config.yml):
sonata_seo:
page:
title: Erasmus internship – Training Experience
metas:
name:
keywords: Erasmus Internships, Internship in Europe, International Internships, Erasmus+, Erasmus Entrepreneur, Student Internships, Internships Abroad, Student Placements
description: Find Internships with Training Experience: Students can find internships & employment opportunities in Europe’s platform for internships. Search paid internships and placements abroad.
viewport: width=device-width, initial-scale=1
format-detection: telephone=no
robots: index, follow
property:
'og:site_name': Training Experience
'og:title': Erasmus internship – Training Experience
'og:description': Find Internships with Training Experience: Students can find internships & employment opportunities in Europe’s platform for internships. Search paid internships and placements abroad."
'og:url': https://www.trainingexperience.org
'og:image': https://www.trainingexperience.org/bundles/index/images/tx-orange.png
http-equiv:
'Content-Type': text/html; charset=utf-8
head:
'xmlns': http://www.w3.org/1999/xhtml
'xmlns:og': http://opengraphprotocol.org/schema/
cmf_seo:
title: seo.title
description: seo.description
sitemap:
enabled: true
content_listener:
enabled: false
Added routes to routing.yml:
sitemaps:
prefix: /sitemaps
resource: "#CmfSeoBundle/Resources/config/routing/sitemap.xml"
Now when I access /sitemaps/sitemap.xml it is opened, but no urls are listed:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"></urlset>
What could I be missing?
Important for the items on sitemat is, that they got a matching (allowed, and sitemap name) as a content. Mostly the content isn't loaded. To do so your Content have to implement \Symfony\Cmf\Bundle\SeoBundle\SitemapAwareInterface, which forces you to implement and fill a flag. You can find an example in the tests: SitemapAwareContent:
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2017 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Bundle\SeoBundle\Tests\Resources\Document;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
use Symfony\Cmf\Bundle\CoreBundle\Translatable\TranslatableInterface;
use Symfony\Cmf\Bundle\SeoBundle\SitemapAwareInterface;
use Symfony\Cmf\Component\Routing\RouteReferrersReadInterface;
use Symfony\Component\Routing\Route;
/**
* #PHPCRODM\Document(referenceable=true, translator="attribute")
*
* #author Maximilian Berghoff <Maximilian.Berghoff#gmx.de>
*/
class SitemapAwareContent extends ContentBase implements RouteReferrersReadInterface, TranslatableInterface, SitemapAwareInterface
{
/**
* #var string
*
* #PHPCRODM\Locale
*/
protected $locale;
/**
* #var ArrayCollection|Route[]
*
* #PHPCRODM\Referrers(
* referringDocument="Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route",
* referencedBy="content"
* )
*/
protected $routes;
/**
* #var bool
*
* #PHPCRODM\Field(type="boolean",property="visible_for_sitemap")
*/
private $isVisibleForSitemap;
/**
* #var string
*
* #PHPCRODM\Field(type="string",translated=true)
*/
protected $title;
public function __construct()
{
$this->routes = new ArrayCollection();
}
/**
* #param string $sitemap
*
* #return bool
*/
public function isVisibleInSitemap($sitemap)
{
return $this->isVisibleForSitemap;
}
/**
* #param bool $isVisibleForSitemap
*
* #return SitemapAwareContent
*/
public function setIsVisibleForSitemap($isVisibleForSitemap)
{
$this->isVisibleForSitemap = $isVisibleForSitemap;
return $this;
}
/**
* Add a route to the collection.
*
* #param Route $route
*/
public function addRoute($route)
{
$this->routes->add($route);
}
/**
* Remove a route from the collection.
*
* #param Route $route
*/
public function removeRoute($route)
{
$this->routes->removeElement($route);
}
/**
* Get the routes that point to this content.
*
* #return Route[] Route instances that point to this content
*/
public function getRoutes()
{
return $this->routes;
}
/**
* #return string|bool the locale of this model or false if
* translations are disabled in this project
*/
public function getLocale()
{
return $this->locale;
}
/**
* #param string|bool $locale the local for this model, or false if
* translations are disabled in this project
*/
public function setLocale($locale)
{
$this->locale = $locale;
}
}
you will also see that implementing the interface isn't the only task, you have to set the doctrine mapping also. Doing so, the default loader will fetch you documents and see them (they are visible now).
But you can implement your own loader, voter (another decission item to select) and guesser (to fill in extra data) on your own. So you can decide which content is visible on which (you can have several) sitemap.
The documentation currently shows the process for the loaders, voter and guessers only, so we should insert some hints for the default visibility flag and the default usage at all. So i created an issue. It would be nice to get some feedback there, too.
Related
I am getting the error Could not resolve type of column "id" of class "App\Entity\Officecurrencymax" from my installation. Have checked similar questions but I can't seem to get the Doctrine annotations right.
I have 2 entities with a ManyToOne relationship, Office and OfficeCurrencyMax. One Office can have many OfficeCurrencyMax's.
/**
* Office
*
* #ORM\Table(name="Office")
* #ORM\Entity(repositoryClass="App\Repository\OfficeRepository")
*/
class Office
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Officecurrencymax", inversedBy="offices")
*/
private $officeCurrencyMaxes;
// ...
public function getOfficeCurrencyMaxes(): ?Officecurrencymax
{
return $this->officeCurrencyMaxes;
}
public function setOfficeCurrencyMaxes(?Officecurrencymax $officeCurrencyMaxes): self
{
$this->officeCurrencyMaxes = $officeCurrencyMaxes;
return $this;
}
}
Then there is the Officecurrencymax entity:
/**
* Officecurrencymax
*
* #ORM\Table(name="OfficeCurrencyMax", indexes={#ORM\Index(name="IDX_6F39111B73FD6E34", columns={"Office"})})
* #ORM\Entity(repositoryClass="App\Repository\OfficeCurrencyMaxRepository")
*/
class Officecurrencymax
{
// ...
/**
* #var integer
*
* #ORM\Column(name="Id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Office
*
* #ORM\ManyToOne(targetEntity="Office", inversedBy="offices")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Office", referencedColumnName="OfficeId")
* })
*/
private $office;
// ...
public function getId(): ?int
{
return $this->id;
}
// ...
}
I had to cut down the code a lot since StackOverflow wouldn't let me post since it looks like your post is mainly code, please add some more details.
In your case, you want to have 1-Office have Many-Officecurrencymax
So Office should have a OneToMany property and Officecurrencymax a ManyToOne.
There are a few errors, for example your Office entity says that the property is inversed by: inversedBy="offices" whereas $offices does not exist in the Officecurrencymax entity.
Your OneToMany in Office should look like:
/**
* #ORM\OneToMany(targetEntity=Officecurrencymax, mappedBy="office")
*/
private $officeCurrencyMaxes;
And in Officecurrencymax we have the opposite side of the relation:
/**
* #ORM\ManyToOne(targetEntity=Office, inversedBy="officeCurrencyMaxes")
* #ORM\JoinColumn(name="Office", referencedColumnName="OfficeId")
*/
private $office;
Do not forget to update the database schema.
based on these docs I would assume the default column name for your mapping should be id (note the case). You have capitalized your column name for some reason
#ORM\Column(name="Id" ...
I would suggest simply changing that to
#ORM\Column(name="id" ...
I am currently learning how to use the Symfony framework. The project that I'm working on is a Web API for a blog application.
Now I have created the necessary entities, provided data into it, set JWT Tokens, etc..
The next step was to automatically set an author (which is currently authorized with the token) to a written blog post. I've added some constraints and other annotations, but when I now use Postman to "POST" a new blog onto the DB it gives me the following error:
{
"title": "Latest Blog Post!",
"published": "2020-08-02 17:00:00",
"content": "This the contentof the latest blog post!",
"slug": "latest-blog-post"
}
Now, the thing is that the property "published" is of type datetime:
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\BlogPostRepository")
* #ApiResource(
* itemOperations={"get"},
* collectionOperations={
* "get",
* "post"={
* "access_control"="is_granted('IS_AUTHENTICATED_FULLY')"
* }
* }
* )
*/
class BlogPost
{
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank()
* #Assert\DateTime()
*/
private $published;
public function getPublished(): ?\DateTimeInterface
{
return $this->published;
}
public function setPublished(\DateTimeInterface $published): self
{
$this->published = $published;
return $this;
}
}
What am I overlooking here?
Deleted: #Assert\DateTime() and everything worked again properly.
I am reading and following along in code what is written in the Symfony2 book on using Database and Doctrine (http://symfony.com/doc/2.0/book/doctrine.html). I have reached the "Entity Relationships/Associations" section but the framework does not seem to be doing what it is meant to be doing. I have added the protected $category field to the Product entity and added the $products field to the Category entity. My Product and Category entities are as below:
Product:
<?php
namespace mydomain\mywebsiteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
/**
* Set description
*
* #param string $description
* #return Product
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Category:
<?php
namespace mydomain\mywebsiteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/*
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
public function __construct(){
$this->products = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set description
*
* #param string $description
* #return Category
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
}
According to the documentation, if i now execute
$ php app/console doctrine:generate:entities mydomain
the framework should generate the getters/setters for the new category field in Product and for the new products field in Category.
HOWEVER when i run the command it supposedly updates the entities but it does not add the properties. I have compared with the backup(~) files and there are no differences. If i add another field (e.g. description2) and add doctrine annotations for persistence to it then it generates the properties. I ignored this at first and manually added the properties for the mapping fields and then executed:
$php app/console doctrine:schema:update --force
for it to add the new association columns.
HOWEVER once again it told me that the metadata and schema were upto date.
I have deleted the app/cache/dev folder and allowed the system to recreate it but it has made no difference.
Can anyone see why the framework is not behaving as described in the documentation??
Thanks
You have forgotten one star here:
/*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
it must be
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
UPDATE: After trying different things with absolutely no success i ended up deleting the entire bundle and associated database and starting from scratch again. Second time around things are generated correctly and the database schema is being updated correctly. Such flaky behavior is EXTREMELY POOR of the framework and as mentioned in the comment above is the reason that as a developer i am moving away from Grails. Now i find that symfony2 has the same sort of problems.
When i use a framework i should not need to always keep in the back of my mind whether something is not working because the framework is buggy. This is quite unacceptable for such a mainstream framework and it would seem i am not the only person that has come across such kind of problems. The framework developers should definitely address such issues either by (preferably) resolving them or providing some means of understanding why the framework fails on random occasions.
Based on what I found the issue is that you can't have two types of definitions..in the book the entity create comand for category also creates a yml configoration so the annotations failed. You must use either annotations or yml or xml or php. Once I removed the yml config and recreated the tables with annotations it worked..be careful and don't use the comnad for the category createion..you will still though get an error that the description is mandatory field :)
I had the exact same issue and I solved it like this:
Delete the "doctrine"-Folder containing the yml-files with the (in your case redundant!) configuration for the entities. Do this ONLY on your test-system for educational purposes.
Some background-information (maybe someone with more experience than me - probably almost everybody here ;o)) can add to this:
Doctrine preferes YML-Schema configuration over annotations in the entity-class (/** #ORM ... */)
when working through the book you might have created a blog-entity with YML-Schema configuration a few chapters in before chapter 8 - maybe you played around a little and this YML-Schema is in the same bundle than your chapter 8 exercise
consequently: Doctrine thinks you want to use YML but it finds only configuation for "anotherEntity" but not for product and category
OR: you run a few Doctrine commands for testing and choose once (by mistake?) YML and voilà: all further annotation chances will be ignored because i.e. a product.orm.yml exists
Hope that helped. I just started chapter 10 ;-)
When it comes to generating getters and setters Symfony is just using the ReflectionClass to look if the methods already exist.
It doesn't look what properties are written in the annotation.
Concerning the schema update problem I don't have another solution then resetting the database and creating it from scratch.
I faced this problems a few times but never really found a good solution, it seems Symfony doesn't differ between some properties, which results in not finding any updates.
I don't have a framework by hand now, to look it up. Maybe you can try to find out what schema:update does exactly thus finding the error.
I wanted to have a created_by field for my model, say Product, that is automatically updated and I am using FOSUserBundle and Doctrine2. What is the recommended way of inputting the User id into Product?
Can I do it in the Product model? I am not sure how to do so and any help would be wonderful. Thanks!
I want to do something like this in the model, but I don't know how to get the user id.
/**
* Set updatedBy
*
* #ORM\PrePersist
* #ORM\PreUpdate
* #param integer $updatedBy
*/
public function setUpdatedBy($updatedBy=null)
{
if (is_null($updatedBy)) {
$updatedBy = $user->id;
}
$this->updatedBy = $updatedBy;
}
To relate the user to the product you want to associate the two entities:
http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="products")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* You may need to use the full namespace above instead of just User if the
* User entity is not in the same bundle e.g FOS\UserBundle\Entity\User
* the example is just a guess of the top of my head for the fos namespace though
*/
protected $user;
and for the automatic update field you may be after lifecyclecallbacks:
http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* #ORM\PreUpdate
*/
public function setCreatedValue()
{
$this->created = new \DateTime();
}
}
EDIT
This discussion talks about getting the container in the entity in which case you could then get the security.context and find the user id from that if you mean to associate the current user to the product they edited:
https://groups.google.com/forum/?fromgroups#!topic/symfony2/6scSB0Kgds0
//once you have the container you can get the session
$user= $this->container->get('security.context')->getToken()->getUser();
$updated_at = $user->getId();
Maybe that is what you are after, not sure it is a good idea to have the container in the entity though, could you not just set the user on the product in the update action in your product controller:
public function updateAction(){
//....
$user= $this->get('security.context')->getToken()->getUser();
$product->setUser($user)
}
File based translations don't work for me because clients need to change the texts.
So I am thinking about implementing this interface to fetch data from the database and cache the results in an APC cache.
Is this a good solution?
This could be what you are looking for:
Use a database as a translation provider in Symfony 2
Introduction
This article explain how to use a database as translation storage in Symfony 2. Using a database to provide translations is quite easy to do in Symfony 2, but unfortunately it’s actually not explained in Symfony 2 website.
Creating language entities
At first, we have to create database entities for language management. In my case, I’ve created three entities : the Language entity contain every available languages (like french, english, german).
The second entity is named LanguageToken. It represent every available language tokens. The token entity represent the source tag of the xliff files. Every translatable text available is a token. For example, I use home_page as a token and it’s translated as Page principale in french and as Home page in english.
The last entity is the LanguageTranslation entity : it contain the translation of a token in a specific language. In the example below, the Page principale is a LanguageTranslation entity for the language french and the token home_page.
It’s quite inefficient, but the translations are cached in a file by Symfony 2, finally it’s used only one time at Symfony 2 first execution (except if you delete Symfony 2’s cache files).
The code of the Language entity is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageRepository")
*/
class Language {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200) */
private $locale;
/** #ORM\column(type="string", length=200) */
private $name;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getLocale() {
return $this->locale;
}
public function setLocale($locale) {
$this->locale = $locale;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
The code of the LanguageToken entity is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTokenRepository")
*/
class LanguageToken {
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200, unique=true) */
private $token;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getToken() {
return $this->token;
}
public function setToken($token) {
$this->token = $token;
}
}
And the LanguageTranslation entity’s code is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTranslationRepository")
*/
class LanguageTranslation {
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200) */
private $catalogue;
/** #ORM\column(type="text") */
private $translation;
/**
* #ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\Language", fetch="EAGER")
*/
private $language;
/**
* #ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\LanguageToken", fetch="EAGER")
*/
private $languageToken;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getCatalogue() {
return $this->catalogue;
}
public function setCatalogue($catalogue) {
$this->catalogue = $catalogue;
}
public function getTranslation() {
return $this->translation;
}
public function setTranslation($translation) {
$this->translation = $translation;
}
public function getLanguage() {
return $this->language;
}
public function setLanguage($language) {
$this->language = $language;
}
public function getLanguageToken() {
return $this->languageToken;
}
public function setLanguageToken($languageToken) {
$this->languageToken = $languageToken;
}
}
Implementing a LoaderInterface
The second step is to create a class implementing the Symfony\Component\Translation\Loader\LoaderInterface. The corresponding class is shown here :
class DBLoader implements LoaderInterface{
private $transaltionRepository;
private $languageRepository;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager){
$this->transaltionRepository = $entityManager->getRepository("AppCommonBundle:LanguageTranslation");
$this->languageRepository = $entityManager->getRepository("AppCommonBundle:Language");
}
function load($resource, $locale, $domain = 'messages'){
//Load on the db for the specified local
$language = $this->languageRepository->getLanguage($locale);
$translations = $this->transaltionRepository->getTranslations($language, $domain);
$catalogue = new MessageCatalogue($locale);
/**#var $translation Frtrains\CommonbBundle\Entity\LanguageTranslation */
foreach($translations as $translation){
$catalogue->set($translation->getLanguageToken()->getToken(), $translation->getTranslation(), $domain);
}
return $catalogue;
}
}
The DBLoader class need to have every translations from the LanguageTranslationRepository (the translationRepository member). The getTranslations($language, $domain) method of the translationRepository object is visible here :
class LanguageTranslationRepository extends EntityRepository {
/**
* Return all translations for specified token
* #param type $token
* #param type $domain
*/
public function getTranslations($language, $catalogue = "messages"){
$query = $this->getEntityManager()->createQuery("SELECT t FROM AppCommonBundle:LanguageTranslation t WHERE t.language = :language AND t.catalogue = :catalogue");
$query->setParameter("language", $language);
$query->setParameter("catalogue", $catalogue);
return $query->getResult();
}
...
}
The DBLoader class will be created by Symfony as a service, receiving an EntityManager as constructor argument. All arguments of the load method let you customize the way the translation loader interface work.
Create a Symfony service with DBLoader
The third step is to create a service using the previously created class. The code to add to the config.yml file is here :
services:
translation.loader.db:
class: MyApp\CommonBundle\Services\DBLoader
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: translation.loader, alias: db}
The transation.loader tag indicate to Symfony to use this translation loader for the db alias.
Create fake translation files
The last step is to create an app/Resources/translations/messages.xx.db file for every translation (with xx = en, fr, de, …).
I didn’t found the way to notify Symfony to use DBLoader as default translation loader. The only quick hack I’ve found is to create a app/Resources/translations/messages.en.db file. The db extension correspond to the db alias used in the service declaration. A corresponding file is created for every language available on the website, like messages.fr.db for french or messages.de.db for german.
When Symfony find the messages.xx.db file he load the translation.loader.db to manage this unknown extension and then the DBLoader use database content to provide translation.
I’ve also didn’t found the way to clean properly the translations cache on database modification (the cache have to be cleaned to force Symfony to recreate it). The code I actually use is visible here :
/**
* Remove language in every cache directories
*/
private function clearLanguageCache(){
$cacheDir = __DIR__ . "/../../../../app/cache";
$finder = new \Symfony\Component\Finder\Finder();
//TODO quick hack...
$finder->in(array($cacheDir . "/dev/translations", $cacheDir . "/prod/translations"))->files();
foreach($finder as $file){
unlink($file->getRealpath());
}
}
This solution isn’t the pretiest one (I will update this post if I find better solution) but it’s working ^^
Be Sociable, Share!
Take a look at the Translatable behavior extension for Doctrine 2. StofDoctrineExtensionsBundle integrates it with Symfony.
You may want to take a look into this Loader + Resource using PDO connection: https://gist.github.com/3315472
You then only need to make it cache aware, like adding a memcache, apc, .. in between.
If so, you can then disable the filecaching of the Translator itself.