Autocomplete with ajax in Symfony2 - jquery-ui

I'm using jQueryUI autocomplete to get a list of cities. I'm using a json array to display cities, but I just can't get the list to appear.
here is my Ajax function :
/**
* #Route("/ajax", name="ajax")
*/
public function ajaxAction(Request $request)
{
$value = $request->get('term');
$em = $this->getDoctrine()->getEntityManager();
$branches = $em->getRepository('AOFVHFlyBundle:City')
->findByName($value);
// Get branches by user if non-admin
$json = array();
foreach ($branches as $branch) {
$json[] = array(
'label' => sprintf('%s (%s)', $branch['name'], $branch['departement']),
'value' => $branch['id']
);
}
$response = new Response();
// better way to return json - via header?
$response->setContent(json_encode($json));
return $response;
}
and here is the form input :
->add('reqdeparture', 'genemu_jqueryautocompleter_entity', array(
'class' => 'AOFVH\FlyBundle\Entity\City',
'property' => 'name',
'route_name' => 'ajax',
'attr' => array(
'placeholder'=>'Départ',
'autocomplete' => 'on')
))
Whatever I type in the input, nothing appears.
Any idea why ? (at first I thought it was because collection was too big, but I barely have 50 cities)

You can use JsonResponse object. The Content-Type header will be 'application/json'. Symfony manage the chars to escape automatically and others conditions to return a JSON normalized correctly.
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* #Route("/ajax", name="ajax")
*/
public function ajaxAction(Request $request)
{
$value = $request->get('term');
$branches = $this->get("doctrine.orm.default_entity_manager")
->getRepository("NamespaceMyBundle:Entity")
->findByName($value);
$json = array();
foreach ($branches as $branch) {
$json[] = array(…);
}
return new JsonResponse($json);
}

Related

Yii2 best Implementation when you want to consume MS SQL stored procedure

I need advice on the best way to use yii2 with stored procedures. As this area is very grey.
I currently have a Yii 1 project implementation and like any developer, we are always looking out for new ways to speed up and write better code than we did the previous day.
I want to start off and port my new app to yii2 and was wondering what is the best way to consume stored procedure as that is the architecture currently in the organization and as much as I would with to interact with the database directly using Yii Active records it just impossible.
So in my current Yii 1 project I have created a class in component and imported it in config/main.php
'import' => array(
'application.models.*',
'application.components.SqlResource.*',
),
Then i proceeded to construct my class as
class SqlResource extends CApplicationComponent {
const LOG_CAT = "ext.SqlResource";
public $resources = array();
public $db;
public $mssql;
/**
* __construct
*
* #access public
* #return void
*/
public function __construct() {
$serverUrl = Yii::app()->params->mssql['host'];
$serverUser = Yii::app()->params->mssql['user'];
$serverPass = Yii::app()->params->mssql['password'];
$serverDb = Yii::app()->params->mssql['db_name'];
try {
$this->mssql = new PDO('dblib:host=' . $serverUrl . ';dbname=' . $serverDb, $serverUser, $serverPass);
$this->mssql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
Yii::log("Connection Error: #(DB connection was unavailable )[" . $e->getMessage() . "] ", "error", self::LOG_CAT);
die($e->getMessage());
}
}
/**
*
* #param type $data array containg username and password from the login form
* #return array
*/
public function ExampleSqlResourceFunction($username, $password) {
if (!empty($username) && is_string($username)) {
$procedure = "exec login_access #username = :username, #password = :password"; //Procedure to be called
$query = $this->mssql->prepare($procedure); //PDO prepare a query for execution using bind parameters to avaid leve 1 and level 2 sql injection
$start = array_sum(explode(' ', round(microtime(TRUE) * 1000))); //start time to calculate the time it takes to execute a query. the log time is logged for debugging process
$query->bindValue(":username", $username, PDO::PARAM_STR); //bind the alias username from the prepared statment to the actual value and specify the datatype for this variable
$query->bindValue(":password", $password, PDO::PARAM_STR); //bind the alias password from the prepared statment to the actual value and specify the datatype for this variable
$execute = $query->execute(); //execute the query
$stop = array_sum(explode(' ', round(microtime(true) * 1000))); //stop time to calculate the time it takes to execute a query. the log time is logged for debugging process
$totalMs = substr(($stop - $start), 0, 5); //total ms it took to execute the query
$array = array(); //declare the return $array as an array
if ($execute == TRUE) {//If query executes successfully return $return array $results
$key_column = null; //format the retun array
while ($obj = $query->fetch(PDO::FETCH_ASSOC)) {
isset($obj[$key_column]) ? $array[$obj[$key_column]] = $obj : $array[] = $obj;
}//log the how long it took to execute the query and the trace which procedure was executed
Yii::log("Took $totalMs ms to fetch Login Details result set", "info", self::LOG_CAT);
Yii::log("[login] " . '" ' . $procedure . '"', "trace", self::LOG_CAT);
return $array;
} else {
$results = 'not execute';
return $results;
}
}
}
After that i initialize my SqlResouce in my controller as follows
public function actionExampleControllerAction() {
$sql = new SqlResource();
$results = $sql->ExampleSqlResourceFunction();
if (isset($results) && !empty($results)) {
foreach ($results as $key => $value) {
$array[$key] = array(
'type' => 'column',
'name' => $value["Department_Name"],
'value' => $value['Count'],
);
}
}
echo json_encode($array, JSON_NUMERIC_CHECK);
Yii::app()->end();
}
Use createCommand function inside Yii2 app.
$result = \Yii::$app->db->createCommand("exec login_access #username = :username, #password = :password")
->bindValue(':username' , $username)
->bindValue(':password', $password)
->execute();
To change db to another just create another component like dbMS
//Your MySql db
'db'=>
[
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=my_database',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
//Your MS SQL db
'dbMS'=>
[
'class' => 'yii\db\Connection',
'dsn' => 'dblib:host=mssqlserver;dbname=my_database',
'username' => 'sa',
'password' => 'superpass',
'charset' => 'utf8',
],
So now you can easily and dynamically change your MySQL db to MSSQL dbMS on runtime.
$result = \Yii::$app->dbMS->createCommand("exec login_access #username = :username, #password = :password")
->bindValue(':username' , $username)
->bindValue(':password', $password)
->execute();

Declaring Symfony Form options (Sym 2.8/3.0)

I am looking for the best method for creating/adding dynamic options in a form. By options, I mean things like choice value pairs, or maybe even default values. I can see at least three options:
1) add the options to the $options array when adding the form type. For this, it appears that I must first declare a default value and then add them in the add method and in the controller:
controller:
$choices = [];
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$options = ['pages' => $choices];
$form = $this->createForm('MyBundle\Form\Type\PageType', $data, $options);
FormType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $options['pages'],
'label' => __('Page')
]);
}
...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'pages' => []
]);
}
}
2) If the values are not dependent on controller values, it seems I could create them in the OptionsResolver (assuming access to the source data)
FormType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $options['pages'],
'label' => __('Page')
]);
}
...
public function configureOptions(OptionsResolver $resolver)
{
$choices = [];
$pages = $this->getPages();
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$resolver->setDefaults([
'pages' => $choices
]);
}
3) Finally, I can also add in the buildForm method (again assuming access to source data):
FormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [];
$pages = $this->getPages();
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $choices,
'label' => __('Page')
]);
}
Obviously, there is the most flexibility in the first option, but If I do not require that flexibility, or do not want to manage the options in the controller for some reason, does it make more sense to do the work in the buildForm or configureOptions methods?
If you require flexibility you can't use solution 3. But if you want to avoid flexibility, solution 3 is the best.
Solution 1 and 2 are OK, it really depend of what you need :
If you use your form in several actions with different choices: use solution 1, but add a requirement on this option to prevent the form to be called without choices
If your choices are often the same, but you want to override them only sometimes: chose solution 2
Personally I prefer the solution 1, because it's always better if your form relies on the less possible external objects ($this->pages in your example).
Regards
If you work with Doctrine Entities, you should use this:
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...
$builder->add('pid', EntityType::class, array(
'class' => 'AppBundle:Page',
'choice_label' => 'title',
));
For working with another type of objects this one:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use AppBundle\Entity\Page;
// ...
$builder->add('pid', ChoiceType::class, [
'choices' => [
new Page('Page 1'),
new Page('Page 2'),
new Page('Page 3'),
new Page('Page 4'),
],
'choices_as_values' => true,
'choice_label' => function($page, $key, $index) {
/** #var Page $page */
return $page->getTitle();
}
]);
More information you can read in blog post here.

How to modify parameters in Guzzle middleware?

I want to write a middleware for Guzzle that adds a specific key to the form_params and populates it with a value. In the docs I have read how to modify the headers but have not found anything about other properties of the $request object. Following the example from the docs this is what I have:
$key = 'asdf';
$stack = new HandlerStack();
$stack->setHandler(new CurlHandler());
$stack->push(Middleware::mapRequest(function (RequestInterface $request) use ($key) {
// TODO: Modify the $request so that
// $form_params['api_key'] == 'default_value'
return $request;
}));
$client = new Client(array(
'handler' => $stack
));
The middleware should modify the request so that this:
$client->post('example', array(
'form_params' => array(
'foo' => 'some_value'
)
));
has the same effect as this:
$client->post('example', array(
'form_params' => array(
'foo' => 'some_value',
'api_key' => 'default_value'
)
));
Having done something similar to this, I can say it is very easy.
If you reference GuzzleHttp\Client two things happen when you pass an array into the request in the 'form_params' input option. First, the contents of the array become the body of the request after being urlencoded using http_build query() and secondly, the 'Content-Type' header is set to 'x-www-form-urlencoded'
The snippet below is akin to what you are looking for.
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
// perform logic
return new GuzzleHttp\Psr7\Request(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
http_build_query($added_parameters_array) . '&' . $request->getBody()->toString()
);
}));

How to add a class to all labels in a ZF2 form

I'm using a jQuery plugin that takes the text from labels associated with form elements and puts them as default text for the fields themselves. (You can find the plugin here.)
Here's the catch: it can only do this if the label has the class "inline". Now, I know I can use the following code to do this:
$this->add(array (
'name' -> 'name',
....
'options' => array (
'label' => 'Name',
'label_attributes' => array (
'class' => 'inline'
)
)
));
This will work fine, and if it has to be done item by item, then so be it. But I was wondering if there's some way I can add the class to ALL labels associated with text and text area form elements without using JavaScript. I'm thinking this would either done by a plugin, or by looping through all the elements in the form, but I don't know how to do either.
You could extend the FormRow view helper.
Here is a little example:
use Zend\Form\View\Helper\AbstractHelper;
use Zend\Form\View\Helper\FormRow;
class CustomFormRow extends FormRow
{
public function render(ElementInterface $element) {
...
$label = $element->getLabel();
if (isset($label) && '' !== $label) {
// Translate the label
if (null !== ($translator = $this->getTranslator())) {
$label = $translator->translate(
$label, $this->getTranslatorTextDomain()
);
}
$label->setAttribute('class', 'inline');
}
...
if ($this->partial) {
$vars = array(
'element' => $element,
'label' => $label,
'labelAttributes' => $this->labelAttributes,
'labelPosition' => $this->labelPosition,
'renderErrors' => $this->renderErrors,
);
return $this->view->render($this->partial, $vars);
}
...
}
You could probably leave the rest as it is and you should be good to go once you add some configuration in your Module.php for your view helper.
public function getViewHelperConfig() {
return array(
'factories' => array(
'CustomFormRow' => function($sm) {
return new \Application\View\Helper\CustomFormRow;
},
)
);
}
In your template files you now have to use your viewHelper instead.
<?php echo $this->CustomFormRow($form->get('yourelement')); ?>

How can I add a violation to a collection?

My form looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$factory = $builder->getFormFactory();
$builder->add('name');
$builder->add('description');
$builder->add('manufacturers', null, array(
'required' => false
));
$builder->add('departments', 'collection', array(
'type' => new Department
));
}
I have a class validator on the entity the form represents which calls:
if (!$valid) {
$this->context->addViolationAtSubPath('departments', $constraint->message);
}
Which will only add a 'global' error to the form, not an error at the sub path. I assume this is because departments is a collection embedding another FormType.
If I changed departments to one of the other fields it works fine.
How can I get this error to appear in the right place? I assume it would work fine if my error was on a single entity within the collection, and thus rendered in the child form, but my criteria is that the violation occur if none of the entities in the collection are marked as active, thus it needs to be at the parent level.
By default, forms have the option "error_bubbling" set to true, which causes the behavior you just described. You can turn off this option for individual forms if you want them to keep their errors.
$builder->add('departments', 'collection', array(
'type' => new Department,
'error_bubbling' => false,
));
I have been wrestling with this issue in Symfony 3.3, where I wished to validate an entire collection, but pass the error to the appropriate collection element/field. The collection is added to the form thus:
$form->add('grades', CollectionType::class,
[
'label' => 'student.grades.label',
'allow_add' => true,
'allow_delete' => true,
'entry_type' => StudentGradeType::class,
'attr' => [
'class' => 'gradeList',
'help' => 'student.grades.help',
],
'entry_options' => [
'systemYear' => $form->getConfig()->getOption('systemYear'),
],
'constraints' => [
new Grades(),
],
]
);
The StudentGradeType is:
<?php
namespace Busybee\Management\GradeBundle\Form;
use Busybee\Core\CalendarBundle\Entity\Grade;
use Busybee\Core\SecurityBundle\Form\DataTransformer\EntityToStringTransformer;
use Busybee\Core\TemplateBundle\Type\SettingChoiceType;
use Busybee\Management\GradeBundle\Entity\StudentGrade;
use Busybee\People\StudentBundle\Entity\Student;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class StudentGradeType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
/**
* StaffType constructor.
*
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('status', SettingChoiceType::class,
[
'setting_name' => 'student.enrolment.status',
'label' => 'grades.label.status',
'placeholder' => 'grades.placeholder.status',
'attr' => [
'help' => 'grades.help.status',
],
]
)
->add('student', HiddenType::class)
->add('grade', EntityType::class,
[
'class' => Grade::class,
'choice_label' => 'gradeYear',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('g')
->orderBy('g.year', 'DESC')
->addOrderBy('g.sequence', 'ASC');
},
'placeholder' => 'grades.placeholder.grade',
'label' => 'grades.label.grade',
'attr' => [
'help' => 'grades.help.grade',
],
]
);
$builder->get('student')->addModelTransformer(new EntityToStringTransformer($this->om, Student::class));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(
[
'data_class' => StudentGrade::class,
'translation_domain' => 'BusybeeStudentBundle',
'systemYear' => null,
'error_bubbling' => true,
]
);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'grade_by_student';
}
}
and the validator looks like:
namespace Busybee\Management\GradeBundle\Validator\Constraints;
use Busybee\Core\CalendarBundle\Entity\Year;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class GradesValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (empty($value))
return;
$current = 0;
$year = [];
foreach ($value->toArray() as $q=>$grade)
{
if (empty($grade->getStudent()) || empty($grade->getGrade()))
{
$this->context->buildViolation('student.grades.empty')
->addViolation();
return $value;
}
if ($grade->getStatus() === 'Current')
{
$current++;
if ($current > 1)
{
$this->context->buildViolation('student.grades.current')
->atPath('['.strval($q).']') // could do a single atPath with a value of "[".strval($q)."].status"
->atPath('status') // full path = children['grades'].data[1].status
->addViolation();
return $value;
}
}
$gy = $grade->getGradeYear();
if (! is_null($gy))
{
$year[$gy] = empty($year[$gy]) ? 1 : $year[$gy] + 1 ;
if ($year[$gy] > 1)
{
$this->context->buildViolation('student.grades.year')
->atPath('['.strval($q).']')
->atPath('grade')
->addViolation();
return $value;
}
}
}
}
}
This results in the error being added to the field in the element of the collection as per the attach image.
Craig
I have a case very similar. I have a CollectionType with a Custom Form (with DataTransformers inside, etc...), i need check one by one the elements and mark what of them is wrong and print it on the view.
I make that solution at the ConstraintValidator (my custom validator):
The validator must target to CLASS_CONSTRAINT to work or the propertyPath doesnt work.
public function validate($value, Constraint $constraint) {
/** #var Form $form */
$form = $this->context->getRoot();
$studentsForm = $form->get("students"); //CollectionType's name in the root Type
$rootPath = $studentsForm->getPropertyPath()->getElement(0);
/** #var Form $studentForm */
foreach($studentsForm as $studentForm){
//Iterate over the items in the collection type
$studentPath = $studentForm->getPropertyPath()->getElement(0);
//Get the data typed on the item (in my case, it use an DataTransformer and i can get an User object from the child TextType)
/** #var User $user */
$user = $studentForm->getData();
//Validate your data
$email = $user->getEmail();
$user = $userRepository->findByEmailAndCentro($email, $centro);
if(!$user){
//If your data is wrong build the violation from the propertyPath getted from the item Type
$this->context->buildViolation($constraint->message)
->atPath($rootPath)
->atPath(sprintf("[%s]", $studentPath))
->atPath("email") //That last is the name property on the item Type
->addViolation();
}
}
}
Just i validate agains the form elements in the collection and build the violation using the propertyPath from the item in the collection that is wrong.

Resources