Setting up Payum Bundle with Symfony2 giving error - php

I am working with Symfony 2.6 and trying to setup PayumBundle (paypal express checkout) and I am getting an error
InvalidConfigurationException in BaseNode.php line 313: Invalid configuration for path "payum.security.token_storage": The storage entry must be a valid model class. It is set Acme\featuresBundle\Entity\PaymentToken
I am following the steps mentioned in there documetation
This is how my config.yml looks like
doctrine:
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
auto_mapping: true
mappings:
payum:
is_bundle: false
type: xml
dir: %kernel.root_dir%/../vendor/payum/core/Payum/Core/Bridge/Doctrine/Resources/mapping
prefix: Payum\Core\Model
payum:
security:
token_storage:
Acme\featuresBundle\Entity\PaymentToken: { doctrine: orm }
storages:
Acme\featuresBundle\Entity\PaymentDetails: { doctrine: orm }
contexts:
paypal:
paypal_express_checkout_nvp:
username: 'asdasd'
password: 'adsasd'
signature: 'asdasdasd'
sandbox: true
This is my Entity PaymentToken
namespace Acme\featuresBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\Token;
/**
* #ORM\Table
* #ORM\Entity
*/
class PaymentToken extends Token
{
}
And this is Entity PaymentDetails
namespace Acme\featuresBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\Order as BaseOrder;
/**
* #ORM\Table
* #ORM\Entity
*/
class PaymentDetails extends BaseOrder
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #var integer $id
*/
protected $id;
}
I have gone through alot of documentation online and other posts like this but I dont understand why I am getting this error.
The storage entry must be a valid model class. It is set Acme\featuresBundle\Entity\PaymentToken
I cant even get to the controller so something tells me it is the config.yml configuration of Payum that is not set correctly. I have gone through the documentation over and over and over and I cant seem to find what am I doing wrong.
I will really appreciate any help in getting pass this error.

I finally managed to get it done.
I needed 4 files
PaymentController
Orders (Entity)
PaymentToken (Entity)
Orders (Model)
This is my PaymentController looks like
<?php
namespace ClickTeck\featuresBundle\Controller;
use ClickTeck\featuresBundle\Entity\Orders;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Payum\Paypal\ExpressCheckout\Nvp\Api;
use Payum\Core\Registry\RegistryInterface;
use Payum\Core\Request\GetHumanStatus;
use Payum\Core\Security\GenericTokenFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration as Extra;
class PaymentController extends Controller
{
public function preparePaypalExpressCheckoutPaymentAction(Request $request)
{
$paymentName = 'paypal';
$eBook = array(
'author' => 'Jules Verne',
'name' => 'The Mysterious Island',
'description' => 'The Mysterious Island is a novel by Jules Verne, published in 1874.',
'price' => 8.64,
'currency_symbol' => '$',
'currency' => 'USD',
'clientId' => '222',
'clientemail' => 'xyz#abc.com'
);
$storage = $this->get('payum')->getStorage('ClickTeck\featuresBundle\Entity\Orders');
/** #var $paymentDetails Orders */
$paymentDetails = $storage->create();
$paymentDetails->setNumber(uniqid());
$paymentDetails->setCurrencyCode($eBook['currency']);
$paymentDetails->setTotalAmount($eBook['price']);
$paymentDetails->setDescription($eBook['description']);
$paymentDetails->setClientId($eBook['clientId']);
$paymentDetails->setClientEmail($eBook['clientemail']);
$paymentDetails['PAYMENTREQUEST_0_CURRENCYCODE'] = $eBook['currency'];
$paymentDetails['PAYMENTREQUEST_0_AMT'] = $eBook['price'];
$paymentDetails['NOSHIPPING'] = Api::NOSHIPPING_NOT_DISPLAY_ADDRESS;
$paymentDetails['REQCONFIRMSHIPPING'] = Api::REQCONFIRMSHIPPING_NOT_REQUIRED;
$paymentDetails['L_PAYMENTREQUEST_0_ITEMCATEGORY0'] = Api::PAYMENTREQUEST_ITERMCATEGORY_DIGITAL;
$paymentDetails['L_PAYMENTREQUEST_0_AMT0'] = $eBook['price'];
$paymentDetails['L_PAYMENTREQUEST_0_NAME0'] = $eBook['author'].'. '.$eBook['name'];
$paymentDetails['L_PAYMENTREQUEST_0_DESC0'] = $eBook['description'];
$storage->update($paymentDetails);
$captureToken = $this->getTokenFactory()->createCaptureToken(
$paymentName,
$paymentDetails,
'payment_done'
);
$paymentDetails['INVNUM'] = $paymentDetails->getId();
$storage->update($paymentDetails);
return $this->redirect($captureToken->getTargetUrl());
}
public function doneAction(Request $request)
{
$token = $this->get('payum.security.http_request_verifier')->verify($request);
$payment = $this->get('payum')->getPayment($token->getPaymentName());
// you can invalidate the token. The url could not be requested any more.
// $this->get('payum.security.http_request_verifier')->invalidate($token);
// Once you have token you can get the model from the storage directly.
//$identity = $token->getDetails();
//$order = $payum->getStorage($identity->getClass())->find($identity);
// or Payum can fetch the model for you while executing a request (Preferred).
$payment->execute($status = new GetHumanStatus($token));
$order = $status->getFirstModel();
// you have order and payment status
// so you can do whatever you want for example you can just print status and payment details.
return new JsonResponse(array(
'status' => $status->getValue(),
'response' => array(
'order' => $order->getTotalAmount(),
'currency_code' => $order->getCurrencyCode(),
'details' => $order->getDetails(),
),
));
}
/**
* #return RegistryInterface
*/
protected function getPayum()
{
return $this->get('payum');
}
/**
* #return GenericTokenFactoryInterface
*/
protected function getTokenFactory()
{
return $this->get('payum.security.token_factory');
}
}
This is my Orders Entity
<?php
namespace ClickTeck\featuresBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use ClickTeck\featuresBundle\Model\Orders as BasePaymentDetails;
/**
* Orders
*/
class Orders extends BasePaymentDetails
{
/**
* #var integer
*/
protected $id;
private $number;
private $description;
private $client_email;
private $client_id;
private $total_amount;
private $currency_code;
protected $details;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set number
*
* #param integer $number
* #return Orders
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* #return integer
*/
public function getNumber()
{
return $this->number;
}
/**
* Set description
*
* #param string $description
* #return Orders
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set client_email
*
* #param string $clientEmail
* #return Orders
*/
public function setClientEmail($clientEmail)
{
$this->client_email = $clientEmail;
return $this;
}
/**
* Get client_email
*
* #return string
*/
public function getClientEmail()
{
return $this->client_email;
}
/**
* Set client_id
*
* #param string $clientId
* #return Orders
*/
public function setClientId($clientId)
{
$this->client_id = $clientId;
return $this;
}
/**
* Get client_id
*
* #return string
*/
public function getClientId()
{
return $this->client_id;
}
/**
* Set total_amount
*
* #param float $totalAmount
* #return Orders
*/
public function setTotalAmount($totalAmount)
{
$this->total_amount = $totalAmount;
return $this;
}
/**
* Get total_amount
*
* #return float
*/
public function getTotalAmount()
{
return $this->total_amount;
}
/**
* Set currency_code
*
* #param string $currencyCode
* #return Orders
*/
public function setCurrencyCode($currencyCode)
{
$this->currency_code = $currencyCode;
return $this;
}
/**
* Get currency_code
*
* #return string
*/
public function getCurrencyCode()
{
return $this->currency_code;
}
/**
* Set details
*
* #param string $details
* #return Orders
*/
public function setDetails($details)
{
$this->details = $details;
return $this;
}
/**
* Get details
*
* #return string
*/
public function getDetails()
{
return $this->details;
}
}
This is my PaymentToken Entity
<?php
namespace ClickTeck\featuresBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\Token;
/**
* PaymentToken
*/
class PaymentToken extends Token
{
}
This is my Orders model
<?php
namespace ClickTeck\featuresBundle\Model;
use Payum\Core\Model\ArrayObject;
class Orders extends ArrayObject
{
protected $id;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
}
Now when I call the Action
preparePaypalExpressCheckoutPaymentAction via route
I get redirected to make the payment
I can see the response in doneAction
Very neat library. Took me a while to figure it out and I am glad it works now. I am sure i have alot more to learn about Payum and I hope someone can confirm if this is the right way :)

Related

API-Platform: Filtering Custom Data Provider

I'm currently experiencing issues when trying to filter my results when using an external API source (Stripe) in API-Platform.
What I need to be able to do, is return a list of subscriptions for a specified customer. So going to http://localhost/api/subscriptions?customer=123foo would return all records matched to this customer.
Now, the code below is throwing an error because of the ORM\Filter and would be functional without it, as the actual filtering is performed on Stripes API, not by me, BUT, I really want the Swagger-API GUI to have the filter box.
In short, how do I get the Annotations in my Entity to display, searchable fields within the Swagger UI when using an external data source.
What I have is an Entity as below (simplified for example purposes):
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Annotation\ApiFilter;
/**
* Subscriptions allow you to charge a customer on a recurring basis. A subscription ties a customer to a particular plan you've created.
* #ApiResource()
* #ApiFilter(SearchFilter::class, properties={"customer": "exact"})
* #package App\Entity
*/
class Subscription
{
/**
* Unique identifier for the object.
* #ApiProperty(identifier=true)
* #var string | null
*/
protected $id;
/**
* ID of the customer who owns the subscription.
* #var string | null
*/
protected $customer;
// Plus a bunch more properties and their Getters & Setters
}
And the SubscriptionCollectionDataProvider:
<?php
namespace App\DataProvider;
use App\Entity\Subscription;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Controller\BaseController;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class SubscriptionCollectionDataProvider
* #package App\DataProvider
* #author dhayward
*/
final class SubscriptionCollectionDataProvider extends BaseController implements CollectionDataProviderInterface, RestrictedDataProviderInterface
{
protected $requestStack;
/**
* SubscriptionCollectionDataProvider constructor.
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
/**
* #param string $resourceClass
* #param string|null $operationName
* #param array $context
* #return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Subscription::class === $resourceClass;
}
/**
* #param string $resourceClass
* #param string|null $operationName
* #return \Generator
* #throws \Stripe\Error\Api
*/
public function getCollection(string $resourceClass, string $operationName = null): \Generator
{
$customer = $this->request->get("customer");
$data = \Stripe\Subscription::all(["customer" => $customer]);
foreach($data['data'] as $subscriptionObject){
$this->serializer()->deserialize(json_encode($subscriptionObject), Subscription::class, 'json', array('object_to_populate' => $subscription = new Subscription()));
yield $subscription;
}
}
}
Error result, which is presumably because I'm using an ORM/Filter without any ORM setup:
Call to a member function getClassMetadata() on null
Any pointers would be greatly appreciated.
So I finally managed to work it out. It was as simple as creating my own version of the SearchFilter, implementing ApiPlatform\Core\Api\FilterInterface.
<?php
namespace App\Filter;
use ApiPlatform\Core\Api\FilterInterface;
/**
* Class SearchFilter
* #package App\Filter
*/
class SearchFilter implements FilterInterface
{
/**
* #var string Exact matching
*/
const STRATEGY_EXACT = 'exact';
/**
* #var string The value must be contained in the field
*/
const STRATEGY_PARTIAL = 'partial';
/**
* #var string Finds fields that are starting with the value
*/
const STRATEGY_START = 'start';
/**
* #var string Finds fields that are ending with the value
*/
const STRATEGY_END = 'end';
/**
* #var string Finds fields that are starting with the word
*/
const STRATEGY_WORD_START = 'word_start';
protected $properties;
/**
* SearchFilter constructor.
* #param array|null $properties
*/
public function __construct(array $properties = null)
{
$this->properties = $properties;
}
/**
* {#inheritdoc}
*/
public function getDescription(string $resourceClass): array
{
$description = [];
$properties = $this->properties;
foreach ($properties as $property => $strategy) {
$filterParameterNames = [
$property,
$property.'[]',
];
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
'property' => $property,
'type' => 'string',
'required' => false,
'strategy' => self::STRATEGY_EXACT,
'is_collection' => '[]' === substr($filterParameterName, -2),
];
}
}
return $description;
}
}

Controller doesn't return OneToMany relational field in Symfony 2

I need return full response with Document model. I have response but there are absent some fields, which are defined in entity. For example I need to have in response both 'campaign' and 'template' properties - but actually 'campaign' is absent.
Below are my controller and entity.
I have such action in my controller:
/**
* #REST\View(serializerGroups={"Default", "DocumentDetails"})
* #REST\Get("/{id}", requirements={"id" = "\d+"})
* #ParamConverter("document", class="AppBundle:Document");
*/
public function showAction(Request $request, Document $document)
{
return $document;
}
But the Document entity has relations:
/**
* Document entity
*
* #ORM\Entity(repositoryClass="AppBundle\Repository\DocumentRepository")
* #ORM\Table(name="document")
* #ORM\HasLifecycleCallbacks()
*
* #Serializer\ExclusionPolicy("all")
*/
class Document
{
.......
/**
* #var campaign
* #ORM\ManyToOne(targetEntity="Campaign", inversedBy="documents")
* #ORM\JoinColumn(name="campaign", referencedColumnName="id")
*
* #Serializer\Expose()
*/
protected $campaign; // **THIS FIELD IS ABSENT - WHY !???**
/**
* #var DocumentTemplate Szablon dokumentu
*
* #ORM\ManyToOne(targetEntity="DocumentTemplate")
* #ORM\JoinColumn(name="template_id", referencedColumnName="id")
*
* #Serializer\Expose()
*/
protected $template; // **THIS PROPERTY IS DISPLAYED**
.......
$document->template is present in $document response. But $document->campaign is absent. What is wrong ? Probably it is related somehow to serializerGroups ?? Thanks for any help.
Solved ! Thanks everyone for the help. The issue was related to JMSSerializer.
There was need to set this serializer in config file services.yml at first:
app.serializer.listener.document:
class: AppBundle\EventListener\Serializer\DocumentSerializationListener
tags:
- { name: jms_serializer.event_subscriber }
And then create this listener which is creating form child-field campaign and inserting there Campaign object:
<?php
namespace AppBundle\EventListener\Serializer;
use AppBundle\Entity\Campaign;
use AppBundle\Entity\Document;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
class DocumentSerializationListener implements EventSubscriberInterface
{
/**
* #param ObjectEvent $event
* #return void
*/
public function onPostSerialize(ObjectEvent $event)
{
$entity = $event->getObject();
if (!($entity instanceof Document)) {
return ;
}
$groups = $event->getContext()->attributes->get('groups')->getOrElse([]);
if (in_array('DocumentDetails', $groups)) {
$visitor = $event->getVisitor();
$campaign = $this->getCampaignClone($entity->getCampaign());
if ($visitor->hasData('campaign')) {
$visitor->setData('campaign', $campaign);
} else {
$visitor->addData('campaign', $campaign);
}
}
}
/**
* #inheritdoc
*/
public static function getSubscribedEvents()
{
return [
[
'event' => 'serializer.post_serialize',
'class' => 'AppBundle\Entity\Document',
'method' => 'onPostSerialize'
]
];
}
private function getCampaignClone(Campaign $documentCampaign)
{
$campaign = new \stdClass();
$campaign->id = $documentCampaign->getId();
$campaign->title = $documentCampaign->getTitle();
$campaign->status = $documentCampaign->getStatus();
$campaign->rows = $documentCampaign->getRows();
$campaign->createdAt = $documentCampaign->getCreatedAt()->format(DATE_W3C);
$campaign->updatedAt = $documentCampaign->getUpdated()->format(DATE_W3C);
return $campaign;
}
}
This looks weird I know - but this only solution I found to force inserting the Entity into the form request.

Form submit error data_class formview

I have a form builded by formbuilder
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->em->createQueryBuilder();
$query->select('sn.serial_nr')
->from('KopictAdminBundle:SerialNumber', 'sn');
$serialnumbers = $query->getQuery()->getResult();
$options = array();
foreach($serialnumbers as $serialnumber){
$options[$serialnumber['serial_nr']] = $serialnumber['serial_nr'];
}
$builder->add("serial_nr","text");
}
It shows the form correctly but when i submit it I get this error:
"The form's view data is expected to be an instance of class Kopict\AdminBundle\Entity\SerialNumber, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Kopict\AdminBundle\Entity\SerialNumber." at /var/www/kopadmin/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 373
This is how my entity looks like:
class SerialNumber
{
/**
* #var integer $id
*/
private $id;
/**
* #var interger $product_revision_id
*/
private $product_revision_id;
/**
* #var interger $booking_id
*/
private $booking_id;
/**
* #var string $serial_nr
*/
public $serial_nr;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set product_revision_id
*
* #param integer $product_revision_id
* #return SerialNumber
*/
public function setProduct_revision_id($product_revision_id)
{
$this->product_revision_id = $product_revision_id;
return $this;
}
/**
* Get product_revision_id
*
* #return integer
*/
public function getProduct_revision_id()
{
return $this->product_revision_id;
}
/**
* Set booking_id
*
* #param integer $booking_id
* #return SerialNumber
*/
public function setBooking_id($booking_id)
{
$this->booking_id = $booking_id;
return $this;
}
/**
* Get booking_id
*
* #return integer
*/
public function getBooking_id()
{
return $this->booking_id;
}
/**
* Set serial_nr
*
* #param string $serial_nr
* #return SerialNumber
*/
public function setSerial_nr($serial_nr)
{
$this->serial_nr = $serial_nr;
return $this;
}
/**
* Get serial_nr
*
* #return string
*/
public function getSerial_nr()
{
return $this->serial_nr;
}
}
I have tried to add the data_class but I can't find the good place to add it because the code keeps giving me te same error.
First of all, you need to make your serial_nr private otherwise no need to have getSerial_nr and setSerial_nr functions. Because you can reach to serial_nr outside of your class without having setters and getters.
Second, why you are adding serial numbers into options field?
Assuming you want to have serial numbers as a choice field. I have a solution for you.
Usually entities are related in Doctrine ORM as many-to-one one-to-many. In that case its very simple to get related fields as a choice field. For this case symfony has a built in entity field.
SerialNumberType - form type class. (You have to change this name to yours)
<?php
namespace Kopict\AdminBundle\Form;
use Doctrine\ORM\em;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class SerialNumberType extends AbstractType
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$query = $this->em->createQueryBuilder();
$query->select('sn.serial_nr')->from('KopictAdminBundle:SerialNumber', 'sn');
$serialNumbers = $query->getQuery()->getResult();
$choices = array();
foreach ($serialNumbers as $serialNumber) {
$choices[$serialNumber['serial_nr']] = $serialNumber['serial_nr'];
}
$builder->add("serial_nr", "choice", array(
'choices' => $choices,
));
}
public function getName()
{
return 'app_bundle_serial_number_type';
}
}
Inside Controller Action
<?php
namespace Kopict\AdminBundle\Controller;
use Kopict\AdminBundle\Entity\SerialNumber;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$serialNumber = new SerialNumber();
$form = $this->createForm($this->get('kopict_admin.form.serialnumber'), $serialNumber);
return $this->render('KopictAdminBundle:Default:index.html.twig', array('form' => $form->createView()));
}
}
services.yml
services:
kopict_admin.form.serialnumber:
class: Kopict\AdminBundle\Form\SerialNumberType
arguments: [ #doctrine.orm.entity_manager ]
scope: request

Sylius: adding resource with translateable content

I am building an app based on Sylius standard edition. With the ResourceBundle i handled to integrate my own entities and the corresponding relations. This new resources should be related later to the products entity, but first i want to get it working "standalone". The backend works for both, the added resource and for relations. These are editable via form-collections. Very fine! Now i want to get translated database-content for my new resource. I tried the way i did it in Symfony earlier, but it didn't work. The last vew days i tried every possible solution found, but none of this works, or i made mistakes... Neither the translation-tables where constructed when typing:
app/console doctrine:schema:update --force
nor translatable content is visible in the forms. When calling the edit action, i get following error:
error:
Neither the property "translations" nor one of the methods "getTranslations()", "translations()", "isTranslations()", "hasTranslations()", "__get()" exist and have public access in class "KontaktBundle\Entity\Kontakte".
Is somebody out there with an example implementation of a extended Sylius-resource with translateable database-content? I'm still learning symfony and sylius too, can you tell me what i'm missing or doing wrong?
Many Thanks to #gvf. Now i figured out that the config entry must be set. This gave me a functional example which i want to provide here:
Configs
# app/config/sylius_config.yml (must be imported in config)
# Adding Resource
sylius_resource:
# Resource Settings
settings:
sortable: true
paginate: 50
allowed_paginate: [50, 100, 500]
filterable: true
resources:
dbk.authors:
driver: doctrine/orm
templates: AuthorBundle:Backend
object_manager: default
classes:
model: AuthorBundle\Entity\Kontakte
#interface: // if you have an interface configured
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
translation:
model: AuthorBundle\Entity\AuthorsTranslation
mapping:
fields: ['anrede','biografie']
Services
# app/config/sylius_services.yml (must be imported in config)
parameters:
# Parameter for our author entity
app.form.type.authors.class: AuthorBundle\Form\AuthorsType
app.form.type.authors_translation.class: AuthorBundle\Form\AuthorsTranslationType
services:
# Adding Authors Backend menu Item
dbk_backend_authors.menu_builder:
class: AuthorBundle\EventListener\MenuBuilderListener
tags:
- { name: kernel.event_listener, event: sylius.menu_builder.backend.main, method: addBackendMenuItems }
- { name: kernel.event_listener, event: sylius.menu_builder.backend.sidebar, method: addBackendMenuItems }
# Adding Authors FormType
app.form.type.authors:
class: "%app.form.type.authors.class%"
tags:
- {name: form.type, alias: dbk_authors }
app.form.type.authors_translation:
class: "%app.form.type.authors_translation.class%"
tags:
- {name: form.type, alias: dbk_authors_translation }
EventListener (Adds Menu Entry in Sylius-Backend)
<?php
// AuthorBundle/EventListener/MenuBuilderListener/MenuBuilderListener.php
namespace AuthorBundle\EventListener;
use Sylius\Bundle\WebBundle\Event\MenuBuilderEvent;
class MenuBuilderListener
{
public function addBackendMenuItems(MenuBuilderEvent $event)
{
$menu = $event->getMenu();
$menu['assortment']->addChild('vendor', array(
'route' => 'Authors',
'labelAttributes' => array('icon' => 'glyphicon glyphicon-user'),
))->setLabel('Authors');
}
}
Entities
Authors (The new resource hich we want to add and to translate)
<?php
// AuthorBundle/Entity/Authors.php
namespace AuthorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Translation\Model\AbstractTranslatable;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Authors
*
* #ORM\Entity
* #ORM\Table(name="authors")
*/
class Authors extends AbstractTranslatable
{
//
// IDENTIFIER FIELDS
//
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", length=20, nullable=false, options={"unsigned":"true"})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//
// FIELDS
//
/**
* #var string
*
* #ORM\Column(name="vorname", type="string", length=255)
*/
private $vorname;
/**
* #var string
*
* #ORM\Column(name="nachname", type="string", length=255)
*/
private $nachname;
public function __construct() {
parent::__construct();
}
//
// TranslationFields - Getters and Setters
//
/**
* Get Anrede
* #return string
*/
public function getAnrede()
{
return $this->translate()->getAnrede();
}
/**
* Set Anrede
*
* #param string $anrede
*
* #return Authors
*/
public function setAnrede($anrede)
{
$this->translate()->setAnrede($anrede);
return $this;
}
/**
* Get Biografie
* #return string
*/
public function getBiografie()
{
return $this->translate()->getBiografie();
}
/**
* Set Biografie
*
* #param string $biografie
*
* #return Authors
*/
public function setBiografie($biografie)
{
$this->translate()->setBiografie($biografie);
return $this;
}
//
// Getters and Setters
//
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set vorname
*
* #param string $vorname
*
* #return Authors
*/
public function setVorname($vorname)
{
$this->vorname = $vorname;
return $this;
}
/**
* Get vorname
*
* #return string
*/
public function getVorname()
{
return $this->vorname;
}
/**
* Set nachname
*
* #param string $nachname
*
* #return Authors
*/
public function setNachname($nachname)
{
$this->nachname = $nachname;
return $this;
}
/**
* Get nachname
*
* #return string
*/
public function getNachname()
{
return $this->nachname;
}
public function __toString(){
return $this->getFullName();
}
}
AuthorsTranslation (This is the Entity for tanslation of Authors)
<?php
// AuthorBundle/Entity/AuthorsTranslation.php
namespace AuthorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Translation\Model\AbstractTranslation;
/**
* AuthorsTranslation
*
* #ORM\Entity
* #ORM\Table(name="authors_translation")
*/
class AuthorsTranslation extends AbstractTranslation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", length=20, nullable=false, options={"unsigned":"true"})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//
// TRANSLATABLE - FIELDS
//
/**
* #var string
* #ORM\Column(name="anrede", type="string", length=255)
*/
private $anrede;
/**
* #var string
* #ORM\Column(name="biografie", type="text")
*/
private $biografie;
/**
* {#inheritdoc}
*/
public function getId()
{
return $this->id;
}
//
// GETTERS AND SETTERS
//
/**
* Set anrede
*
* #param string $anrede
* #return Authors
*/
public function setAnrede($anrede)
{
$this->anrede = $anrede;
return $this;
}
/**
* Get anrede
*
* #return string
*/
public function getAnrede()
{
return $this->anrede;
}
/**
* Set biografie
*
* #param string $biografie
*
* #return Authors
*/
public function setBiografie($biografie)
{
$this->biografie = $biografie;
return $this;
}
/**
* Get biografie
*
* #return string
*/
public function getBiografie()
{
return $this->biografie;
}
}
FormTypes
AuthorsType
<?php
// AuthorBundle/Form/AuthorsType.php
namespace AuthorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\QueryBuilder;
class AuthorsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Add Translations to Form.
->add('translations', 'a2lix_translations', array(
'required' => false,
'fields' => array(
'anrede' => array(
'label' => 'Anrede',
),
'biografie' => array(
'label' => 'Biografie',
'attr' => array('data-edit' => 'wysiwyg', 'rows' => '15'),
'required' => false,
)
)
))
->add('vorname', null, array(
'label' => 'Vorname',
'required' => false,
))
->add('nachname', null, array(
'label' => 'Nachname',
'required' => false,
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
'data_class' => 'AuthorBundle\Entity\Authors'
));
}
/**
* #return string
*/
public function getName()
{
return 'dbk_authors';
}
}
AuthorsTranslationType
<?php
// AuthorBundle/Form/AuthorsTranslationType.php
namespace AuthorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorsTranslationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('anrede', 'text', array(
'label' => 'Anrede'
))
->add('biografie', 'textarea', array(
'label' => 'Biografie'
))
;
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'dbk_authors_translation';
}
}
Form Template
{# AuthorBundle/Resources/views/backend/edit||add.html.twig - as you like... the call of form.translation (line 5) is the point here #}
{% form_theme form 'SyliusWebBundle:Backend:forms.html.twig' %}
<div class="row">
<div class="col-md-12">
{{ form_row(form.translations, {'attr': {'class': 'input-lg'}}) }}
{{ form_row(form.vorname) }}
{{ form_row(form.nachname) }}
</div>
</div>
Have a look at the Product and ProductTranslation under the models folder in Sylius Components to see an example of how Sylius implements it.
Kontakte needs to extend AbstractTranslatable, you also need to create a class KontakteTranslation that extends AbstractTranslation. Under sylius_resource you also need to configure the translations:
sylius_resource:
resources:
dbk.contact:
driver: doctrine/orm
templates: KontaktBundle:Backend
object_manager: default
classes:
model: KontaktBundle\Entity\Kontakte
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
translation:
model: KontaktBundle\Entity\KontakteTranslation
mapping:
fields: {...fields that will be translated...}
Get rid of gedmo translatable extension because Sylius doesn't use it.

Doctrine2 - Trigger event on property change (PropertyChangeListener)

I am not writing "what did I try" or "what is not working" since I can think of many ways to implement something like this. But I cannot believe that no one did something similar before and that is why I would like to ask the question to see what kind of Doctrine2 best practices show up.
What I want is to trigger an event on a property change. So let's say I have an entity with an $active property and I want a EntityBecameActive event to fire for each entity when the property changes from false to true.
Other libraries often have a PropertyChanged event but there is no such thing available in Doctrine2.
So I have some entity like this:
<?php
namespace Application\Entity;
class Entity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var boolean
* #ORM\Column(type="boolean", nullable=false)
*/
protected $active = false;
/**
* Get active.
*
* #return string
*/
public function getActive()
{
return $this->active;
}
/**
* Is active.
*
* #return string
*/
public function isActive()
{
return $this->active;
}
/**
* Set active.
*
* #param bool $active
* #return self
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
}
Maybe ChangeTracking Policy is what you want, maybe it is not!
The NOTIFY policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that purpose,
a class that wants to use this policy needs to implement the
NotifyPropertyChanged interface from the Doctrine\Common namespace.
Check full example in link above.
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
UPDATE
This is a full example but silly one so you can work on it as you wish. It just demonstrates how you do it, so don't take it too serious!
entity
namespace Football\TeamBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="country")
*/
class Country extends DomainObject
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="smallint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(type="string", length=2, unique=true)
*/
protected $code;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
* #return Country
*/
public function setCode($code)
{
if ($code != $this->code) {
$this->onPropertyChanged('code', $this->code, $code);
$this->code = $code;
}
return $this;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
}
domainobject
namespace Football\TeamBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
protected function onPropertyChanged($propName, $oldValue, $newValue)
{
$filename = '../src/Football/TeamBundle/Entity/log.txt';
$content = file_get_contents($filename);
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
file_put_contents($filename, $content . "\n" . time());
}
}
}
}
controller
namespace Football\TeamBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Football\TeamBundle\Entity\Country;
class DefaultController extends Controller
{
public function indexAction()
{
// First run this to create or just manually punt in DB
$this->createAction('AB');
// Run this to update it
$this->updateAction('AB');
return $this->render('FootballTeamBundle:Default:index.html.twig', array('name' => 'inanzzz'));
}
public function createAction($code)
{
$em = $this->getDoctrine()->getManager();
$country = new Country();
$country->setCode($code);
$em->persist($country);
$em->flush();
}
public function updateAction($code)
{
$repo = $this->getDoctrine()->getRepository('FootballTeamBundle:Country');
$country = $repo->findOneBy(array('code' => $code));
$country->setCode('BB');
$em = $this->getDoctrine()->getManager();
$em->flush();
}
}
And have this file with 777 permissions (again, this is test) to it: src/Football/TeamBundle/Entity/log.txt
When you run the code, your log file will have timestamp stored in it, just for demonstration purposes.

Categories