How to refresh an entity load with a ServiceEntityRepository - doctrine

I have load an entity using a Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository (new recommandation in SF4) and I want to refresh this entity.
But $entityManager->clear() don't do the job and
$entityManager->refresh($myentity)
told me
Doctrine\ORM\ORMInvalidArgumentException: Entity App\Entity\MyEntity#0000000068ed8bf0000000007dca9dd1 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist
Here is my repo :
<?php
namespace App\Core\Repository;
use App\Core\Entity\Question;
use App\Core\Entity\QuestionnaireResponse;
use App\Core\Entity\Reponse;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* #method Reponse|null find($id, $lockMode = null, $lockVersion = null)
* #method Reponse|null findOneBy(array $criteria, array $orderBy = null)
* #method Reponse[] findAll()
* #method Reponse[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ReponseRepository extends ServiceEntityRepository
{
/**
* ReponseRepository constructor.
* #param RegistryInterface $registry
*/
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Reponse::class);
}
}
Here is my testSetup :
<?php
/*
* Created by Aurelien Jolivard on 24/01/2019.
*/
namespace App\Core\Service;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
class TestSetUp extends WebTestCase
{
/** #var EntityManager $em */
protected $em;
protected $application;
public function beginningTest(String $fixtures){
static::$kernel = static::createKernel(array(
'environment' => 'test',
'debug' => 'true'
));
static::$kernel->boot();
$application = new Application(static::$kernel);
// get the Entity Manager
/** #var EntityManager $em */
$em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
/** #var ContainerAwareEventManager $evm */
$evm = $em->getEventManager();
// drop the database
$command = new DropDatabaseDoctrineCommand(static::$kernel->getContainer()->get('doctrine'));
$application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:database:drop',
'--force' => true,
'--env' => 'test'
));
$command->run($input, new NullOutput());
// we have to close the connection after dropping the database so we don't get "No database selected" error
$connection = $application->getKernel()->getContainer()->get('doctrine')->getConnection();
if ($connection->isConnected()) {
$connection->close();
}
// create the database
$command = new CreateDatabaseDoctrineCommand(static::$kernel->getContainer()->get('doctrine'));
$application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:database:create',
'--env' => 'test'
));
$command->run($input, new NullOutput());
// create schema
$command = new CreateSchemaDoctrineCommand();
$application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:schema:create',
'--env' => 'test'
));
$command->run($input, new NullOutput());
// load fixtures
$client = static::createClient();
$loader = new ContainerAwareLoader($client->getContainer());
$loader->loadFromFile(static::$kernel->getProjectDir().$fixtures);
$purger = new ORMPurger($em);
$executor = new ORMExecutor($em, $purger);
$executor->execute($loader->getFixtures());
$this->em = $em;
$this->application = $application;
return ;
}
}
And here is my test where i try to clear data.
<?php
namespace App\Core\Tests\Form\FunctionalTest;
use App\Core\Entity\Questionnaire;
use App\Core\Entity\QuestionnaireResponse;
use App\Core\Entity\Reponse;
use App\Core\Service\TestSetUp;
use Doctrine\ORM\EntityManager;
class QuestionTypeBooleanTest extends TestSetUp
{
public function setUp()
{
$array = $this->beginningTest('/src/Core/DataFixtures/AppFixturesQuestionTypeBoolean.php');
//$this->em = $array[0];
//$this->application = $array[1];
}
/**
* #group question
* #group test_BrouillonPuisSoumission
*/
public function test_BrouillonPuisSoumission()
{
// vérification de l'état de la base de données avant
$client = static::createClient(
array('environment' => 'test') ,
);
$questionnaire = $this->em->getRepository(Questionnaire::class)->findSystemAndValue('system', '123');
$crawler = $client->request(
'GET',
'/questionnaire/'.$questionnaire->getId().'/display/1'
);
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$form = $crawler->selectButton('appbundle_questionnaire_display[draft]')->form();
$form['appbundle_questionnaire_display[1]'] = 0;
$form['appbundle_questionnaire_display[2]'] = 1;
$crawler = $client->submit($form);
$response = $client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
// vérification de l'état de la base de données
$reponse1 = $this->em->getRepository(Reponse::class)->find(1);
$reponse2 = $this->em->getRepository(Reponse::class)->find(2);
$questionnaireResponse = $this->em->getRepository(QuestionnaireResponse::class)->find(1);
$this->assertEquals('0', $reponse1->getValeur(), 'cas1.1');
$this->assertEquals('1', $reponse2->getValeur(), 'cas1.2');
$this->assertEquals(2, $questionnaireResponse->getVersionId());
$crawler = $client->request(
'GET',
'/soumission/1/edit'
);
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(0, $crawler->filter('form[name="appbundle_questionnaire_display"]')->form()->getValues()['appbundle_questionnaire_display[1]'], 'cas2.1');
$this->assertEquals(1, $crawler->filter('form[name="appbundle_questionnaire_display"]')->form()->getValues()['appbundle_questionnaire_display[2]'], 'cas2.2');
$form = $crawler->selectButton('appbundle_questionnaire_display[submit]')->form();
$form['appbundle_questionnaire_display[1]'] = 1;
$form['appbundle_questionnaire_display[2]'] = 0;
$crawler = $client->submit($form);
$response = $client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
$this->em->refresh($reponse1);
$this->em->refresh($reponse2);
$this->em->refresh($questionnaireResponse);
//$this->em->clear();
$this->assertEquals('1', $this->em->getRepository(Reponse::class)->find(1)->getValeur(), 'cas3.1');
$this->assertEquals('0', $this->em->getRepository(Reponse::class)->find(2)->getValeur(), 'cas3.2');
$this->assertEquals(3, $this->em->getRepository(QuestionnaireResponse::class)->find(1)->getVersionId());
}
}
How can I do that ?

Doctrine/ORM/EntityManager::clear() method
Clears the EntityManager. All entities that are currently managed by this EntityManager become detached.
Doctrine/ORM/EntityManager::refresh() method
Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been persisted.
To refresh the entity, just do:
$entityManager->refresh($myentity)
without clear() before it, remove it.

I have found a solution.
The entityManager i get in the TestSetUp is not the one used by the ServiceEntityRepository. This is due to the construct of the ServiceEntityRepository.
Si to clear the good entityManager, i have to call clear on the Repository and not on my EntityManger :
public function test_BrouillonPuisSoumission()
{
// ...
// vérification de l'état de la base de données
$reponseRepository = $this->em->getRepository(Reponse::class);
$questionnaireResponseRepository = $this->em->getRepository(QuestionnaireResponse::class);
$this->em->getRepository(QuestionnaireResponse::class)->find(1)->getVersionId());
$this->assertEquals('0', $reponseRepository->find(1)->getValeur(), 'cas1.1');
$this->assertEquals('1', $reponseRepository->find(2)->getValeur(), 'cas1.2');
$this->assertEquals(2, $questionnaireResponseRepository->find(1)->getVersionId());
// ...
$reponseRepository->clear();
$questionnaireResponseRepository->clear();
$this->assertEquals('1', $reponseRepository->find(1)->getValeur(), 'cas3.1');
$this->assertEquals('0', $reponseRepository->find(2)->getValeur(), 'cas3.2');
$this->assertEquals(3, $questionnaireResponseRepository->find(1)->getVersionId());
}

Related

Expired token when valid a form Laravel

I'm stuck for a while on this subject, I've searched the internet but unfortunately I can't find anything.
I have a controller that sends a recipe to my database but when I click on the previous icon of Google, it re-displays me the data entered previously are again displayed. Except that I have implemented csrf and from what I understood it allowed not to return to the form that has already been sent.
No sensitive information gets through but that's mostly because I don't like the user to be able to review his previously sent data...
Here is my controller
function index(){
return view('recette');
}
function saveRecette(Request $req){
$req->validate(
[
'nomRecette'=>'required',
'nbrPersonnes'=>'required|regex:/^[0-9]+$/',
'facilite'=>'required',
'detail'=>'required',
'ingredientPrincipal' => 'required',
],
[
'required'=> 'Ce champ est obligatoire',
'nbrPersonnes.regex' => 'Le champ doit contenir que des nombres'
]
);
$idRecette = Str::uuid();
$recette = new Recette;
$recette->id = $idRecette;
$recette->nomRecette = $req->nomRecette;
$recette->nbrPersonnes = $req->nbrPersonnes;
$recette->facilite = $req->facilite;
$recette->detail = $req->detail;
$recette->created_at = now();
$recette->updated_at = now();
$recetteSave =$recette->save();
if($recetteSave){
$listIngredient = $this->getIngredient($req->all());
foreach($listIngredient as $ingredient){
$ingred = new Ingredient;
$ingred->id = Str::uuid();
$ingred->ingredient = $ingredient;
$ingred->idRecette = $idRecette;
if($ingred->save()){
}
}
return redirect('');
}
return redirect()->back()->withInput()->with('bug', "Un problème est survenue. Veuillez réessayer plus tard");
}
function getIngredient($list){
$listIngredient = [];
$i=0;
foreach($list as $ingredient => $de){
if(strpos($ingredient, 'ingredient') !== false){
$listIngredient[$i] = $de;
$i++;
}
}
return $listIngredient;
}
Here is my Model
use HasFactory, Notifiable;
protected $table='creationRecette';
public $incrementing = false;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id',
'nomRecette',
'nbrRecettes',
'facilite',
'detail'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
Thanks a lot :)

Issue in Create Order Pragmatically with multiple products

I am trying to create order with multiple products using below code. code work fine, but one issue is occurring. I don't know why that adding more than one product create an order with just one product and all quantity summed to this.
<?php
namespace Magecomp\Cenpos\Controller\Index;
use Magento\Framework\App\Action;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Controller\ResultFactory;
class Display extends \Magento\Framework\App\Action\Action
{
protected $context;
protected $directory_list;
protected $cartRepositoryInterface;
protected $cartManagementInterface;
protected $_orderRepositoryInterface ;
/**
* #var \Magento\Sales\Model\Order\Email\Sender\OrderSender
*/
protected $orderSender;
/**
* #var \Magento\Checkout\Model\Session $checkoutSession
*/
protected $checkoutSession;
protected $_messageManager;
protected $_encryptor;
protected $_scopeConfig;
protected $logger;
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Framework\App\Filesystem\DirectoryList $directory_list,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Product $product,
\Magento\Framework\Data\Form\FormKey $formkey,
\Magento\Quote\Model\QuoteFactory $quote,
\Magento\Quote\Model\QuoteManagement $quoteManagement,
\Magento\Customer\Model\CustomerFactory $customerFactory,
\Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
\Magento\Sales\Model\Service\OrderService $orderService,
\Magento\Customer\Model\Session $currentCustomer,
\Magento\Checkout\Model\Cart $cart,
\Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface,
\Magento\Quote\Api\CartManagementInterface $cartManagementInterface,
\Magento\Checkout\Model\Session $checkoutSession,
\Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender,
\Magento\Framework\Encryption\EncryptorInterface $encryptor,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
//\Magento\Sales\Api\OrderRepositoryInterface $orderRepositoryInterface
) {
$this->directory_list = $directory_list;
$this->_storeManager = $storeManager;
$this->_product = $product;
$this->_formkey = $formkey;
$this->quote = $quote;
$this->quoteManagement = $quoteManagement;
$this->customerFactory = $customerFactory;
$this->customerRepository = $customerRepository;
$this->orderService = $orderService;
$this->_currentCustomer = $currentCustomer;
$this->_cart = $cart;
$this->cartRepositoryInterface = $cartRepositoryInterface;
$this->cartManagementInterface = $cartManagementInterface;
$this->checkoutSession = $checkoutSession;
$this->orderSender = $orderSender;
$this->_encryptor = $encryptor;
$this->_scopeConfig = $scopeConfig;
//$this->_orderRepositoryInterface = $orderRepositoryInterface;
$this->_messageManager = $context->getMessageManager();
parent::__construct($context);
}
public function saveShipping() {
if(isset($_POST['carrier_code']))
{
$_SESSION['carrier_code'] = $_POST['carrier_code'];
}
return true;
}
public function execute()
{
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$cart = $objectManager->get('\Magento\Checkout\Model\Cart');
$shippingAddress = $cart->getQuote()->getShippingAddress();
$shippingAddressData = $shippingAddress->getData();
$Response = $_GET;
if($Response['message'] == "Approved" && $Response['result'] == "0") {
$store=$this->_storeManager->getStore();
$websiteId = $this->_storeManager->getStore()->getWebsiteId();
$customer=$this->customerFactory->create();
$customer->setWebsiteId($websiteId);
$customer->loadByEmail($shippingAddressData['email']);// load customet by email address
if(!$customer->getEntityId()){
//If not avilable then create this customer
$customer->setWebsiteId($websiteId)
->setStore($store)
->setFirstname($shippingAddressData['firstname'])
->setLastname($shippingAddressData['lastname'])
->setEmail($shippingAddressData['email'])
->setPassword($shippingAddressData['email']);
$customer->save();
$customer= $this->customerRepository->getById($customer->getEntityId());
}
//init the quote
$cart_id = $this->cartManagementInterface->createEmptyCart();
$cart = $this->cartRepositoryInterface->get($cart_id);
$cart->setStore($store);
// if you have already had the buyer id, you can load customer directly
$customer= $this->customerRepository->getById($customer->getEntityId());
$cart->setCurrency();
$cart->assignCustomer($customer); //Assign quote to customer
$productInfo = $this->_cart->getQuote()->getAllItems();
//add items in quote
foreach($productInfo as $item){
$product=$this->_product->load($item->getProductId());
$product->setPrice($item->getPrice());
$cart->addProduct(
$product,
intval($item->getQty())
);
}
$addressData = array(
'firstname' => $shippingAddressData['firstname'],
'lastname' => $shippingAddressData['lastname'],
'street' => $shippingAddressData['street'],
'city' => $shippingAddressData['city'],
'postcode' => $shippingAddressData['postcode'],
'telephone' => $shippingAddressData['telephone'],
'country_id' => $shippingAddressData['country_id'],
'region_id' => $shippingAddressData['region_id'],
'region' => $shippingAddressData['region'],
);
//set shipping and billing address
$quote = $this->quote->create();
$cart->getBillingAddress()->addData($addressData);
$cart->getShippingAddress()->addData($addressData);
if(isset($_SESSION['carrier_code'])) {
$shipping_method = $_SESSION['carrier_code'];
} else {
$session = $this->_objectManager->get('Magento\Checkout\Model\Session');
$shipping_method = $session->getQuote()->getShippingAddress()->getShippingMethod();
}
$shippingAddress = $cart->getShippingAddress();
$shippingAddress->setCollectShippingRates(true)
->collectShippingRates()
->setShippingMethod($shipping_method);
unset($_SESSION['carrier_code']);
$cart->setPaymentMethod('cenpos'); //payment method
//#todo insert a variable to affect the invetory
$cart->setInventoryProcessed(false);
$card_type_code = "VI";
$cart->getPayment()->importData(
[
'method' => 'cenpos',
'cc_type' => $card_type_code,
'cc_number' => '4893772408728522',
'cc_cid' => '341',
'cc_exp_month' => '02',
'cc_exp_year' => '2022'
]
);
// Collect total and save
$cart->collectTotals();
// Submit the quote and create the order
$cart->save();
$cart = $this->cartRepositoryInterface->get($cart->getId());
$order_id = $this->cartManagementInterface->placeOrder($cart->getId());
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$orderRepository = $objectManager->create('Magento\Sales\Model\Order')->load($order_id);
$orderRepository->save();
$orderRepository->setEmailSent(true);
$this->checkoutSession->setForceOrderMailSentOnSuccess(true);
$this->orderSender->send($orderRepository, true);
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$resultRedirect->setUrl('http://m2gymtest.tpesonline.com/checkout/onepage/success');
return $resultRedirect;
}
}
}
Is there any problem in script? Or can it be server issue as the issue starts occurring after server changes.It was working properly before some days.
Use \Magento\Catalog\Model\ProductFactory $product instead of \Magento\Catalog\Model\Product $product in __construct() argument.
And Use
$product = $this->_product->create()->setStoreId($storeId)->load($item->getId());
to load the product instead of
$product=$this->_product->load($item->getProductId());
Hope this will help .

Custom filter API Platform not working

I am trying to implement a custom or-filter in API Platform. But for some reason it is not loading. Find below my configuration.
This is my filter:
<?php
namespace AppBundle\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Common\Annotations\AnnotationReader;
final class SearchFilter extends AbstractFilter
{
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
{
if ($property === 'search') {
$this->logger->info('Search for: ' . $value);
} else {
return;
}
$reader = new AnnotationReader();
$annotation = $reader->getClassAnnotation(new \ReflectionClass(new $resourceClass), \AppBundle\Filter\SearchAnnotation::class);
if (!$annotation) {
throw new \HttpInvalidParamException('No Search implemented.');
}
$parameterName = $queryNameGenerator->generateParameterName($property);
$search = [];
$mappedJoins = [];
foreach ($annotation->fields as $field)
{
$joins = explode(".", $field);
for ($lastAlias = 'o', $i = 0, $num = count($joins); $i < $num; $i++) {
$currentAlias = $joins[$i];
if ($i === $num - 1) {
$search[] = "LOWER({$lastAlias}.{$currentAlias}) LIKE LOWER(:{$parameterName})";
} else {
$join = "{$lastAlias}.{$currentAlias}";
if (false === array_search($join, $mappedJoins)) {
$queryBuilder->leftJoin($join, $currentAlias);
$mappedJoins[] = $join;
}
}
$lastAlias = $currentAlias;
}
}
$queryBuilder->andWhere(implode(' OR ', $search));
$queryBuilder->setParameter($parameterName, '%' . $value . '%');
}
/**
* #param string $resourceClass
* #return array
*/
public function getDescription(string $resourceClass): array
{
$reader = new AnnotationReader();
$annotation = $reader->getClassAnnotation(new \ReflectionClass(new $resourceClass), \AppBundle\Filter\SearchAnnotation::class);
$description['search'] = [
'property' => 'search',
'type' => 'string',
'required' => false,
'swagger' => ['description' => 'Filter on ' . implode(', ', $annotation->fields)],
];
return $description;
}
}
In api_filters.yml:
driver.custom_search_filter:
class: 'AppBundle\Filter\SearchFilter'
autowire: true
tags: [ { name: 'api_platform.filter' } ]
In my annotation file:
<?php
namespace AppBundle\Filter;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
use Doctrine\Common\Annotations\AnnotationException;
/**
* #Annotation
* #Target("CLASS")
*/
final class SearchAnnotation
{
public $fields = [];
/**
* Constructor.
*
* #param array $data Key-value for properties to be defined in this class.
* #throws AnnotationException
*/
public function __construct(array $data)
{
if (!isset($data['value']) || !is_array($data['value'])) {
throw new AnnotationException('Options must be a array of strings.');
}
foreach ($data['value'] as $key => $value) {
if (is_string($value)) {
$this->fields[] = $value;
} else {
throw new AnnotationException('Options must be a array of strings.');
}
}
}
}
And finally in my entity:
/**
* A driver that bring meals from hub to customer.
*
*
* #ApiResource(
* attributes={
* "filters"={"driver.search_filter","driver.custom_search_filter"},
* "denormalization_context"={"groups"={"post_driver"}}
* }
* )
* #Searchable({"firstName"})
*
* #ORM\Entity
* #ORM\Table(name="vendor_driver")
*/
class Driver
{
It is exactly as according to the issue that was reported here:
https://github.com/api-platform/core/issues/398
I am not getting any errors, but the filter is simply not working. I am seeing it in Swagger. But when I enter a value in Swagger, the db returns all entities. Its never reaching the filterProperty method.
Does anyone have an idea?
I've just managed to get this working by removing the autowiring, e.g.
my.custom_search_filter:
class: AppBundle\Filter\CustomSearchFilter
arguments:
- '#doctrine'
- '#request_stack'
- '#logger'
tags: [ { name: 'api_platform.filter', id: 'search' } ]
Hope that helps.
Did you import your custom filter in the Driver entity? In Regexp example of custom filter and here they are explicitly import their custom filters like:
use AppBundle\Filter\SearchAnnotation as Searchable;
/**
* #Searchable({"name", "description", "whatever"})
*/
class Product
{

Can't get POST data with AJAX

I would like to show 4 objects in a page, with a "load more" button which show 4 more object each time we click.
I try to adapt a PHP script which works(tested) to Symfony.
The problem is that I can't get the POST data (a page number) in my Symfony function, even if I can see it in the chrome developer toolbar...
My controller:
<?php
/**
* Content controller.
*
* #Route("content")
*/
class ContentController extends Controller
{
/**
* Lists all software.
*
* #Route("/software", name="content_software")
* #Method({"POST", "GET"})
*/
public function softwareAction(Request $request)
{
if ($request->request->get('page')){
$page_number = filter_var($_POST["page"], FILTER_SANITIZE_NUMBER_INT,
FILTER_FLAG_STRIP_HIGH);
$item_per_page = 4;
$position = (($page_number-1) * $item_per_page);
$contents = $this->getRepo()->findBy(array(),null,$item_per_page,$position);
}
else
{
$contents = "didn't work";
}
return $this->render('content/index.html.twig', array(
'contents' => $contents
));
}
}
index.html.twig :
{% extends 'loicCoreBundle::Default/layout.html.twig' %}
{% block body %}
{{ dump(contents) }}
<script type="text/javascript">
var track_page = 1; //track user click as page number, right now page number is 1
load_contents(track_page); //load content
$("#load_more_button").click(function (e) { //user clicks on button
track_page++; //page number increment everytime user clicks load button
load_contents(track_page); //load content
});
//Ajax load function
function load_contents(track_page){
$.post( "{{ path('content_software') }}", {'page': track_page}, function(data){
if(data.trim().length == 0){
//display text and disable load button if nothing to load
$("#load_more_button").text("No more records!").prop("disabled", true);
}
});
}
</script>
{% endblock %}
I'm not sure where your errors are coming from, I've done a similar test which just works (no problems with $request->request->get()).
You are however loading a full template (index.html) for each sub request as well. Usually you want to separate calls like this into api like methods, however for simple things its a bit silly.
Here is an expanded/ updated version of what I tested, I avoided post data all together and just used the method as switch. This worked fine so try and figure out where you went wrong using this as reflection (or whatever you like to do with it). Note this uses a simple PHP range array for test data, not entities but it should remain the same principle.
Controller
/**
* #Route(
* "software/{page}/{limit}",
* name="content_software",
* requirements = {
* "page": "[1-9]\d*",
* "limit": "[1-9]\d*"
* }
* )
*/
public function softwareAction(Request $request, $page = 1, $limit = 4) {
if ($request->isMethod('POST')) {
// replace these two lines with database logic (SELECT & LIMIT)
$start = ($page - 1) * $limit;
$items = array_slice(range(1, 10), $start, $limit); // Just using a simple item array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as test data
$content = '';
foreach ($items as $item) {
$content .= $this->get('twig')->render('someItemTemplate.html.twig', ['item' => $item]);
}
return new Response($content);
} else {
// alternatively you can send out the default items here for the first get request
// and use the same item template above with {% embed %} to render them in this template
return $this->render('someTemplate.html.twig');
}
}
someTemplate.html.twig
<html>
<head>
<title>Cake or death?</title>
</head>
<body>
<ul id="put-the-things-here"></ul>
<button id="next_please">Ehh cake please!</button>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
var pageTracker = 1,
$next = $("#next_please"),
$target = $('#put-the-things-here');
load_contents(pageTracker);
$next.click(function (e) {
load_contents(++pageTracker);
});
function load_contents(page) {
// using post just for the request method (page is in the url)
$.post("{{ path('content_software') }}/" + page,
function (data) {
if (data) {
$target.append(data);
} else {
$target.append('<li>We are out of cake.</li>');
$next.attr('disabled', true);
}
}
);
}
</script>
</body>
</html>
someItemTemplate.twig
<li>Cake: {{ item }}</li>
try this:
$request->request->get('page')
Instead of this:
$request->request->has('page')
If it doesn't work try to:
var_dump($request->request->all());
And check the variables
I think too the problem comes from routes.
Error message when I use only "POST" method :
"No route found for "GET /content/software": Method Not Allowed (Allow: POST, DELETE)".
Because when I arrive on my page by clicking a link , I arrive with a GET method.
However, when I click on my "load more" button the AJAX works and I can see in my symfony debugbar that I get POST variable:
The problem is when I first arrive on my page I think.
#Jenne van der Meer: Thank you but I would like not to use GET parameters.
I add my full controller code, in case of:
/**
* Content controller.
*
* #Route("content")
*/
class ContentController extends Controller
{
public function getRepo(){
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('loicContentBundle:Content');
return $repo;
}
// private $theRepo = getDoctrine()->getManager()->getRepository('loicContentBundle:Content');
/**
* Lists all content entities.
*
* #Route("/", name="content_index")
* #Method("GET") */
public function indexAction(Request $request)
{
$contents = $this->getRepo()->findAll();
$this->denyAccessUnlessGranted('ROLE_USER', null, 'Unable to access this page!');
$formFilter = $this->createFormBuilder()
->add('_', EntityType::class,array(
'class' => 'loicFilterBundle:Filter',
'multiple' => true,
'expanded' => true,
'choice_label' => function($value) {
return ($value->getName());
},
))
->add('Appliquer filtres', SubmitType::class)
->getForm();
$formFilter->handleRequest($request);
$data = '';
if ($formFilter->isSubmitted() && $formFilter->isValid()) {
$data = $formFilter->getData();
$data = $data['_']->toArray();
$contents = $this->getRepo()->findAll();
}
else
{$contents = $this->getRepo()->findAll();
}
return $this->render('content/index.html.twig', array(
'contents' => $contents,'formFilter' => $formFilter->createView(),'request' => $request
));
}
public function contentAction($categoryId)
{
$contents= $this->getRepo()->findBy(
array('contentCategorycontentCategory' => $categoryId),
null,
4
);
return $contents;
}
/**
* Lists all software.
*
* #Route("/software", name="content_software")
*/
public function softwareAction(Request $request)
{
var_dump($request->request->all());
$bla = $request->request->get('page');
if ($request->request->get('page')){
$page_number = filter_var($_POST["page"], FILTER_SANITIZE_NUMBER_INT,
FILTER_FLAG_STRIP_HIGH);
$item_per_page = 4;
$position = (($page_number-1) * $item_per_page);
$contents = $this->getRepo()->findBy(array(),null,$item_per_page,$position);
}
else
{
$contents = "didn't work";
}
return $this->render('content/index.html.twig', array(
'contents' => $contents,'bla' => $bla
));
}
/**
* Lists all videos.
*
* #Route("/videos", name="content_videos")
* #Method("GET")
*/
public function videoAction()
{
$contents = $this->contentAction(5);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all testimonies.
*
* #Route("/testimonies", name="content_testimonies")
* #Method("GET")
*/
public function testimoniesAction()
{
$contents = $this->contentAction(8);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all whiteBooks.
*
* #Route("/whiteBooks", name="content_whiteBooks")
* #Method("GET")
*/
public function whiteBooksAction()
{
$contents = $this->contentAction(3);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all actuality.
*
* #Route("/actuality", name="content_actuality")
* #Method("GET")
*/
public function actualityAction()
{
$contents = $this->contentAction(6);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all webinar.
*
* #Route("/webinar", name="content_webinar")
* #Method("GET")
*/
public function webinarAction()
{
$contents = $this->contentAction(4);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Lists all blog posts.
*
* #Route("/blog", name="content_blog")
* #Method("GET")
*/
public function blogAction()
{
$contents = $this->contentAction(7);
return $this->render('content/index.html.twig', array(
'contents' => $contents,
));
}
/**
* Creates a new content entity.
*
* #Route("/new", name="content_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$content = new Content();
$form = $this->createForm('loic\ContentBundle\Form\ContentType', $content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($content);
$em->flush($content);
return $this->redirectToRoute('content_show', array('id' => $content->getIdcontent()));
}
return $this->render('content/new.html.twig', array(
'content' => $content,
'form' => $form->createView(),
));
}
/**
* Displays a form to edit an existing content entity.
*
* #Route("/{id}/edit", name="content_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Content $content)
{
$deleteForm = $this->createDeleteForm($content);
$editForm = $this->createForm('loic\ContentBundle\Form\ContentType', $content);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('content_edit', array('id' => $content->getIdcontent()));
}
return $this->render('content/edit.html.twig', array(
'content' => $content,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
/**
* Deletes a content entity.
*
* #Route("/{id}", name="content_delete")
* #Method("DELETE")
*/
public function deleteAction(Request $request, Content $content)
{
$form = $this->createDeleteForm($content);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->remove($content);
$em->flush();
}
return $this->redirectToRoute('content_index');
}
/**
* Creates a form to delete a content entity.
*
* #param Content $content The content entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm(Content $content)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('content_delete', array('id' => $content->getIdcontent())))
->setMethod('DELETE')
->getForm()
;
}
}
My Bundle/Resources/config/routing.yml file is empty.

How to add a custom field to the session table

I'm currently using Symfony 2.1.8 and the built-in PdoSessionHandler.
I want to add a user_id field in the session table to identify to which (logged-in) user the session belongs. The idea is that I can force the user to log back in destroying his session. In my case it will happen if the privileges of the user are updated.
I had a look to the build-in PdoSessionHandler that you can't extends because of those silly private variables.
So I've tried created a new one (copy/paste) and add my column user_id.
Now this column can be null if the user is not logged in (anonymous users).
So I want to write this user_id in the write method of the handler. The user is already stored in the $data so I was thinking that I could check if this user exists, grab its id and add it in the insert / update query.
The problem is that $data is encoded - I guess by session_encode() - so I'm not sure anymore this is the best place ever to handle my new field, but at the same time I can't see anywhere else I could do it as I need to update this MySQL query to insert the value of the new field.
So my question is: where is the best place to handle this additional field? And how to set this user_id value?
On another note, somehing really annoying is that Symfony is creating a new cookie each time I'm logging in or out. So the database ends up with lots of records for nothing (it's always the same user). Why Symfony is not using the same cookie value all the time?
You could extend PdoSessionHandler (solution for >=Symfony 2.1):
namespace Acme\DemoBundle\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\Security\Core\SecurityContext;
class UserIdPdoSessionHandler extends PdoSessionHandler
{
/**
* #var \PDO PDO instance.
*/
private $pdo;
/**
* #var array Database options.
*/
private $dbOptions;
/**
* #var SecurityContext
*/
private $context;
public function __construct(\PDO $pdo, array $dbOptions = array(), SecurityContext $context)
{
$this->pdo = $pdo;
$this->dbOptions = array_merge(
array('db_user_id_col' => 'user_id'),
$dbOptions
);
$this->context = $context;
parent::__construct($pdo, $dbOptions);
}
public function read($id)
{
// get table/columns
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
try {
$sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->execute();
// it is recommended to use fetchAll so that PDO can close the DB cursor
// we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
$sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
if (count($sessionRows) == 1) {
return base64_decode($sessionRows[0][0]);
}
// session does not exist, create it
$this->createNewSession($id);
return '';
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
}
}
/**
* {#inheritDoc}
*/
public function write($id, $data)
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$dbUserIdCol = $this->dbOptions['db_user_id_col'];
//session data can contain non binary safe characters so we need to encode it
$encoded = base64_encode($data);
$userId = $this->context->isGranted('IS_AUTHENTICATED_REMEMBERED') ?
$this->context->getToken()->getUser()->getId() :
null
;
try {
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ('mysql' === $driver) {
// MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged
// it could result in calling createNewSession() whereas the session already exists in
// the DB which would fail as the id is unique
$stmt = $this->pdo->prepare(
"INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id) " .
"ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = VALUES($dbTimeCol)"
);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
} elseif ('oci' === $driver) {
$stmt = $this->pdo->prepare("MERGE INTO $dbTable USING DUAL ON($dbIdCol = :id) ".
"WHEN NOT MATCHED THEN INSERT ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, sysdate, :user_id) " .
"WHEN MATCHED THEN UPDATE SET $dbDataCol = :data WHERE $dbIdCol = :id");
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
} else {
$stmt = $this->pdo->prepare("UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id");
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
if (!$stmt->rowCount()) {
// No session exists in the database to update. This happens when we have called
// session_regenerate_id()
$this->createNewSession($id, $data);
}
}
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
private function createNewSession($id, $data = '')
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$dbUserIdCol = $this->dbOptions['db_user_id_col'];
$userId = $this->context->isGranted('IS_AUTHENTICATED_REMEMBERED') ?
$this->context->getToken()->getUser()->getId() :
null
;
$sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id)";
//session data can contain non binary safe characters so we need to encode it
$encoded = base64_encode($data);
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
return true;
}
}
And configure session to use it:
# config.yml
framework:
session:
# ...
handler_id: session.storage.custom
parameters:
pdo.db_options:
db_table: session
db_id_col: session_id
db_data_col: session_value
db_time_col: session_time
db_user_id_col: session_user_id
services:
pdo:
class: PDO
arguments:
dsn: "mysql:host=%database_host%;dbname=%database_name%"
user: "%database_user%"
password: "%database_password%"
session.storage.custom:
class: Acme\DemoBundle\HttpFoundation\Session\Storage\Handler\UserIdPdoSessionHandler
arguments: [ #pdo, "%pdo.db_options%", #security.context ]
I'm not sure modifying session is a good idea, you can store session id(s) in user entity instead and delete those when needed. This way you can for example ensure only user can be logged in with only one session at a time.
The easiest way to accomplish it would be to use login listener.
Add sessionId field to user entity (or document or whatever persistance you use):
// Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User {
// ...
/**
* #ORM\Column(name="session_id", type="string")
*/
private $sessionId;
public function getSessionId() {
return $this->sessionId;
}
public function setSessionId($sessionId = null) {
$this->sessionId = $sessionId;
return $this;
}
}
And add a listener:
namespace Dbla\UserBundle\Listener;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class LoginListener
{
protected $doctrine;
protected $session;
public function __construct(Session $session, Registry $doctrine)
{
$this->doctrine = $doctrine;
$this->session = $session;
}
public function onLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($user) {
$user->setSessionId($this->session->getId());
$em = $this->doctrine->getEntityManager();
$em->persist($user);
$em->flush();
}
}
}
And add it as a service:
services:
acme_user.listsner.login:
class: Acme\UserBundle\Listener\LoginListener
arguments: [ #session, #doctrine ]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: onLogin }
Then you can simply remove session for users:
$users = []; // ... get user list
$sessionIds = array_map(function($user) {
return $user->getId();
});
if (count(sessionIds) > 0) {
$sql = 'DELETE FROM session WHERE session_id IN (' . implode($sessionIds, ',') . ')';
$entityManager->getConnection()->exec($sql);
}
foreach ($users as $user) {
$user->setSessionId(null);
$entityManager->persist($user);
}
$entityManager->flush();

Resources