I am using Symfony3.1 with FOS UsersBundle and I want some added fields to be loaded as specific Entity.
In RegistrationType I have
->add('country', ChoiceType::class, array(
'label' => 'label.country',
'required' => false,
'placeholder' => 'label.select_country',
'choices' => array(
'France' => '7v8tqr',
),
))
In my Entity User I have
/**
* #ORM\OneToOne(targetEntity="Country")
* #ORM\JoinColumn(name="country", referencedColumnName="short")
*/
protected $country;
I can't use the EntityType as it loads every available entity and I use the same kind of field for provinces and cities which are quite huge (I manage their content with javascript).
When I load a registered user, the country field is served as a Country Entity but when I register a new user or modify an existing one, I only have the string "short" which causes an error Expected value of type "AppBundle\Entity\Country" for association field "AppBundle\Entity\User#$country", got "string" instead..
Is there a solution ?
Thanks to #mcriecken who led me in the right direction, I have implemented the following solution, using an EventListener
in services.yml
app_user.registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: kernel.event_subscriber }
and the EventListener UserRegistrationListener.php
<?php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Doctrine\ORM\EntityManager;
class UserRegistrationListener implements EventSubscriberInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$form = $event->getForm()->getData();
//Gets the locations
$form->setCountry($this->getCountry($form->getCountry()));
$form->setProvince($this->getProvince($form->getProvince()));
$form->setCity($this->getCity($form->getCity()));
}
//Loads the country as an entity
public function getCountry($short)
{
if ($short == null) return null;
$repository = $this->em->getRepository('AppBundle:Country');
return $repository->findOneByShort($short);
}
//Loads the province as an entity
public function getProvince($short)
{
if ($short == null) return null;
$repository = $this->em->getRepository('AppBundle:Province');
return $repository->findOneByShort($short);
}
//Loads the city as an entity
public function getCity($short)
{
if ($short == null) return null;
$repository = $this->em->getRepository('AppBundle:City');
return $repository->findOneByShort($short);
}
}
Then at the end my FOS User object contains COuntry, Province and City as Objects and it can be saved to DB :-)
Related
I recently installed CRM Core and all of its missing modules needed to run it. Sadly I need this module for the project that I am working on but the second I installed them I got this error.
Fatal error: Declaration of CRMCoreContactController::save($contact) must be compatible with EntityAPIController::save($entity, ?DatabaseTransaction $transaction = NULL) in /opt/lampp/htdocs/drupal/modules/crm_core/modules/crm_core_contact/includes/crm_core_contact.controller.inc on line 111
I went back in the code and I couldn't see what to change. Line 111 is the ver last line of the code. Ill paste the code as well maybe someone out there knows how to solve this, please.
<?php
/**
* CRM Contact Entity Class.
*/
class CRMCoreContactEntity extends Entity {
protected function defaultLabel() {
return crm_core_contact_label($this);
}
protected function defaultUri() {
return array(
'path' => 'crm-core/contact/' . $this->identifier(),
'options' => array(
'absolute' => TRUE,
),
);
}
/**
* Method for de-duplicating contacts.
*
* Allows various modules to identify duplicate contact records through
* hook_crm_core_contact_match. This function should implement it's
* own contact matching scheme.
*
* #return array
* Array of matched contact IDs.
*/
public function match() {
$checks = & drupal_static(__FUNCTION__);
$matches = array();
if (!isset($checks->processed)) {
$checks = new stdClass();
$checks->engines = module_implements('crm_core_contact_match');
$checks->processed = 1;
}
// Pass in the contact and the matches array as references.
// This will allow various matching tools to modify the contact
// and the list of matches.
$values = array(
'contact' => &$this,
'matches' => &$matches,
);
foreach ($checks->engines as $module) {
module_invoke($module, 'crm_core_contact_match', $values);
}
// It's up to implementing modules to handle the matching logic.
// Most often, the match to be used should be the one
// at the top of the stack.
return $matches;
}
}
/**
* #file
* Controller class for contacts.
*
* This extends the DrupalDefaultEntityController class, adding required
* special handling for contact objects.
*/
class CRMCoreContactController extends EntityAPIController {
public $revisionKey = 'vid';
public $revisionTable = 'crm_core_contact_revision';
/**
* Create a basic contact object.
*/
public function create(array $values = array()) {
global $user;
$values += array(
'contact_id' => '',
'vid' => '',
'uid' => $user->uid,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
);
return parent::create($values);
}
/**
* Update contact object before saving revision.
*/
protected function saveRevision($entity) {
if (!isset($entity->log)) {
$entity->log = '';
}
$entity->is_new_revision = TRUE;
$entity->uid = $GLOBALS['user']->uid;
return parent::saveRevision($entity);
}
/**
* Updates 'changed' property on save.
*/
public function save($contact) {
$contact->changed = REQUEST_TIME;
// Storing formatted contact label for autocomplete lookups.
$contact->name = crm_core_contact_label($contact);
return parent::save($contact);
}
}
Changing
public function save($contact)
to
public function save($contact, DatabaseTransaction $transaction = NULL)
should work.
You need to switch from PHP 7.x+ to PHP 5.6. This will resolve this error.
Can't give you more advice on how to downgrade without more details on what system you're running but there are many guides out there on this topic.
I'm working on CakePHP 3.4
I have a contact_messages table to save message via form on website.
I want to send user an email whenever a new message is saved.
For that, I have created mailer class like
<?php
namespace App\Mailer;
use Cake\Mailer\Mailer;
use Cake\Event\Event;
use Cake\Datasource\EntityInterface;
class ContactMessageMailer extends Mailer
{
public function newMessage($message)
{
$this
->setProfile('no-reply')
->setTemplate('new_message')
->setLayout('message')
->setEmailFormat('html')
->setTo($user->email) // user email
->setSubject('Verify Account')
->setViewVars(['name' => $user->first_name, 'email' => $user->email, 'message' => $message->body]);
}
public function implementedEvents()
{
return [
'Model.afterSave' => 'alertMessage'
];
}
public function alertMessage(Event $event, EntityInterface $entity, \ArrayObject $options)
{
if ($entity->isNew()) {
$this->send('newMessage', [$entity]);
}
}
}
and registering event in ContactMessagesTable.php
$mailer = new UserMailer(); //use App\Mailer\UserMailer;
$this->eventManager()->on($mailer);
ContactMessages belongsTo Users and Users is having email of user whom to send the email.
How can I get users information in Mailer?
Will probably do this;
In the User table;
public function processUser($user)
{
if($this->save($user)){
$event = new Event('Model.afterSave', $this, [$entity = $user])
$this->eventManager()->dispatch($event);
return true;
}else{
return false;
}
}
In ContactMessage Table ;
public function initialize()
{
parent::intialize();
$mailer = new UserMailer(); //use App\Mailer\UserMailer;
$this->Users->eventManager()->on($mailer); //ContactMessage has to be related to User table
}
Hope I was able to communicate.
I have problem, i can't return my posts array to json becouse symfony returns array with entity object?
Its my code:
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('AppBundle:Post')->findAll();
return $this->json($posts);
}
I use $this->json is return json data, feature added on sf3.
But this is my result:
[
{},
{},
{}
]
i want to load my posts.
ps. i know, i can use Query builder, and method toArray or something, but is any method to use and DRY? Thx
Because entity can have multiple boundaries, proxy objects and related entities, I personally prefer to explicitly specify what is about to be serialized, like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
/** #Column(length=50) */
private $title;
/** #Column(length=50) */
private $text;
public function jsonSerialize()
{
return array(
'title' => $this->title,
'text' => $this->text,
);
}
}
And then it's as simple as json_encode($someEntityInstance);.
You can use JMSSerializerBundle as well to accomplish your task DRY.
Also, there is an option to write your own serializer to normalize the data.
UPDATE:
If you want multiple representations of a JSON, it can be achieved like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
// ...
protected $isList;
public function toList()
{
$this->isList = TRUE;
return $this;
}
private function jsonSerializeToList()
{
return [ // array representing list... ]
}
public function jsonSerialize()
{
if( $this->isList ) {
$normalized = $this->jsonSerializeToList();
} else {
$normalized = array(
'title' => $this->title,
'text' => $this->text,
);
}
return $normalized;
}
}
And called as json_encode($someEntityInstance->toList());. Any way, this is a bit dirty, so I suggest to be consistent with an idea of the interface.
A best solution is to enable the serializer component in Symfony:
#app/config/config.yml
framework:
serializer: ~
Note: the serializer component is disabled by default, you have to uncomment the config line in app/config/config.yml file.
Below is my controller & model logic - I just started a barebones Yii installation to play around with it more.
I get no errors but don't see the new entry in the database - my db has been configured in the main.php (this works as Gii runs).
// controllers/PageController.php
class PageController extends Controller
{
public function actionSave($value='')
{
$pageObj = new Page;
$pageObj->savePage();
}
}
// models/Page.php
class Page extends CActiveRecord
{
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'page';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('title, date_updated', 'required'),
array('live', 'numerical', 'integerOnly'=>true),
array('user_id', 'length', 'max'=>10),
array('title', 'length', 'max'=>100),
array('content, date_published', 'safe'),
// The following rule is used by search().
// #todo Please remove those attributes that should not be searched.
array('id, user_id, live, title, content, date_updated, date_published', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'page_id'),
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
'files' => array(self::MANY_MANY, 'File', 'page_has_file(page_id, file_id)'),
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'user_id' => 'User',
'live' => 'Live',
'title' => 'Title',
'content' => 'Content',
'date_updated' => 'Date Updated',
'date_published' => 'Date Published',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* #return CActiveDataProvider the data provider that can return the models
* based on the search/filter conditions.
*/
public function search()
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id,true);
$criteria->compare('user_id',$this->user_id,true);
$criteria->compare('live',$this->live);
$criteria->compare('title',$this->title,true);
$criteria->compare('content',$this->content,true);
$criteria->compare('date_updated',$this->date_updated,true);
$criteria->compare('date_published',$this->date_published,true);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return Page the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function savePage($value='')
{
$page = new page;
$model->isNewRecord = true;
$model->primaryKey = NULL;
$page->title='sample page';
$page->content='content for the sample page';
$page->save(false);
}
}
In Yii, when you want to insert into a table which has some null columns, you must put null columns in your rules as SAFE like below:
array('primaryKey','safe'),
Now, Yii knows that primaryKey is a null column. So, there would be no problem via inserting into the current model.
As a note, when you call save() method with FALSE, you are telling to your model to do not the validation on insert.
Also, the correct way to skip possible errors is to validate your model before inserting like below:
if($model->validate()){
// VALIDATE, YOU CAN CALL SAVE FUNCTION
}else{
//here you can send an error message via FLASH or you can debug what the exact error is like below:
CVarDumper::dump($model->getErrors(),5678,true);
Yii::app()->end();
}
I hope, it help
So simple... I hate Yii sometimes :-)
Had to set the save() to save(false)
$page = new page;
$page->isNewRecord = true;
$page->primaryKey = NULL;
$page->title='sample page';
$page->content='content for the sample page';
$page->save(false);
Thanks for that - I had missed out some columns.. (silly me)
Improve the function even further with the help above..
public function savePage()
{
$page = new page;
$page->isNewRecord = true;
$page->primaryKey = NULL;
$page->user_id = 1;
$page->live = 0;
$page->content='content for the sample page';
$page->date_updated = date('Y-m-d H:i:s');
$page->date_published = date('Y-m-d H:i:s');
$page->title='sample page';
if ($page->validate()) {
$page->save();
} else {
CVarDumper::dump($page->getErrors(),5678,true);
Yii::app()->end();
}
}
I'm beginner on Symfony2.
I have a Regions-Countries-States-Cities database with more of 2,000,000 results. I have 8 entities:
Region (recursive with itself) - RegionTranslation
Country - CountryTranslation
State (recursive with itself) - StateTranslation
City - CityTranslation
The thing is that when I want to load a countries list (only 250 registers in a pulldown, for example) Symfony+Doctrine load all entities structure (all states of all countries, and all cities of all states, with their respective translations).
I think that it spends a lot of memory.
What's the correct method to do it? Can I load only Country (and translations) with this structure? Any idea?
I have had the same problem for objected that are unassociated. Your best bet is to use select2's ajax loading (http://ivaynberg.github.com/select2/), which will give a limited number of items in the search box, and also narrow searches down by what is typed in the box.
A few things things need to be coded:
A javascript file:
$(document).ready(function(){
$('.select2thing').select2({
minimumInputLength:1
,width: "100%"
,ajax: {
url: <<path>> + "entity/json"
,dataType: 'jsonp'
,quitMillis: 100
,data: function (term, page) {
return {
q: term, // search term
limit: 20,
page: page
};
}
,results: function (data, page) {
var more = (page * 20) < data.total;
return { results: data.objects, more: more };
}
}
});
}
A jsonAction in the controller:
/**
* Lists all Thing entities return in json format
*
*/
public function jsonAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$rep = $em->getRepository('yourBundle:Thing');
$qb = $rep->createQueryBuilder('e');
$limit = $request->query->get('limit');
$current = $request->query->get('current');
$page=$request->query->get('page');
$queries=$request->query->get('q');
$qarray=explode(",", $queries);
$entities=$rep->getJSON($qarray, $page, $limit);
$total=$rep->getJSONCount($qarray);
$callback=$request->query->get('callback');
return $this->render('yourBundle:Thing:json.html.twig'
, array(
'entities' => $entities
,'callback' => $callback
,'total' => $total
)
);
}
A twig template (json.html.twig, possibly customized to display more)
{{callback}}(
{ "objects" :
[
{% for entity in entities %}
{ "id": "{{entity.id}}", "text": "{{entity}}""}
{% if not loop.last %},{% endif %}
{% endfor %}
],
"total": {{total}}
}
)
A transformer:
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use yourBundle\Entity\Thing;
class ThingTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $em;
/**
* #param ObjectManager $em
*/
public function __construct(ObjectManager $em)
{
$this->em = $em;
}
/**
* Transforms an object (thing) to a string (id).
*
* #param Issue|null $thing
* #return string
*/
public function transform($thing)
{
if (null === $thing) {return "";}
if (is_object($thing) && "Doctrine\ORM\PersistentCollection"==get_class($thing)){
$entity->map(function ($ob){return $ob->getId();});
return implode(",",$thing->toArray());
}
return $thing;
}
/**
* Transforms a string (id) to an object (thing).
*
* #param string $id
* #return Issue|null
* #throws TransformationFailedException if object (thing) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
//if (is_array($id)){
$qb=$this->em
->getRepository('yourBundle:Thing')
->createQueryBuilder('t');
$thing=$qb->andWhere($qb->expr()->in('t.id', $id))->getQuery()->getResult();
if (null === $entity) {
throw new TransformationFailedException(sprintf(
'A thing with id "%s" does not exist!',
$id
));
}
return $thing;
}
}
Your Controller using the select2 control will have to pass the 'em' to the form builder:
$editForm = $this->createForm(new ThingType()
,$entity
,array(
'attr' => array(
'securitycontext' => $sc
,'em' => $this->getDoctrine()
->getEntityManager()
)
)
);
And in your form type:
if (isset($options['attr']['em'])){ $em = $options['attr']['em'];} else {$em=null;}
$transformer = new ThingTransformer($em);
$builder->add(
$builder->create('thing'
,'hidden'
,array(
'by_reference' => false
,'required' => false
,'attr' => array(
'class' => 'select2thing'
)
)
)
->prependNormTransformer($transformer)
);
You can try to change the hydration mode, using array consumes less memory than creating objects.
Other way you can achieve this is using iterations to avoid memory problems:
Finally I think you can't load all without spending a lot of time and memory, so, why not make several queries to load the whole data?