Just getting started with Symfony, so please bear with me here.
I have an Entity with a field "myField" that is stored in the database as an ENUM with values 'Y' or 'N'. (This is an old DB schema that I'm working with and trying to use symfony as an app to manipulate the data).
I want to represent "myField" with a checkbox for on or off (0 or 1). When the form is saved, how would I transform the value to the appropriate 'Y' or 'N' value before persisting it to the database?
I looked at Data Transformers, and that may be where I need to go, but it seems so silly to create a new class and file just to convert that data. This is a very simple transformation.
Maybe instead I would just change the setMyField($myField) () {} setter method on the Entity to convert the data there? And likewise, convert it back in getMyField() {}? But doesn't Doctrine also use the setter methods? If so, then when Doctrine set a value, it would be the proper value (0 or 1) from the database and wouldn't need transforming. But if the form set the value, it would need transforming.
Here's the action code:
public function newAction(Request $request)
{
$course = new Course();
$form = $this->createForm(new CourseForm(), $course);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($course);
$em->flush();
return $this->redirect($this->generateUrl('courses'));
}
return $this->render('PucsTestBundle:Course:new.html.twig', array(
'form' => $form->createView(),
));
}
I think I could convert the data myself in the controller just before I call handleRequest, but that is probably not good practice.
Additionally, I have a field in the database "days" that is just a VARCHAR that would be something like MTW for Monday, Tuesday Wednesday. I want to present a checkbox for all these values, and then post-process the form submission to convert the data to the appropriate single value.
You should use a custom Doctrine Type as defined here in the documentation
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class OldBooleanType extends Type
{
const NAME = 'OldBoolean'; // modify to match your type name
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'OldBoolean';
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === 'Y'){
return true;
}
if ($value === 'N'){
return false;
}
return null;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === true){
return 'Y';
}
if ($value === false){
return 'N';
}
return null;
}
public function getName()
{
return self::NAME;
}
}
From Symfony documentation: http://symfony.com/doc/current/cookbook/form/data_transformers.html
Data transformers are used to translate the data for a field into a format that can be displayed in a form (and back on submit).
Related
I want a login with a custom field to authenticate users into the platform.
The point is to check a field 'pw_expires_at' to \DateTime('now'), to log the user.
Here's what I did so far:
In the controller:
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$mdp)
);
$user->setPwExpiresAt(new \DateTime("now + 1 minute"));
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
In the Authenticator:
public function checkCredentials($credentials, UserInterface $user)
{
$valid = false;
$validDate = $this->checkDate($credentials, $user);
$validPassword = $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
if($validDate && $validPassword) {
$valid = true;
}
return $valid;
}
/**
* #return bool
*/
public function checkDate($credentials, UserInterface $user){
$now = new \DateTime('now');
$pwdate = new \DateTime();
$pwdate = $this->entityManager->getRepository(Users::class)->findOneBy([
'email' => $credentials['email']
]);
if ($pwdate > $now) {
return false;
}
else {
return true;
}
}
I also added the new function checkDate() in the AuthenticatorInterface.php.
The problem is : I can log in at anytime.
You are comparing (>) a user object repository->findBy(...) which returns a Users::class with a DateTime object $now = new \DateTime();.
Also the $user object entityManager reponse is most likely the same object returned by your getUsername function (the one you pass as an argument in this function) and thus can be skipped? If it is a DTO that does not contain this expired value then add it back in.
Also you are not using the credentials for anything anymore then so removed it as well.
I would change this to something like:
public function checkDate(UserInterface $user) {
$now = new \DateTime();
$pwdate = $user->getPwExpiresAt();
// we dont need the if/else as this ($pwdate > $now)
// is an expression and will already return true/false;
return $pwdate > $now;
}
Some more suggestions:
You might want to reconsider renaming the function to something more expressive like $this->hasAuthenticationExpired($user) this should give a clear indication of what the function is doing other than "checking a date (for what?!)" without reading through the function.
You can move this function to the user object like
public function hasExpired() { return $this->getPwExpiresAt() && new \DateTime() > $this->getPwExpiresAt(); }
and just call if (!$user->hasExpired()) { which is actually a preferred way for many people as this can be easily reused and accessed whenever handling the user object anywhere.
I have created a custom doctrine type as told in http://doctrine-orm.readthedocs.org/en/latest/cookbook/working-with-datetime.html
Here is the code:
<?php
namespace XXX\Bundle\XXXBundle\Doctrine\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
$value->setTimezone(new \DateTimeZone('UTC'));
$dbDate = $value->format($platform->getDateTimeFormatString());
return $dbDate;
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $val;
}
}
The problem is when I run app/console doctrine:migrations:diff it always will generate new migrations even if I have migrated, and the content is always the same. Example:
$this->addSql('ALTER TABLE Availability CHANGE start start DATETIME NOT NULL, CHANGE end end DATETIME NOT NULL, CHANGE rrule rrule LONGTEXT DEFAULT NULL, CHANGE created created DATETIME NOT NULL, CHANGE updated updated DATETIME NOT NULL');
Here is a response from Steve Müller from this bug report : http://www.doctrine-project.org/jira/browse/DBAL-1085
I think you will have to mark your custom type as requiring a SQL
comment, otherwise the schema manager cannot distinguish between
DateTime type and your custom type because both map to the same native
SQL type.
See here:
https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Types/Type.php#L327-L340
You will have to add the following to your custom type implementation:
/**
* {#inheritdoc}
*/
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
Also I think it might be required to give your custom type a distinct name like:
/**
* {#inheritdoc}
*/
public function getName()
{
return 'datetime_utc';
}
There features are implemented in doctrine >=2.3
There is a standard feature in sonata-admin-bundle to export data using exporter; But how to make export current entity AND mapped ManyToOne entity with it?
Basically what I want, is to download exactly same data as defined in ListFields.
UPD: In docs, there is only todo
UPD2: I've found one solution, but I do not think it is the best one:
/**
* Add some fields from mapped entities; the simplest way;
* #return array
*/
public function getExportFields() {
$fieldsArray = $this->getModelManager()->getExportFields($this->getClass());
//here we add some magic :)
$fieldsArray[] = 'user.superData';
$fieldsArray[] = 'user.megaData';
return $fieldsArray;
}
I created own source iterator inherited from DoctrineORMQuerySourceIterator.
If value in method getValue is array or instance of Traversable i call method getValue recursive to get value for each "Many" entity:
protected function getValue($value)
{
//if value is array or collection, creates string
if (is_array($value) or $value instanceof \Traversable) {
$result = [];
foreach ($value as $item) {
$result[] = $this->getValue($item);
}
$value = implode(',', $result);
//formated datetime output
} elseif ($value instanceof \DateTime) {
$value = $this->dateFormater->format($value);
} elseif (is_object($value)) {
$value = (string) $value;
}
return $value;
}
In your admin class you must override method getDataSourceIterator to return your own iterator.
This
$this->getModelManager()->getExportFields($this->getClass());
returns all entity items. Better practice is to create explicit list of exported items in method getExportFields()
public function getExportFields()
{
return [
$this->getTranslator()->trans('item1_label_text') => 'entityItem1',
$this->getTranslator()->trans('item2_label_text') => 'entityItem2.subItem',
//subItem after dot is specific value from related entity
....
Key in array is used for export table headers (here is traslated).
//Suppose Entity Notes has property 'creationdate' & 'getCreationDate()' method to access.
DefaultController extends Controller {
public function indexAction(){
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('Bundle:Notes');
$notes = $repository->findBy(array('userid' => $userId);
//Now I want to sort the notes array as per creation date using usort
usort($notes, array($this,"cmp"));
}
function cmp($a, $b) {
return strtotime($a->getCreationDate()) > strtotime($b->getCreationDate())? -1:1;
}
}
You can set the order in your call to the repository rather than after like so...
$notes = $repository->findBy(
array('userid' => $userId), // search criteria
array('creationdate' => 'ASC') // order criteria
);
I know you said you wanted to use usort but it seems kind of unnecessary.
Is there any way to access session variable from ClassGeneratorConfiguration in symfony1.4?
I need it because i want to set default filter field values according to session variable.
You can get it from the context (I think there is no other way) and than you can use the getFilterDefaults method to set a default value for a field or use the getFilterFormOptions to pass the user into the form as an option and implement the logic there. This is how I usally do it:
class productGeneratorConfiguration extends BaseProductGeneratorConfiguration
{
/**
* #return sfBasicSecurityUser
*/
public function getUser()
{
return sfContext::getInstance()->getUser();
}
public function getFilterFormOptions()
{
return array('user' => $this->getUser());
}
public function getFilterDefaults()
{
return array(
'some_field' => $this->getUser()->getSomething() ? 'a' : 'b';
);
}
}
You can access session variable like follows:
$varname = sfContext::getInstance->getUser()->getAttribute('your_field');