Doctrine Custom ID with auto increment - entity-framework

I want to define some id with prefix.
For example, for one order entity its : "OR17000001"
In this example, the prefix is "OR17"
So i have declare my id entities like this :
/**
* #var string
*
* #ORM\Column(name="id", type="string", length=8)
* #ORM\Id
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="My\Bundle\Generator\OrderCodeGenerator")
*/
private $id;
And my Generator is :
<?php
namespace My\Bundle\Generator;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\EntityManager;
use My\Bundle\Entity\Order;
class OrderCodeGenerator extends AbstractIdGenerator
{
/**
* Format :
* $prefix - string
* $year - take 2 first letters (17)
* $increment - Take the last code + 1
*
* #param EntityManager $em
* #param \Doctrine\ORM\Mapping\Entity $entity
* #return bool|string
*/
public function generate(EntityManager $em, $entity)
{
if ($entity instanceof Order) {
$now = new \DateTime();
$year = $now->format('y');
$prefix = 'OR';
$maxCode = $em->getRepository('MyRepo:Order')->findMaxCode($year, $prefix);
if ($maxCode) {
$increment = substr($maxCode[1], -4);
$increment = (int)$increment + 1;
} else
$increment = 0;
$code = $prefix . $year . sprintf('%04d', $increment);
return $code;
}
return false;
}
}
Without forget the method findMaxCode :
public function findMaxCode($year, $prefix)
{
$qb = $this->createQueryBuilder('entity');
$qb->where($qb->expr()->like('entity.id', ':code'))
->setParameter('code', '%' . $prefix . $year . '%');
$qb->select($qb->expr()->max('entity.id'));
return $qb->getQuery()->getOneOrNullResult();
}
That's work fine =)
My problem is when i try to add some entities in same time.
My case is :
Order entity with some items (its a form collection)
Item entity
So i need to custom id of Items Order with this strategy. And the problem is for found the max code. I have this error :
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicata du
champ 'IT170000001' pour la clef 'PRIMARY'
The generator can't found the max code for generate the second item, because there is no flush.
How can i save the increment value between 2 id generation before the flush ??
Solution :
I keep numeric id for my Item. Its useful for my Order entity, its more readable than an simple int. But i don't care for Item.
Thx to Honza Rydrych

Querying DB for last inserted ID and then inserting "+one" isn't reliable solution.
My solution for this case would be let doctrine generate ID's by the standard way and add the prefix "OR/Year/" when you need to present the data.
(Optionaly you can write custom Twig extension for presenting the ID http://symfony.com/doc/current/templating/twig_extension.html)

Related

How do I get uid of a File Reference Object in TYPO3?

I am trying to get a file through this code $f = $resourceFactory->getFileObject($uid); but the problem is the uid is a protected field in the file reference object, as seen below so I am not able to get the uid, and getUid() obviously wont work either.
So how can I get the uid of the file reference (FAL)
/**
* A file reference object (File Abstraction Layer)
*
* #api experimental! This class is experimental and subject to change!
*/
class FileReference extends
\TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
{
/**
* Uid of the referenced sys_file. Needed for extbase to serialize the
* reference correctly.
*
* #var int
*/
protected $uidLocal;
/**
* #param \TYPO3\CMS\Core\Resource\ResourceInterface $originalResource
*/
public function setOriginalResource(\TYPO3\CMS\Core\Resource\ResourceInterface $originalResource)
{
$this->originalResource = $originalResource;
$this->uidLocal = (int)$originalResource->getOriginalFile()->getUid();
}
/**
* #return \TYPO3\CMS\Core\Resource\FileReference
*/
public function getOriginalResource()
{
if ($this->originalResource === null) {
$this->originalResource = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($this->getUid());
}
return $this->originalResource;
}
}
Given you have an instance of TYPO3\CMS\Extbase\Domain\Model\FileReference then you can use getOriginalResource() to get the wrapped TYPO3\CMS\Core\Resource\FileReference. If you need the referenced file, you can then use getOriginalFile(). Thus as a chained call:
$file = $fileReference->getOriginalResource()->getOriginalFile();
Notice that you don't have to use the ResourceFactory yourself in all of this, this is taken care of internally.
Work form me.
You can find or get file refernce uid using custom query.
In Controller :
$uid = $yourObject->getUid();
$fileReference = $this->yourRepository->getFileReferenceObject($uid);
In Repository
public function getFileRefernceHeaderLogo($uid){
$query = $this->createQuery();
$queryString = "SELECT *
FROM sys_file_reference
WHERE deleted = 0
AND hidden = 0
AND tablenames='your_table_name'
AND fieldname='your_field_name'
AND uid_foreign =".$uid;
$query->statement($queryString);
return $res = $query->execute(true);
}
In Controller
$fileRefUid = $fileReference[0]['uid'];
Here you can get uid of file reference table.It is long process.
You can also get sys_file table uid for getFileObject.like,
$sys_file_uid = $fileReference[0]['uid_local'];

Filtering for seleted categories and subcategories in extbase TYPO3 frontend plugin

I am using TYPO3 8.7.
I have got an extension which allows to list events. In the list plugin settings the redactor selects the categories. He wants that all events which have the selected category or a subcategory of it assigned are show in the frontend.
I have been reading the documentation on docs.typo3.org and have been looking at the class CategoryCollection. As far as I understand, this class helps me to get the keywords for a certain record, but does not help to select records by a keyword.
I would like to filter also for subcategories because it makes the handling of the extension much easier. Let's imagine the following event categories:
course of studies
master's degree
bachaelor's degree
training
internal training
external training
The backend editor wants to have the choice to display either e.g. internal trainings or all trainings, without activating the parent categories on every event explicitly.
What is the correct way to filter for categories and its subcategories in an extbase repository to display a list of records in the frontend?
Do I have to implement the logic manually to filter for categories and subcategories?
My solution looks like this:
<?php
namespace Snowflake\Events\Domain\Repository;
/***************************************************************
* Copyright notice
*
* (c) 2018 snowflake productions gmbh <support#snowflake.ch>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Repository;
class EventRepository extends Repository {
/**
* #param $categories
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
* #throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
*/
public function findByCategoryFilter($categories)
{
$categories = array_map('intval', explode(',', $categories));
$categories = $this->expandCategories($categories);
$uids = $this->getUidsByCategories($categories);
if (count($uids) == 0)
return array();
$query = $this->createQuery();
$query->matching(
$query->in('uid', $uids)
);
return $query->execute(true);
}
/**
* Return the categories and all subcategories (recursive)
*
* #param $categories
* #return array
*/
private function expandCategories($categories)
{
// get all categories from database
/** #var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder
->select('uid', 'parent')
->from('sys_category');
$allCategoriesFromDatabase = $queryBuilder->execute()->fetchAll();
// index the categories by parent
$categoriesByParent = array();
foreach($allCategoriesFromDatabase as $categoryFromDatabase) {
$categoriesByParent[(int)$categoryFromDatabase['parent']][] = (int)$categoryFromDatabase['uid'];
}
// expand the categories to all subcategories
$categoriesToExpand = $categories;
$expandedCategories = $categories;
while(count($categoriesToExpand) > 0) {
$currentSubCategories = array();
foreach($categoriesToExpand as $category) {
foreach ($categoriesByParent[$category] as $subCategory) {
$currentSubCategories[] = $subCategory;
}
}
$categoriesToExpand = array_diff($currentSubCategories, $expandedCategories);
$expandedCategories = array_unique(array_merge($expandedCategories, $currentSubCategories));
}
return $expandedCategories;
}
/**
* This is a workaround because
*
* $query = $this->createQuery();
* $query->matching(
* $query->contains('category', $categories)
* );
* return $query->execute(true);
*
* generate a useless SQL query (equals instead of in, see first WHERE condition in subquery)
*
* SELECT `tx_events_domain_model_event`.*
* FROM `tx_events_domain_model_event` `tx_events_domain_model_event`
* WHERE (`tx_events_domain_model_event`.`uid` IN
* (SELECT `uid_foreign`
* FROM `sys_category_record_mm`
* WHERE (`uid_local` = '1,3,5,4,6')
* AND ((`sys_category_record_mm`.`tablenames` = 'tx_events_domain_model_event')
* AND (`sys_category_record_mm`.`fieldname` = 'category'))))
* AND (`tx_events_domain_model_event`.`sys_language_uid` IN (0, -1))
* AND (`tx_events_domain_model_event`.`pid` = 161)
* AND ((`tx_events_domain_model_event`.`deleted` = 0)
* AND (`tx_events_domain_model_event`.`hidden` = 0)
* AND (`tx_events_domain_model_event`.`starttime` <= 1516715340)
* AND (
* (`tx_events_domain_model_event`.`endtime` = 0)
* OR (`tx_events_domain_model_event`.`endtime` > 1516715340)))
*
* #param $categories
* #return array
*/
private function getUidsByCategories($categories) {
$result = array();
/** #var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category_record_mm');
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder
->select('uid_foreign')
->from('sys_category_record_mm')
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in('uid_local', $categories),
$queryBuilder->expr()->eq('tablenames', '\'tx_events_domain_model_event\''),
$queryBuilder->expr()->eq('fieldname', '\'category\'')
)
);
$records = $queryBuilder->execute()->fetchAll();
foreach($records as $record) {
$result[] = $record['uid_foreign'];
}
return $result;
}
}

Complex for/if conditions for rendering newsItems in Fluid Template

I am working on a special template for the news extension tx_news in Typo3.
I am completely new to Typo3 and especially Fluid.
What I want is an output of exactly 4 news items, but each of these items must have an image.
What I need is programming logic, something like:
If the newsItem has an image, and less than 4 items have been rendered so far, then render. Else don't do anything.
I read this question and answer:
TYPO3 Fluid complex if conditions
so I suspect I need something like a viewhelper.
So far my templates has this code to output items:
<f:for each="{news}" as="newsItem" iteration="iterator">
<f:if condition="{newsItem.falMedia}">
<f:if condition="{iterator.cycle}<=4">
<f:render partial="List/TeaserItem" arguments="{newsItem: newsItem,settings:settings,iterator:iterator, contentObjectData:contentObjectData}" />
</f:if>
</f:if>
</f:for>
But this will of course stop after iterating through news 4 times. So if one entry without image didn't get rendered, I will only have three items output.
I'd need an if condition kind of like this:
if ({newsItem.falMedia} && {iterator.cycle}<=4){
render image }
else {iterator.cycle--}
but I can't figure out how to pass the iterator variable of my for-loop to the new viewhelper, and especially to pass it back to the for-loop.
In short words this kind of logic isn't possible in Fluid - reason is simple -it's template engine.
You need to create your own extension and create a ViewHelper in it, which will take the collection of News will check if it has required settings (falMedia existing in this case) and will return limited array which you can iterate. Indeed, reusing f:for will be fastest solution.
I'm afraid, that's only way.
Here's the sample (compare it to original f:for viewhelper):
<?php
namespace TYPO3\CMS\Fluid\ViewHelpers;
class ForNewsWithMediaViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* Iterates through elements of $each and renders child nodes
*
* #param array $each The array or \TYPO3\CMS\Extbase\Persistence\ObjectStorage to iterated over
* #param string $as The name of the iteration variable
* #param string $key The name of the variable to store the current array key
* #param boolean $reverse If enabled, the iterator will start with the last element and proceed reversely
* #param string $iteration The name of the variable to store iteration information (index, cycle, isFirst, isLast, isEven, isOdd)
* #param int $limit Limit of the news items to show
* #return string Rendered string
* #api
*/
public function render($each, $as, $key = '', $reverse = FALSE, $iteration = NULL, $limit = NULL) {
return self::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext, $limit);
}
/**
* #param array $arguments
* #param \Closure $renderChildrenClosure
* #param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
* #param int $limit Limit of the news items to show
* #return string
* #throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
*/
static public function renderStatic(array $arguments, \Closure $renderChildrenClosure, \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext, $limit = NULL) {
$templateVariableContainer = $renderingContext->getTemplateVariableContainer();
if ($arguments['each'] === NULL) {
return '';
}
if (is_object($arguments['each']) && !$arguments['each'] instanceof \Traversable) {
throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('ForViewHelper only supports arrays and objects implementing \Traversable interface', 1248728393);
}
if ($arguments['reverse'] === TRUE) {
// array_reverse only supports arrays
if (is_object($arguments['each'])) {
$arguments['each'] = iterator_to_array($arguments['each']);
}
$arguments['each'] = array_reverse($arguments['each']);
}
$iterationData = array(
'index' => 0,
'cycle' => 1,
'total' => count($arguments['each'])
);
$limitCycle = 1;
$output = '';
/**
* #type $singleElement Tx_News_Domain_Model_News
*/
foreach ($arguments['each'] as $keyValue => $singleElement) {
if (is_null($singleElement->getFalMedia())
|| !is_null($limit) && $limitCycle > $limit
) {
continue;
}
$limitCycle++;
$templateVariableContainer->add($arguments['as'], $singleElement);
if ($arguments['key'] !== '') {
$templateVariableContainer->add($arguments['key'], $keyValue);
}
if ($arguments['iteration'] !== NULL) {
$iterationData['isFirst'] = $iterationData['cycle'] === 1;
$iterationData['isLast'] = $iterationData['cycle'] === $iterationData['total'];
$iterationData['isEven'] = $iterationData['cycle'] % 2 === 0;
$iterationData['isOdd'] = !$iterationData['isEven'];
$templateVariableContainer->add($arguments['iteration'], $iterationData);
$iterationData['index']++;
$iterationData['cycle']++;
}
$output .= $renderChildrenClosure();
$templateVariableContainer->remove($arguments['as']);
if ($arguments['key'] !== '') {
$templateVariableContainer->remove($arguments['key']);
}
if ($arguments['iteration'] !== NULL) {
$templateVariableContainer->remove($arguments['iteration']);
}
}
return $output;
}
}
So you can use it in your view as:
<f:forNewsWithMedia each="{news}" as="newsItem" iteration="iterator" limit="4">
<f:render partial="List/TeaserItem" arguments="{newsItem: newsItem,settings:settings,iterator:iterator, contentObjectData:contentObjectData}" />
</f:forNewsWithMedia>

Prestashop display all products form category without pagination

I am building a Module for the category page in prestashop.
Basically in my module.php I have this code:
$category = new Category(Context::getContext()->shop->getCategory(),(int)Context::getContext()->language->id);
$nb = (int)(Configuration::get('MOD_NBR'));
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
$this->smarty->assign(array(
'myproducts' => $products,
'add_prod_display' => Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY'),
'homeSize' => Image::getSize(ImageType::getFormatedName('home')),
));
Then in mymodule.tpl I have this:
{foreach from=$products item=product name=myproducts}
+ other stuff
The problem is that I need to get all the products inside the category, but it is only displaying the products on the first page. I can't delete or modify pagination completely, because I need the other products on the category page to be paginated, but in my module I want to get all the products at once (after I will filter them to show only some of them).
As you may see I am kind of lost, but also so desperate, I will appreciate any guidance :)
thanks
In your code you have:
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
which corresponds to:
/**
* Return current category products
*
* #param integer $id_lang Language ID
* #param integer $p Page number
* #param integer $n Number of products per page
* #param boolean $get_total return the number of results instead of the results themself
* #param boolean $active return only active products
* #param boolean $random active a random filter for returned products
* #param int $random_number_products number of products to return if random is activated
* #param boolean $check_access set to false to return all products (even if customer hasn't access)
* #return mixed Products or number of products
*/
public function getProducts($id_lang, $p, $n, $order_by = null, $order_way = null, $get_total = false, $active = true, $random = false, $random_number_products = 1, $check_access = true, Context $context = null)
So you are asking for page 1 and $nb or 10 elements.
Try adding before that line $nb = 10000; to show up to 10k products (and feel free to increase it if your category has more than 10k products)
So it should be something like:
$category = new Category(Context::getContext()->shop->getCategory(),(int)Context::getContext()->language->id);
$nb = 10000;
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
$this->smarty->assign(array(
'myproducts' => $products,
'add_prod_display' => Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY'),
'homeSize' => Image::getSize(ImageType::getFormatedName('home')),
));
UPDATE: Reviewing your question I've found that in your template you are iterating $products variable, but assigning it as myproducts. I'm guessing smarty has the assigned variables $products with only the first page and $myproducts with the ones you have obtained.
Try updating your template to:
{foreach from=$myproducts item=product name=myproducts}

How do I get the "converted" action name for Zend Framework MVC?

I know update-profile-picture translates to the updateProfilePictureAction() function.
Where does the conversion of the action param take place?
I'd like to get updateProfilePicture as a value, I could write the function but it must already be in the library somewhere.
When using $this->_getParam('action') it returns update-profile-picture;
/**
* Auto call scripts
* #see Zend_Controller_Action::postDispatch()
*/
public function postDispatch(){
$action = $this->_getParam('action',false);
$method = $action.'Scripts';
if ($action && method_exists($this, $method))
$this->$method();
}
this works fine for indexAction - indexScripts but not for updateProfilePictureScripts (looking for update-profile-pictureScripts)
Get it with
$this->getFrontController()->getDispatcher()->formatActionName($this->_getParam('action',null));
It is happening in
/Zend/Controller/Dispatcher/Abstract.php
/**
* Formats a string into an action name. This is used to take a raw
* action name, such as one that would be stored inside a Zend_Controller_Request_Abstract
* object, and reformat into a proper method name that would be found
* inside a class extending Zend_Controller_Action.
*
* #param string $unformatted
* #return string
*/
public function formatActionName($unformatted)
{
$formatted = $this->_formatName($unformatted, true);
return strtolower(substr($formatted, 0, 1)) . substr($formatted, 1) . 'Action';
}
/**
* Formats a string from a URI into a PHP-friendly name.
*
* By default, replaces words separated by the word separator character(s)
* with camelCaps. If $isAction is false, it also preserves replaces words
* separated by the path separation character with an underscore, making
* the following word Title cased. All non-alphanumeric characters are
* removed.
*
* #param string $unformatted
* #param boolean $isAction Defaults to false
* #return string
*/
protected function _formatName($unformatted, $isAction = false)
{
// preserve directories
if (!$isAction) {
$segments = explode($this->getPathDelimiter(), $unformatted);
} else {
$segments = (array) $unformatted;
}
foreach ($segments as $key => $segment) {
$segment = str_replace($this->getWordDelimiter(), ' ', strtolower($segment));
$segment = preg_replace('/[^a-z0-9 ]/', '', $segment);
$segments[$key] = str_replace(' ', '', ucwords($segment));
}
return implode('_', $segments);
}
Have a look at Zend_Filter_Inflector : http://framework.zend.com/manual/en/zend.filter.inflector.html