Skip to content

Instantly share code, notes, and snippets.

@duayres
Last active November 29, 2017 12:33
Show Gist options
  • Save duayres/5fbc54098dcf763f647ab7587cd71a0d to your computer and use it in GitHub Desktop.
Save duayres/5fbc54098dcf763f647ab7587cd71a0d to your computer and use it in GitHub Desktop.
EntityType symfony equivalent for wouterj\eloquent-bundle

EloquentType for eloquent on symfony2 (v0.0.0.1)

Installation

Step 1: Download the Bundle

Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:

$ composer require wouterj/eloquent-bundle "^0.5"

OBS: This command requires you to have Composer installed globally

Step 2: Enable the Bundle

Then, enable the bundle by adding it to the list of registered bundles in the app/AppKernel.php file of your project:

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...

            new WouterJ\EloquentBundle\WouterJEloquentBundle(),
        );

        // ...
    }

    // ...
}

...then configure your database in config.yml (wouterj_eloquent:) as (also) explained on wouter bundle setup.

Step 3: Activate eloquent models, aliases and create the models

wouterj_eloquent:
    # ...
    eloquent: true
    aliases: true

Then create your models in src/Model/, but, extending from CustomModel.php (on this gist).

Warning: Remeber to put a __toString in every relational entity you have. Ex:

//Author.php
    function __toString() {
        return $this->attributes["name"];//returns the Author name
    }

Step 4: Put the files on the paths and build your forms

  • Put EloquentType.php inside of /vendor/wouterj/eloquent-bundle/src/Form/Type
  • Put EloquentChoiceLoader.php inside of /vendor/wouterj/eloquent-bundle/src/Form/ChoiceList
  • Put EloquentTransformer.php inside of /vendor/wouterj/eloquent-bundle/src/Form/DataTransformer

PS: The EloquentTransformer here is just a dumb entity, i'll plan to use it later to REALLY implement the multiple select... PS2: I'll use a Book (with Authors relation as example)..

Step 5: Finally using it xD

Now you can create your formBuilder:

//MyCustomType.php
// ...
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
                ->add('nome', TextType::class, array('attr' => array('class' => 'form-control') ) )
                ->add('author', EloquentType::class, array( "class" =>  'AppBundle\Model\Author'));

// ...

And then intercept and save the data in the database :)

//...YourController.php
//...
    public function updateAction(Request $request, $id)
    {
        $entity = Book::find($id);


        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Book entity.');
        }

        $editForm = $form = $this->createForm(new MyCustomType(), $entity, array(
            'action' => $this->generateUrl('book_update', array('id' => $entity->id)),
            'method' => 'PUT',
        ));
        $editForm->handleRequest($request);

        if ($editForm->isValid()) {
            $entity->fixRelations();
            $entity->save();

            return $this->redirect($this->generateUrl('book_update', array('id' => $id))); //@todo required due to problems reselecting after submit
        }

        return array(
            'entity'      => $entity, 
            'edit_form'   => $editForm->createView(),
        );
    }
 //...

Disclaimer

The source-code on this gist is based on propelORM bundle ModelType.php and is VERY VEEEERY poor in quality due to its own version 0.0.0.1

Again: the src here STILL stinks, lacks objectivity and standards/patterns... well, i've done everything in about 5, 6 hours

Multiple selects does't work yet, custom property (key) is 60% done (but do not works), and custom query is 15% done

Check here later, i'll will improve it and, later, will do an PR to wouterj eloquent-bundle project :)

OBS: Tested on symfony v2.8 only

<?php
namespace AppBundle\Model;
use Illuminate\Database\Eloquent\Model;
class CustomModel extends Model
{
public function fixRelations(){
foreach ($this->attributes as $attribute => $value) {
if (substr($attribute, -3)=="_id"){//maps ex. 'author' to correct field 'author_id'
$this->attributes[$attribute]=$this->attributes[substr($attribute, 0, -3)];
unset($this->attributes[substr($attribute, 0, -3)]);
}
}
}
///the src code bellow isn't really necessary for this gist ou are free to remove...
public static function getFields() {
$clazz=get_called_class();
$resultSet = $clazz::where('id',">", 1)->take(1)->get();
if (!$resultSet->isEmpty()){
$attributes = array_keys($resultSet->toArray()[0]);
} else { //nao existem dados persistidos no banco, vai precisar dar describe
$attributes = Schema::getColumnListing(with(new $clazz())->getTable());
}
return $attributes;
}
/**
* Return an object based on table structure
* @return self::Object
*/
public static function dynamicInstance() {
$clazz=get_called_class();
$resultSet = $clazz::where('id',">", 1)->take(1)->get();
if (!$resultSet->isEmpty()){
$attributes = array_keys($resultSet->toArray()[0]);
} else { //nao existem dados persistidos no banco, vai precisar dar describe
$attributes = Schema::getColumnListing(with(new $clazz())->getTable());
}
$instance = new $clazz;
foreach ($attributes as $property) {
$instance->{$property} = NULL;
}
return($instance);
/*//dont work because stdClassObj is not the correct class
$attributes = array_combine($attributes,array_pad(array(),count($attributes),NULL)); //array_combine($attributes,range(1,count($attributes)));
return (object)($attributes);*/
}
}
<?php
/**
* This file is part of the wouterj-eloquentbundle package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace WouterJ\EloquentBundle\Form\ChoiceList;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
* @author Moritz Schroeder <moritz.schroeder@molabs.de>
* @author Eduardo Ayres <eduardo@eduardoayres.com>
*/
class EloquentChoiceLoader implements ChoiceLoaderInterface
{
/**
* @var ChoiceListFactoryInterface
*/
protected $factory;
/**
* @var string
*/
protected $class;
/**
* @var Query\Builder
*/
protected $query;
/**
* The fields of which the identifier of the underlying class consists
*
* This property should only be accessed through identifier.
*
* @var array
*/
protected $identifier = array();
/**
* Whether to use the identifier for index generation.
*
* @var bool
*/
protected $identifierAsIndex = true;
/**
* @var ChoiceListInterface
*/
protected $choiceList;
/**
* EloquentChoiceLoader constructor.
*
* @param ChoiceListFactoryInterface $factory
* @param string $class
*/
public function __construct(ChoiceListFactoryInterface $factory, $class, $queryObject, $useAsIdentifier = null)
{
$this->factory = $factory;
$this->class = $class;
$this->query = $queryObject;
$this->identifier = $queryObject->getKeyName();
//@fixme well, i'll need to put it to work
/*if ($useAsIdentifier) {
$this->identifier = array($this->query->getTableMap()->getColumn($useAsIdentifier));
} else {
$this->identifier = $this->query->getTableMap()->getPrimaryKeys();
}
if (1 === count($this->identifier) && $this->isScalar(current($this->identifier))) {
$this->identifierAsIndex = true;
}*/
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if ($this->choiceList) {
return $this->choiceList;
}
$models = $this->query->all();
$this->choiceList = $this->factory->createListFromChoices($models, $value);
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
// Performance optimization
if (empty($values)) {
return array();
}
//return $this->loadChoiceList($value)->getChoicesForValues($values);
$class = get_class($this->query);
//$object = $class::where($this->identifier, $values[0])->get();
return array($values[0]);
// The commented source bellow is from original propelORM ModelChoiceList, its here only for later usage xD
// Optimize performance in case we have a single-field identifier
/*if (!$this->choiceList && $this->identifierAsIndex) {
$phpName = camel_case($this->identifier);
$query = clone $this->query;
$unorderedObjects = $query->filterBy($phpName, $values);
$objectsById = array();
$objects = array();
// Maintain order and indices from the given $values
foreach ($unorderedObjects as $object) {
$objectsById[(string) current($this->getIdentifierValues($object))] = $object;
}
foreach ($values as $i => $id) {
if (isset($objectsById[$id])) {
$objects[$i] = $objectsById[$id];
}
}
return $objects;
}
return $this->loadChoiceList($value)->getChoicesForValues($values);*/
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
// Performance optimization
if (empty($choices)) {
return array();
}
if (!$this->choiceList && $this->identifierAsIndex) {
$values = array();
// Maintain order and indices of the given objects
foreach ($choices as $i => $object) {
if ($object instanceof BelongsTo) {
// Make sure to convert to the right format
//$values[$i] = (string) current($this->getIdentifierValues($object));
$identifier = ($this->identifier=='id') ? 'key' : $this->identifier;
// @todo bellow
// @fixme getResults() queries the database again, idk where I get the data :/
$values[$i] = $object->getResults()->{'get'.camel_case($identifier)}();
}
}
return $values;
}
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* DUMB METHOD... here for later use, when i'll implement some mutiple choice functionality
* Returns the values of the identifier fields of a model.
*
* Propel must know about this model, that is, the model must already
* be persisted or added to the idmodel map before. Otherwise an
* exception is thrown.
*
* @param object $model The model for which to get the identifier
*
* @return array
*/
private function getIdentifierValues($model)
{
if (!$model instanceof $this->class) {
return array($this->identifier);
}
return array($this->identifier);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace WouterJ\EloquentBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* CollectionToArrayTransformer class.
*
* @author William Durand <william.durand1@gmail.com>
* @author Pierre-Yves Lebecq <py.lebecq@gmail.com>
* @author Eduardo Ayres <eduardo@eduardoayres.com>
*/
class EloquentTransformer implements DataTransformerInterface
{
public function transform($collection)
{
$collection = $collection->getResults();
if (null === $collection) {
return array();
}
if (!$collection instanceof \Illuminate\Database\Model) {
throw new TransformationFailedException('Expected a \Illuminate\Database\Model\nop.');
}
return $collection->toArray();
}
public function reverseTransform($array)
{
// notImpl yet... sorry.. maybe on v0.2? xD
/*$collection = new ObjectCollection();
if ('' === $array || null === $array) {
return $collection;
}
if (!is_array($array)) {
throw new TransformationFailedException('Expected an array.');
}
$collection->setData($array);
return $collection;*/
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace WouterJ\EloquentBundle\Form\Type;
use WouterJ\EloquentBundle\Form\DataTransformer\EloquentTransformer;
use WouterJ\EloquentBundle\Form\ChoiceList\EloquentChoiceLoader;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
/**
* ModelType class.
*
* @author William Durand <william.durand1@gmail.com>
* @author Toni Uebernickel <tuebernickel@gmail.com>
* @author Eduardo Ayres <eduardo@eduardoayres.com>
*
* Example using the preferred_choices option.
*
* <code>
* public function buildForm(FormBuilderInterface $builder, array $options)
* {
* $builder
* ->add('product', 'model', array(
* 'class' => 'Model\Product',
* 'query' => {{notImpl... internally using default all() query}},
* 'preferred_choices' => DB::table('product')
* ->where('active', false)->get();
* ,
* ))
* ;
* }
* </code>
*/
class EloquentType extends AbstractType
{
/**
* @var ChoiceListFactoryInterface
*/
private $choiceListFactory;
/**
* ModelType constructor.
*
* @param PropertyAccessorInterface|null $propertyAccessor
* @param ChoiceListFactoryInterface|null $choiceListFactory
*/
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(
new DefaultChoiceListFactory(),
$propertyAccessor
);
}
/**
* Creates the label for a choice.
*
* For backwards compatibility, objects are cast to strings by default.
*
* @param object $choice The object.
*
* @return string The string representation of the object.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceLabel($choice)
{
return (string) $choice;
}
/**
* Creates the field name for a choice.
*
* This method is used to generate field names if the underlying object has
* a single-column integer ID. In that case, the value of the field is
* the ID of the object. That ID is also used as field name.
*
* @param object $choice The object.
* @param int|string $key The choice key.
* @param string $value The choice value. Corresponds to the object's
* ID here.
*
* @return string The field name.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceName($choice, $key, $value)
{
return str_replace('-', '_', (string) $value);
}
/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['multiple']) {
$builder
->addViewTransformer(new EloquentTransformer(), true)
;
}
}
/**
* {@inheritDoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$choiceLoader = function (Options $options) {
// Unless the choices are given explicitly, load them on demand
/*if (null === $options['choices']) {
$propelChoiceLoader = new EloquentChoiceLoader(
$this->choiceListFactory,
$options['class'],
$options['query'],
$options['index_property']
);
return $propelChoiceLoader;
}*/
return new EloquentChoiceLoader(
$this->choiceListFactory,
$options['class'],
$options['query'],
$options['index_property']
);
return null;
};
$choiceName = function (Options $options) {
/** @var Query\Builder $query */
/*$query = $options['query'];
if ($options['index_property']) {
$identifier = array($query->getTableMap()->getColumn($options['index_property']));
} else {
$identifier = $query->getTableMap()->getPrimaryKeys();
}*/
/** @var ColumnMap $firstIdentifier */
/*$firstIdentifier = current($identifier);
if (count($identifier) === 1 && $firstIdentifier->getPdoType() === \PDO::PARAM_INT) {
return array(__CLASS__, 'createChoiceName');
}*/
return null;
};
$choiceValue = function (Options $options) {
/** @var Query\Builder $query */
$query = $options['query'];
if ($options['index_property']) {
//$identifier = array($query->getTableMap()->getColumn($options['index_property']));
$identifier = $query->getKeyName();
} else {
$identifier = $query->getKeyName();
}
return function($object) use ($identifier,$query) {
if ($object && !$object instanceof BelongsTo) {
//@TODO get by phpname (camelCased)
//return call_user_func([$object, 'get' . ucfirst(camel_case($identifier))]);
return call_user_func([$object, camel_case('getKey')]);
}
return null;
};
// the commented parts from this code are from the original propel ModelType.php that i'm based on...
//..maybe it will be helpfull in future
/** @var ColumnMap $firstIdentifier *//*
$firstIdentifier = current($identifier);
if (count($identifier) === 1 && in_array($firstIdentifier->getPdoType(), [\PDO::PARAM_BOOL, \PDO::PARAM_INT, \PDO::PARAM_STR])) {
return function($object) use ($firstIdentifier) {
if ($object) {
return call_user_func([$object, 'get' . ucfirst($firstIdentifier->getPhpName())]);
}
return null;
};
}*/
/*if ($options['index_property']) {
return "nome";//$options['class']::
} else {
}
return null;*/
};
$queryNormalizer = function (Options $options, $query) {
if ($query === null) {
$queryClass = $options['class'];// . 'Query';
if (!class_exists($queryClass)) {
if (empty($options['class'])) {
throw new MissingOptionsException('The "class" parameter is empty, you should provide the model class');
}
throw new InvalidOptionsException(
sprintf(
'The query class "%s" is not found, you should provide the FQCN of the model class',
$queryClass
)
);
}
$query = new $queryClass();
}
return $query;
};
$choiceLabelNormalizer = function (Options $options, $choiceLabel) {
if ($choiceLabel === null) {
if ($options['property'] == null) {
$choiceLabel = array(__CLASS__, 'createChoiceLabel');
} else {
$valueProperty = $options['property'];
/** @var Query\Builder $query */
$query = $options['query'];
$choiceLabel = function($choice) use ($valueProperty) {
$getter = 'get'.ucfirst(camel_case($valueProperty));
if (!method_exists($choice, $getter)) {
//@TODO
$getter = 'get' . ucfirst(camel_case($query->getAttribute($valueProperty)));
}
return call_user_func([$choice, $getter]);
};
}
}
return $choiceLabel;
/*$query = $options['query'];
return $query->{$choiceLabel};*/
};
$resolver->setDefaults([
'query' => null,
'index_property' => null,
'property' => null,
'choices' => null,
'choice_loader' => $choiceLoader,
'choice_label' => null,
'choice_name' => $choiceName,
'choice_value' => $choiceValue,
'choice_translation_domain' => false,
'by_reference' => false,
]);
$resolver->setRequired(array('class'));
$resolver->setNormalizer('query', $queryNormalizer);
$resolver->setNormalizer('choice_label', $choiceLabelNormalizer);
//$resolver->setAllowedTypes('query', ['null', 'Propel\Runtime\ActiveQuery\Query\Builder']);
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'model';
}
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment