Using Symfony 5.4.17. New to Symfony.
I have three similar Entity types and want to return an array called Food that I can loop over in a twig template like so:
{% for food in foodArr %}
<div class = 'border p-3 mb-3'>
View
<p>Food Name: {{food.Description}}</p>
<p>Food Id: {{food.id}}</p>
<p>Date: {{food.Date|date('m-d-Y')}}</p>
Delete?
</div>
{% endfor %}
Here are is the code in the controller. Note that I get each of the food type objects that are related to the current user, and just for now put them in an array.
$menustatFoodRepo = $this->em->getRepository(MenustatFood::class);
$usdaBrandedFoodRepo = $this->em->getRepository(UsdaBrandedFood::class);
$usdaNonBrandedFoodRepo = $this->em->getRepository(UsdaNonBrandedFood::class);
$menustatFoods= $menustatFoodRepo->findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$usdaBrandedFoods = $usdaBrandedFoodRepo -> findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$usdaNonBrandedFoods = $usdaNonBrandedFoodRepo -> findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$foodArr = array();
// add all foods to array
array_push($foodArr,$menustatFoods,$usdaBrandedFoods,$usdaNonBrandedFoods);
How can I store these three entity types sorted by Date into a renderable format? The idea is to have one loopable array that is already sorted by Date that contains any of the three types.
With the help of #Cerad, I made an answer:
Sorting function in the Controller:
function date_sort($objA,$objB){
if($objA->getDate() == $objB->getDate()) return 0;
return ($objA->getDate() < $objB->getDate()) ? -1:1;
}
Controller view:
$menustatFoodRepo = $this->em->getRepository(MenustatFood::class);
$usdaBrandedFoodRepo = $this->em->getRepository(UsdaBrandedFood::class);
$usdaNonBrandedFoodRepo = $this->em->getRepository(UsdaNonBrandedFood::class);
$menustatFoods= $menustatFoodRepo->findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$usdaBrandedFoods = $usdaBrandedFoodRepo -> findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$usdaNonBrandedFoods = $usdaNonBrandedFoodRepo -> findBy(
['User' => $this->getUser()],
['Date' => 'ASC']
);
$foodArr = array_merge($menustatFoods,$usdaBrandedFoods,$usdaNonBrandedFoods);
// add all foods to array
usort($foodArr,array($this,"date_sort"));
Related
I have an entity bundle called Map Points that has a taxonomy term reference field of countries. I'm using EntityFieldQuery in a custom module to list out all the map point entities but I need to group them by the country reference field.
Here is the code I have right now which filters the query by tid 1 and I know I can repeat the query for each country to group the results but I'm looking for a more refined way to group the results by the country term id's without having to write a new query for each country.
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'givepower')
->entityCondition('bundle', 'map_points')
->propertyCondition('type', 'map_points', '=')
->fieldCondition('field_map_point_country', 'tid', '1');
$result = $query->execute();
// set empty map point id array
$map_point_ids = array();
// loop through all givepower entities
foreach($result['givepower'] as $record) {
$map_point_ids[] = $record->id;
}
// load entities
$map_point = entity_load('givepower', $map_point_ids);
// set entity view
$entities = entity_view('givepower', $map_point);
return $entities;
I believe I figured this out. Here's the finished code:
//load recent_work_countries taxonomy
$vocabulary = taxonomy_vocabulary_machine_name_load('recent_work_countries');
// load all terms in the recent_work_countries taxonomy
$terms = entity_load('taxonomy_term', FALSE, array('vid' => $vocabulary->vid));
// set empty countries array
$countries = array();
// loop through each recent_work_countries term
foreach($terms as $tid => $value) {
// set country term name and term id(tid) vars
$country_name = $terms[$tid]->name;
$country_tid = $terms[$tid]->tid;
// add each country to the counties array
$countries[$country_name] = array();
// create new query to grab all map point entities with the current recent_work_countries tid
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'givepower')
->entityCondition('bundle', 'map_points')
->propertyCondition('type', 'map_points', '=')
->fieldCondition('field_map_point_country', 'tid', $country_tid);
$result = $query->execute();
// set empty tab id array
$map_point_ids = array();
// loop through all givepower entities that have the map_point_ids
foreach($result['givepower'] as $record) {
$map_point_ids[] = $record->id;
}
// load entities
$map_point = entity_load('givepower', $map_point_ids);
// set entity view
$entities = entity_view('givepower', $map_point);
// loop through each entities results
foreach($entities['givepower'] as $eid => $e_val) {
// add entity result data to countries array
$countries[$country_name][] = array(
'title' => $e_val['field_map_point_title']['#items'][0]['safe_value'],
'description' => $e_val['field_map_point_description']['#items'][0]['safe_value'],
'latitude' => $e_val['field_map_point_latitude']['#items'][0]['safe_value'],
'longitude' => $e_val['field_map_point_longitute']['#items'][0]['safe_value'],
);
}
}
This will create an array that looks like this:
Array (
[Kenya] => Array (
[0] => Array (
[title] => Test Map Point 1
[description] => lorum ipsum
[latitude] => 1.765404
[longitude] => 40.079880 )
[1] => Array (
[title] => Test Map Point 1
[description] => Lorum ipsum
[latitude] => 0.633657
[longitude] => 37.350050 ) )
[Haiti] => Array (
[0] => Array (
[title] => GA University
[description] => lorum ipsum
[latitude] => 18.574420
[longitude] => -72.310981 ) )
[Nepal] => Array ( )
[Ghana] => Array ( )
)
I'm making my first Symfony web app.
Now after creating a couple of forms I found some problem creating a form with a collection of choises.
Creating a collection of TextTypes is very easy and is working in a couple of my forms. Now when I create a collections of ChoiseTypes it does not show anything on the screen. After checking the html code it only show the select statement without any options. Also the prototype contains only the select and not the options.
Where is how I add the collections of choises to the form:
->add('fieldTypes', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'entry_options' => array(
'choices' => $fieldTypes,
'required' => true,
),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
))
And this is the twig part of the sollution:
<td>
<ul id="typeTable" data-prototype="{{ form_widget(form.fieldTypes.vars.prototype)|e }}">
{% for fieldType in form.fieldTypes %}
<li>
{{ form_widget(fieldType) }}
</li>
{% endfor %}
</ul>
</td>
And the full JS code:
<script type="text/javascript">
// keep track of how many email fields have been rendered
var nameCount = '{{ form.fieldNames|length }}';
var typeCount = '{{ form.fieldTypes|length }}';
var optionCount = '{{ form.fieldOptions|length }}';
jQuery(document).ready(function () {
jQuery('#add-new-field').click(function (e) {
e.preventDefault();
var nameList = jQuery('#nameTable');
var typeList = jQuery('#typeTable');
var optionList = jQuery('#optionTable');
// grab the prototype template
var newNameWidget = nameList.attr('data-prototype');
var newTypeWidget = typeList.attr('data-prototype');
var newOptionWidget = optionList.attr('data-prototype');
//var newTypeWidget = jQuery('#fieldType_prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newNameWidget = newNameWidget.replace(/__name__/g, nameCount);
newTypeWidget = newTypeWidget.replace(/__name__/g, typeCount);
newOptionWidget = newOptionWidget.replace(/__name__/g, optionCount);
var $options = $("#fieldType_prototype > option").clone();
//$(newTypeWidget).append($options);
$("#form_fieldTypes_" + typeCount).html( $("#fieldType_prototype").html() );
// newTypeWidget.attr("id","form_fieldTypes_" + typeCount);
// newTypeWidget.attr('name', 'form[fieldTypes][' + typeCount + ']');
nameCount++;
typeCount++;
optionCount++;
// create a new list element and add it to the list
var newLi = jQuery('<li></li>').html(newNameWidget);
newLi.appendTo(nameList);
var newLi = jQuery('<li></li>').html(newTypeWidget);
newLi.appendTo(typeList);
var newLi = jQuery('<li></li>').html(newOptionWidget);
newLi.appendTo(optionList);
});
})
</script>
Variable $fieldTypes contains this:
array('Text Field' => TextType::class,
'Text area' => TextareaType::class,
'Email' => EmailType::class,
'Number' => IntegerType::class,
'Url' => UrlType::class,
'Drop down' => ChoiceType::class,
'Date' => DateType::class,
'Checkbox' => CheckboxType::class);
}
Can someone help me with this issue?
It was a bug:
xabbuh:
'I guess you might be affected by a regression that will be fixed by github.com/symfony/symfony/pull/17162. Can you test if the changes from that PR solve your problem?'
I am creating simple search form that will query and render data using Doctrine LIKE expression.I am confused how to get the parameter like this
$name = 'San Francisco China';
In the controller i created a simple form
public function searchAction(Request $request)
{
$data = array();
$form= $this->createFormBuilder($data)
->add('name_city', 'text', array(
'label' => 'Search Here',
'error_bubbling' => true,
))
->add('search', 'submit')
->getForm();
if ($request->isMethod('POST')) {
$form->handleRequest($request);
$data = $form->getData();
}
$name = $request->request->get('name_city');//this confused me
//$name = 'Beijing Angeles';//this will work
$em = $this->getDoctrine()->getManager();
$city = $em->getRepository('Bundle:City')->searchCity($name);
return $this->render('Bundle:City:list.html.twig', array(
'city' => $city,
'form' => $form->createView(),
));
}
In this case, the $name variable is passed to the searchCity method as the argument
public function searchCity($name)
{
return $this
->createQueryBuilder('c')
->select('c')
->where('c.name LIKE :name_city')
->setParameter('name_city', '%'.$name.'%')
// ->orderBy('v.dateCreated', 'DESC')
->getQuery()
->getResult()
;
}
//list.twig
{% extends '::base.html.twig' %}
{% block body %}
{{ form(form)}}
{% for city in city %}
{{ city }}
{% endfor %}
{% endblock %}
This will not work since all city name are displayed during page load. If i manually add a value to the $name, e.g $name = 'Beijing Washington', search works.Whats the correct way?
As for the setting the default name_city, you could do this:
$data = array(
'name_city' => 'Beijing Angeles'
);
$form= $this->createFormBuilder($data)
->add('name_city', 'text', array(
'label' => 'Search Here',
'error_bubbling' => true,
))
->add('search', 'submit')
->getForm();
// The rest of your code
Then, get the city name like:
$form->handleRequest($request);
$data = $form->getData();
$name = $data['name_city'];
You might notice that I removed the POST check. That's because search forms are traditionally set to method="get", because of history/bookmarks. So, in your example, you would never check for POST but handlerRequest() each and every time.
Hope this helps...
Feel free to correct me if I misunderstood the intention here ;)
This is my WordPress table. I created an array so that I could try it out, but I need to add classes and IDs so I can use CSS to style it like the top level plugin page.
How can I add classes to the table elements?
<?php
if(!class_exists('WP_List_Table')){
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
class TT_Example_List_Table extends WP_List_Table {
var $example_data = array(
array(
'ID' => 1,
'title' => '300',
'rating' => 'R',
'director' => 'Zach Snyder'
),
array(
'ID' => 2,
'title' => 'Eyes Wide Shut',
'rating' => 'R',
'director' => 'Stanley Kubrick'
),
array(
'ID' => 3,
'title' => 'Moulin Rouge!',
'rating' => 'PG-13',
'director' => 'Baz Luhrman'
),
array(
'ID' => 4,
'title' => 'Snow White',
'rating' => 'G',
'director' => 'Walt Disney'
),
array(
'ID' => 5,
'title' => 'Super 8',
'rating' => 'PG-13',
'director' => 'JJ Abrams'
),
array(
'ID' => 6,
'title' => 'The Fountain',
'rating' => 'PG-13',
'director' => 'Darren Aronofsky'
),
array(
'ID' => 7,
'title' => 'Watchmen',
'rating' => 'R',
'director' => 'Zach Snyder'
)
);
function __construct(){
global $status, $page;
//Set parent defaults
parent::__construct( array(
'singular' => 'movie', //singular name of the listed records
'plural' => 'movies', //plural name of the listed records
'ajax' => false //does this table support ajax?
) );
}
function column_default($item, $column_name){
switch($column_name){
case 'rating':
case 'director':
return $item[$column_name] . 'hi';
default:
return print_r($item,true) . ' hi'; //Show the whole array for troubleshooting purposes
}
}
function column_title($item){
//Build row actions
$actions = array(
'edit' => sprintf('Edit',$_REQUEST['page'],'edit',$item['ID']),
'delete' => sprintf('Delete',$_REQUEST['page'],'delete',$item['ID']),
);
//Return the title contents
return sprintf('%1$s <span style="color:silver">(id:%2$s)</span>%3$s',
/*$1%s*/ $item['title'],
/*$2%s*/ $item['ID'],
/*$3%s*/ $this->row_actions($actions)
);
}
function column_cb($item){
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
/*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label ("movie")
/*$2%s*/ $item['ID'] //The value of the checkbox should be the record's id
);
}
function get_columns(){
$columns = array(
'cb' => '<input type="checkbox" />', //Render a checkbox instead of text
'title' => 'Title',
'rating' => 'Rating',
'director' => 'Director'
);
return $columns;
}
function get_sortable_columns() {
$sortable_columns = array(
'title' => array('title',true), //true means its already sorted
'rating' => array('rating',false),
'director' => array('director',false)
);
return $sortable_columns;
}
function get_bulk_actions() {
$actions = array(
'delete' => 'Delete'
);
return $actions;
}
function process_bulk_action() {
//Detect when a bulk action is being triggered...
if( 'delete'===$this->current_action() ) {
wp_die('Items deleted (or they would be if we had items to delete)!');
}
}
function prepare_items() {
$per_page = 5;
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
$this->process_bulk_action();
$data = $this->example_data;
function usort_reorder($a,$b){
$orderby = (!empty($_REQUEST['orderby'])) ? $_REQUEST['orderby'] : 'title'; //If no sort, default to title
$order = (!empty($_REQUEST['order'])) ? $_REQUEST['order'] : 'asc'; //If no order, default to asc
$result = strcmp($a[$orderby], $b[$orderby]); //Determine sort order
return ($order==='asc') ? $result : -$result; //Send final sort direction to usort
}
usort($data, 'usort_reorder');
$current_page = $this->get_pagenum();
$total_items = count($data);
$data = array_slice($data,(($current_page-1)*$per_page),$per_page);
$this->items = $data;
$this->set_pagination_args( array(
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page, //WE have to determine how many items to show on a page
'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
) );
}
}
function tt_add_menu_items(){
add_menu_page('Example Plugin List Table', 'List Table Example', 'activate_plugins', 'tt_list_test', 'tt_render_list_page');
} add_action('admin_menu', 'tt_add_menu_items');
function tt_render_list_page(){
$testListTable = new TT_Example_List_Table();
$testListTable->prepare_items();
?>
<div class="wrap">
<div id="icon-users" class="icon32"><br/></div>
<h2>List Table Test</h2>
<form id="movies-filter" method="get">
<input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
<?php $testListTable->display() ?>
</form>
</div>
<?php
}
The only way to do this is by overriding some of the methods of the WP_List_Table class.
I went ahead and modified your class to support conditional HTML classes for each tr/td in the table. You weren't clear enough about to which elements you want classes applied, nor how exactly you want to specify that, so excuse me if it's not what you wanted(and please specify further details).
You can see the full code(only the TT_Example_List_Table class is there - the rest is the same) here.
Basically you define a class property called $cond_classes. This property is a multidimensional array of conditions. In there you have two top-level keys which are reserved - "odd" and "even". As you can guess they will be accessed for each row that is either odd or even.
The rest of the top-level keys can be column id's or item ID's.
Each top-level key can hold either an array or a string
If the top-level key holds a string, then when this condition is met, that class is added
If the top-level key holds an array, then it's looped through.
The second-level array can have string values and key=>value pairs, where the key is the class and the value
is an array of conditions.
I guess that's quite confusing, but the example bellow should give you an idea of how this works.
var $cond_classes = array(
'odd' => array(
'odd-class', // This class will always be given to odd rows and their columns
'special-odd-class' => array( // This class will only be given to odd rows and their columns if the rows is for an item with ID 1, 4 or 7
'ID' => array( 1, 4, 7 )
)
),
'even' => array(
'even-class'
),
'title' => array(
'custom_title_class',
'special_title_class' => array(
'ID' => array( 3, 7 ), // This will only be given to the "title" column for an item with ID 3 or 7
'title' => 'The Fountain', // This will only be given to the "title" column for an item with title "The Fountain"
),
),
7 => 'id_7_class', // This will be given to a row and it's columns for item with ID 7
);
And you can see the applied classes in the resulting table:
Hope that helps! If you have any questions - go ahead :)
I am validating some values:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array('email' => $this->getRequest()->get('email'), 'password' => $this->getRequest()->get('password'));
$errors = $this->get('validator')->validateValue($data, $collectionConstraint);
But for some reason the fields (propertyPath) are stored with square brackets - I'd like to understand why Sf does that. I have to manually remove all the brackets which seems absurd so I think I am missing some functionality somewhere.
Dump of $errors:
Symfony\Component\Validator\ConstraintViolationList Object
(
[violations:protected] => Array
(
[0] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [email]
[invalidValue:protected] =>
)
[1] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [password]
[invalidValue:protected] =>
)
)
)
Even the toString function is useless.
"[email]: This value should not be blank","[password]: This value should not be blank"
Property paths can map either to properties or to indices. Consider a class OptionBag which implements \ArrayAccess and a method getSize().
The property path size refers to $optionBag->getSize()
The property path [size] refers to $optionBag['size']
In your case, you validate an array. Since array elements are also accessed by index, the resulting property path in the violation contains squared brackets.
Update:
You don't have to manually remove the squared brackets. You can use Symfony's PropertyAccess component to map errors to an array with the same structure as your data, for example:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array(
'email' => $this->getRequest()->get('email'),
'password' => $this->getRequest()->get('password')
);
$violations = $this->get('validator')->validateValue($data, $collectionConstraint);
$errors = array();
$accessor = $this->get('property_accessor');
foreach ($violations as $violation) {
$accessor->setValue($errors, $violation->getPropertyPath(), $violation->getMessage());
}
=> array(
'email' => 'This value should not be blank.',
'password' => 'This value should have 6 characters or more.',
)
This also works with multi-dimensional data arrays. There the property paths will be something like [author][name]. The PropertyAccessor will insert the error messages in the same location in the $errors array, i.e. $errors['author']['name'] = 'Message'.
PropertyAccessor's setValue is no real help because it cannot handle multiple violations for a single field. For instance, a field might be shorter than a constraint length and also contain illegal characters. For this, we would have two error messages.
I had to create my own code:
$messages = [];
foreach ($violations as $violation) {
$field = substr($violation->getPropertyPath(), 1, -1);
$messages[] = [$field => $violation->getMessage()];
}
$output = [
'name' => array_unique(array_column($messages, 'name')),
'email' => array_unique(array_column($messages, 'email')),
];
return $output;
We manually strip the [] characters from the property path and create an
array of arrays of fields and corresponding messages. Later we transform the
array to group the messages by fields.
$session = $request->getSession();
$session->getFlashBag()->setAll($messages);
In the controller, we add the messages to the flash bag.