Is it possible to retrieve the html code generated from controller A within controller B?
Controller A
/**
*
*
* #Route("/{user_id}/cart", name="user_cart")
* #Template()
*/
public function showCartAction($user_id)
{
$cart = $this->getCartManager()
->getUserCart($user_id);
return array(
'cart'=> cart
);
}
Controller B
/**
*
*
* #Route("/html", name="htmlGenerated")
* #Template()
*/
public function showHTMLAction()
{
$user_id = 3;
$html = //How to obtain the html generated by Controller A with UserId = 3 ????
//...
}
You could forward the request in Controller B
public function showHTMLAction()
{
$user_id = 3;
$html = $this->forward('AcmeDemoBundle:ControllerB:showCardAction', array(
'user_id' => $user_id,
))->getContent();
}
Even though this should work perfectly fine, I would actually advise you to embed the controller in your template instead.
Related
I have several user roles with access to orders and controllers for each of them. Are there ways to change the normalizer for one entity, for example...
in this action i need to get the normalizer for the courier:
## CourierController
/**
* #Rest\Get()
*/
public function orders()
{
$serializer = $this->get('serializer');
$orders = $this->getDoctrine()
->getRepository(Order::class)
->findBy(['courier' => $this->getUser()->getCourierAccount()]);
$data = $serializer->normalize($orders); // <--------- 1) how to choose the right normalizer?
return $this->json($data);
}
But in this i need for something like 'ClientOrderNormalizer'
## ClientController
/**
* #Rest\Get()
*/
public function orders()
{
$serializer = $this->get('serializer');
$orders = $this->getDoctrine()
->getRepository(Order::class)
->findBy(['client' => $this->getUser()->getClientAccount()]);
$data = $serializer->normalize($orders); // <--------- 2) how to choose the right normalizer?
return $this->json($data);
}
1) Using Serialization Groups Annotations -> look here
2) Create custom DTO then serialize this object for a response.
Example of how to create custom DTO.
final class SignInResponse implements ResponseInterface
{
/**
* #var string
*
* #SWG\Property(type="string", description="Token.")
*/
private string $token;
public function __construct(UserSession $userSession)
{
$this->token = $userSession->getToken();
}
/**
* Get Token
*
* #return string
*/
public function getToken(): string
{
return $this->token;
}
}
I'm currently trying out Symfony 4, but I am having some problems with events triggered by database action (prePersist, preUpdate...)
With Symfony 3, I used to use EntityListener to accomplish this, but I found them really convoluted in Symfony 4 documentation. But I also discovered the LifecycleCallbacks, that I used like this:
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Post
{
//Attributes and other functions not included for the sake of clarity, but if I use them, consider that they exist
/**
* #ORM\PrePersist
*/
public function setPostSlug()
{
$title = $this->getPostTitle();
$title = strtolower($title);
$keywords = preg_split("/[\s,']+/", $title);
$slug = implode('-', $keywords);
dump($slug);
$this->$slug = $slug;
return $this;
}
}
My post are created through a Symfony form, and before persistence, I want to break down the title I gave to my post in a standardized string that I will use in my URLs to access said post. Unfortunately, the event never trigger on persistence, despite the slug being generated correctly. I tried to do the operation both on prePersist and postPersist events, but none worked. I searched the issue, and saw that LifecycleCallbacks needed a cache clear to be taken into account, but doing so didn't help.
Here is the action responsible for the post creation, if that might help:
/**
* #Route("/admin/create/post", name="admin-create-post")
* #param Request $request
*/
public function createPost(Request $request)
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em = $this->getDoctrine()->getManager();
$post = $form->getData();
$em->persist($post);
$em->flush();
$this->redirectToRoute('main');
}
return $this->render('admin/new_post.html.twig', array(
'form' => $form->createView()
));
}
Would you know the source of the problem, or which other tools I could use to obtain the desired result?
Thanks in advance.
I handle complex Lifecycle with EventListener
for this .. do :
# services.yml
AppBundle\EventListener\YourListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
// YourListener.php
namespace AppBundle\EventListener;
class YourListener {
/**
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args): void
{
$post = $args->getEntity();
if ($post instanceof Post) {
// Do your job
}
}
}
But I use symfony EventListenerSubscriber Like this:
/**
* This needs to be set through passed argument in case of accident duplicate
*
* #ORM\PrePersist()
*/
public function setTrackingNumber()
{
$this->trackingNumber = NumberCreator::randomStringWithNDigits(self::TRACKING_DIGIT_COUNT);
}
so I think you need do that in your slug setter like this
/**
* #ORM\PrePersist
*/
public function setSlug()
{
$title = $this->getPostTitle();
$title = strtolower($title);
$keywords = preg_split("/[\s,']+/", $title);
$slug = implode('-', $keywords);
dump($slug);
$this->$slug = $slug;
return $this;
}
I think method name is issue ... I hope this is help to you
I created Block in Drupal 8 with a custom module.
Is it possible to implement this with PHPUnit?
If you can implement it please tell me how.
I want to realize the test with PHPUnit below.
I would be pleased if you could reply just whether it was possible or not.
moduleNameBlock.php
/**
* #file
* create block
*/
namespace Drupal\moduleName\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Url;
/**
*
* Provides a 'testBlock' block.
* #Block(
* id = "test_block",
* admin_label = #Translation("Test"),
* category = #Translation("Menu"),
* )
*/
class moduleNameBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build()
{
$build = [];
$url = '';
$nid = '';
$nid = $this->getCurrentUserNode();
if ( !empty($nid) ) {
$url = Url::fromRoute('entity.node.canonical', ['node' => $nid]);
}
$block = [
'#theme' => 'block_theme',
'#url' => $url,
'#nid' => $nid,
'#cache' => [
'max-age' => 0
]
];
$build['test_block'] = $block;
return $build;
}
/**
* The node associated with the user
* #return nid
*/
private function getCurrentUserNode() {
$user_id = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
$nid = $user_id->get('field_name')->getValue();
return $nid[0]['target_id'];
}
}
Yes this is possible by writing a PHPUnit Functional test.
In your module directory create the following structure /tests/src/Functional then create a file like ModuleNameBlockTest.php then you can place the block in the setUp function and create tests to test the block.
<?php
namespace Drupal\Tests\my_module_name\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Class ModuleNameBlockTest.
*
* #package Drupal\Tests\my_module_name\Functional
* #group my_group
*/
class ModuleNameBlockTest extends BrowserTestBase {
/**
* Modules to enable.
*
* #var array
*/
public static $modules = ['block', 'my_module_name'];
/**
* {#inheritdoc}
*/
protected function setUp() {
parent::setUp();
$adminUser = $this->drupalCreateUser(['administer blocks']);
$this->drupalLogin($adminUser);
$this->drupalPlaceBlock('my_block_name');
$this->drupalLogout($adminUser);
}
/**
* Test the block.
*/
public function testMyAwesomeBlock() {
// Your test logic here.
}
}
You can always look into the source code of Drupal for some examples. E.g. UserBlocksTest.php of the core user module.
I am using the Silverstripe Swipestripe module for an online store. Because of the number of products the client has, it's not practical to nave them navigate to each individual product through the site tree when they want to make changes to a product (which happens fairly regularly) so I'd like to have a modeladmin to list all products and allow them to search for a product by name/stockcode.
I thought this would be solvable in the same way as DataObjects (and searches seem to suggest that people have done achieved this), however when I navigate to products in ModelAdmin view, I get:
Fatal Error : Call to a member function stat() on a non-object in
/path/to/folder/wwwroot/framework/model/DataObject.php on line 3192
<?php
class ProductAdmin extends ModelAdmin
{
private static $managed_models = array('Product');
private static $url_segment = 'product';
private $menu_title = 'Products';
}
Interestingly, pages and other extensions of the page class do work.
Here is the code for the Product class:
class Product extends Page {
/**
* Flag for denoting if this is the first time this Product is being written.
*
* #var Boolean
*/
protected $firstWrite = false;
/**
* DB fields for Product.
*
* #var Array
*/
private static $db = array(
'Price' => 'Decimal(19,4)',
'Currency' => 'Varchar(3)',
'StockCode' => 'Varchar(255)',
'Stock' => 'Int',
'Featured' => 'Boolean',
'YouTubeID' => 'Varchar(255)'
);
/**
* Actual price in base currency, can decorate to apply discounts etc.
*
* #return Price
*/
public function Amount() {
// TODO: Multi currency
$shopConfig = ShopConfig::current_shop_config();
$amount = new Price();
$amount->setAmount($this->Price);
$amount->setCurrency($shopConfig->BaseCurrency);
$amount->setSymbol($shopConfig->BaseCurrencySymbol);
//Transform amount for applying discounts etc.
$this->extend('updateAmount', $amount);
return $amount;
}
/**
* Display price, can decorate for multiple currency etc.
*
* #return Price
*/
public function Price() {
$amount = $this->Amount();
//Transform price here for display in different currencies etc.
$this->extend('updatePrice', $amount);
return $amount;
}
/**
* Has many relations for Product.
*
* #var Array
*/
private static $has_many = array(
'Attributes' => 'Attribute',
'Options' => 'Option',
'Variations' => 'Variation'
);
/**
* Defaults for Product
*
* #var Array
*/
private static $defaults = array(
'ParentID' => -1,
'Stock' => 999
);
/**
* Summary fields for displaying Products in the CMS
*
* #var Array
*/
private static $summary_fields = array(
'Amount.Nice' => 'Price',
'Title' => 'Title'
);
private static $searchable_fields = array(
'Title' => array(
'field' => 'TextField',
'filter' => 'PartialMatchFilter',
'title' => 'Name'
)
);
/**
* Set firstWrite flag if this is the first time this Product is written.
*
* #see SiteTree::onBeforeWrite()
* #see Product::onAfterWrite()
*/
public function onBeforeWrite() {
parent::onBeforeWrite();
if (!$this->ID) $this->firstWrite = true;
//Save in base currency
$shopConfig = ShopConfig::current_shop_config();
$this->Currency = $shopConfig->BaseCurrency;
}
/**
* Unpublish products if they get deleted, such as in product admin area
*
* #see SiteTree::onAfterDelete()
*/
public function onAfterDelete() {
parent::onAfterDelete();
if ($this->isPublished()) {
$this->doUnpublish();
}
}
/**
* Set some CMS fields for managing Products
*
* #see Page::getCMSFields()
* #return FieldList
*/
public function getCMSFields() {
$shopConfig = ShopConfig::current_shop_config();
$fields = parent::getCMSFields();
//Product fields
$fields->addFieldToTab('Root.Main', new PriceField('Price'), 'Content');
$fields->addFieldToTab('Root.Main', new TextField('StockCode'), 'Price');
$fields->addFieldToTab('Root.Main', new TextField('Stock'), 'Price');
$fields->addFieldToTab('Root.Main', new CheckBoxField('Featured'), 'Content');
$fields->addFieldToTab('Root.Images', new TextField('YouTubeID', 'YouTube Video ID (Taken from the end of the video url. ie https://www.youtube.com/watch?v=ABC123 would be ABC123)'));
//Replace URL Segment field
if ($this->ParentID == -1) {
$urlsegment = new SiteTreeURLSegmentField("URLSegment", 'URLSegment');
$baseLink = Controller::join_links(Director::absoluteBaseURL(), 'product/');
$url = (strlen($baseLink) > 36) ? "..." .substr($baseLink, -32) : $baseLink;
$urlsegment->setURLPrefix($url);
$fields->replaceField('URLSegment', $urlsegment);
}
if ($this->isInDB()) {
//Product attributes
$listField = new GridField(
'Attributes',
'Attributes',
$this->Attributes(),
GridFieldConfig_BasicSortable::create()
);
$fields->addFieldToTab('Root.Attributes', $listField);
//Product variations
$attributes = $this->Attributes();
if ($attributes && $attributes->exists()) {
//Remove the stock level field if there are variations, each variation has a stock field
$fields->removeByName('Stock');
$variationFieldList = array();
foreach ($attributes as $attribute) {
$variationFieldList['AttributeValue_'.$attribute->ID] = $attribute->Title;
}
$variationFieldList = array_merge($variationFieldList, singleton('Variation')->summaryFields());
$config = GridFieldConfig_HasManyRelationEditor::create();
$dataColumns = $config->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields($variationFieldList);
$listField = new GridField(
'Variations',
'Variations',
$this->Variations(),
$config
);
$fields->addFieldToTab('Root.Variations', $listField);
}
}
//Ability to edit fields added to CMS here
$this->extend('updateProductCMSFields', $fields);
if ($warning = ShopConfig::base_currency_warning()) {
$fields->addFieldToTab('Root.Main', new LiteralField('BaseCurrencyWarning',
'<p class="message warning">'.$warning.'</p>'
), 'Title');
}
return $fields;
}
/**
* Get the URL for this Product, products that are not part of the SiteTree are
* displayed by the {#link Product_Controller}.
*
* #see SiteTree::Link()
* #see Product_Controller::show()
* #return String
*/
public function Link($action = null) {
if ($this->ParentID > -1) {
return parent::Link($action);
}
return Controller::join_links(Director::baseURL() . 'product/', $this->RelativeLink($action));
}
/**
* A product is required to be added to a cart with a variation if it has attributes.
* A product with attributes needs to have some enabled {#link Variation}s
*
* #return Boolean
*/
public function requiresVariation() {
$attributes = $this->Attributes();
return $attributes && $attributes->exists();
}
/**
* Get options for an Attribute of this Product.
*
* #param Int $attributeID
* #return ArrayList
*/
public function getOptionsForAttribute($attributeID) {
$options = new ArrayList();
$variations = $this->Variations();
if ($variations && $variations->exists()) foreach ($variations as $variation) {
if ($variation->isEnabled()) {
$option = $variation->getOptionForAttribute($attributeID);
if ($option) $options->push($option);
}
}
$options = $options->sort('SortOrder');
return $options;
}
/**
* Validate the Product before it is saved in {#link ShopAdmin}.
*
* #see DataObject::validate()
* #return ValidationResult
*/
public function validate() {
$result = new ValidationResult();
//If this is being published, check that enabled variations exist if they are required
$request = Controller::curr()->getRequest();
$publishing = ($request && $request->getVar('action_publish')) ? true : false;
if ($publishing && $this->requiresVariation()) {
$variations = $this->Variations();
if (!in_array('Enabled', $variations->map('ID', 'Status')->toArray())) {
$result->error(
'Cannot publish product when no variations are enabled. Please enable some product variations and try again.',
'VariationsDisabledError'
);
}
}
return $result;
}
}
Can anyone suggest what I'm doing wrong here or an alternative way to do what I'm trying to achieve?
Cheers
Just in case anyone still having the same problem.
So, it happens if you have installed Product Categories module for swipestripe.
The cause of this is private static $searchable_fields in ProductCategory_Extension class in ProductCategory.php file in that module. Just comment out that field and it will work.
It is because the dataobject class tries to stat Category as a class - which obviously doesnt exist.
I will fix it and push to github if I get some time. Just wanted to update here so that others dont waste time scratching head why it doesnt work.
Accessing my route /message/new i'm going to show a form for sending a new message to one or more customers. Form model has (among others) a collection of Customer entities:
class MyFormModel
{
/**
* #var ArrayCollection
*/
public $customers;
}
I'd like to implement automatic customers selection using customers GET parameters, like this:
message/new?customers=2,55,543
This is working now by simply splitting on , and do a query for getting customers:
public function newAction(Request $request)
{
$formModel = new MyFormModel();
// GET "customers" parameter
$customersIds = explode($request->get('customers'), ',');
// If something was found in "customers" parameter then get entities
if(!empty($customersIds)) :
$repo = $this->getDoctrine()->getRepository('AcmeHelloBundle:Customer');
$found = $repo->findAllByIdsArray($customersIds);
// Assign found Customer entities
$formModel->customers = $found;
endif;
// Go on showing the form
}
How can i do the same using Symfony 2 converters? Like:
public function newAction(Request $request, $selectedCustomers)
{
}
Answer to my self: there is not such thing to make you life easy. I've coded a quick and dirty (and possibly buggy) solution i'd like to share, waiting for a best one.
EDIT WARNING: this is not going to work with two parameter converters with the same class.
Url example
/mesages/new?customers=2543,3321,445
Annotations:
/**
* #Route("/new")
* #Method("GET|POST")
* #ParamConverter("customers",
* class="Doctrine\Common\Collections\ArrayCollection", options={
* "finder" = "getFindAllWithMobileByUserQueryBuilder",
* "entity" = "Acme\HelloBundle\Entity\Customer",
* "field" = "id",
* "delimiter" = ",",
* }
* )
*/
public function newAction(Request $request, ArrayCollection $customers = null)
{
}
Option delimiter is used to split GET parameter while id is used for adding a WHERE id IN... clause. There are both optional.
Option class is only used as a "signature" to tell that converter should support it. entity has to be a FQCN of a Doctrine entity while finder is a repository method to be invoked and should return a query builder (default one provided).
Converter
class ArrayCollectionConverter implements ParamConverterInterface
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
function apply(Request $request, ConfigurationInterface $configuration)
{
$name = $configuration->getName();
$options = $this->getOptions($configuration);
// Se request attribute to an empty collection (as default)
$request->attributes->set($name, new ArrayCollection());
// If request parameter is missing or empty then return
if(is_null($val = $request->get($name)) || strlen(trim($val)) === 0)
return;
// If splitted values is an empty array then return
if(!($items = preg_split('/\s*'.$options['delimiter'].'\s*/', $val,
0, PREG_SPLIT_NO_EMPTY))) return;
// Get the repository and logged user
$repo = $this->getEntityManager()->getRepository($options['entity']);
$user = $this->getSecurityContext->getToken()->getUser();
if(!$finder = $options['finder']) :
// Create a new default query builder with WHERE user_id clause
$builder = $repo->createQueryBuilder('e');
$builder->andWhere($builder->expr()->eq("e.user", $user->getId()));
else :
// Call finder method on repository
$builder = $repo->$finder($user);
endif;
// Edit the builder and add WHERE IN $items clause
$alias = $builder->getRootAlias() . "." . $options['field'];
$wherein = $builder->expr()->in($alias, $items);
$result = $builder->andwhere($wherein)->getQuery()->getResult();
// Set request attribute and we're done
$request->attributes->set($name, new ArrayCollection($result));
}
public function supports(ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
// Check if class is ArrayCollection from Doctrine
if('Doctrine\Common\Collections\ArrayCollection' !== $class)
return false;
$options = $this->getOptions($configuration);
$manager = $this->getEntityManager();
// Check if $options['entity'] is actually a Dcontrine one
try
{
$manager->getClassMetadata($options['entity']);
return true;
}
catch(\Doctrine\ORM\Mapping\MappingException $e)
{
return false;
}
}
protected function getOptions(ConfigurationInterface $configuration)
{
return array_replace(
array(
'entity' => null,
'finder' => null,
'field' => 'id',
'delimiter' => ','
),
$configuration->getOptions()
);
}
/**
* #return \Doctrine\ORM\EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.default_entity_manager');
}
/**
* #return \Symfony\Component\Security\Core\SecurityContext
*/
protected function getSecurityContext()
{
return $this->container->get('security.context');
}
}
Service definition
arraycollection_converter:
class: Acme\HelloBundle\Request\ArrayCollectionConverter
arguments: ['#service_container']
tags:
- { name: request.param_converter}
It's late, but according to latest documentation about #ParamConverter, you can achieve it follow way:
* #ParamConverter("users", class="AcmeBlogBundle:User", options={
* "repository_method" = "findUsersByIds"
* })
you just need make sure that repository method can handle comma (,) separated values