I'm working on mechanism that will allow me create forms automatically from class annotations.
For example there is a class called "News" with some custom annotations.
/**
* #Admin\Form(name="news")
*/
class News
{
/**
*
* #Admin\Field(name="title", type="text")
*/
private $title;
}
My goal is to write mechanism that will check if exists class with "Form" annotation and create form based on this class fields.
Where should I put this mechanism? First I was thinking about owerwritting FormFactory but I believe there is a better place for such thing, maybe Extension?
There already is a bundle that does what you're asking for: http://knpbundles.com/FlintLabs/FormMetadataBundle
However, if you'd like to create it yourself, you should create a bundle and within it create a custom annotation driver based on the doctrine2 specs (as Symfony uses Doctrine for reading annotations)
In Symfony2, you can add functionality to existing form fields through the use of "Form Type Extensions".
To apply your extension to all field types, set the return value of the getExtendedType() method to "form", i.e:
public function getExtendedType()
{
return 'form';
}
I haven't figured out how to fetch the annotations from the form extension yet.
Related
I have model and entity in my CakePHP(latest) app that I want to extend in this way. I want to tell the cake to use ExtraStuff model class instead of app Stuff
App\Model\Table\Stuff
MyPlugin\Model\Table\ExtraStuff
I'm trying to redefine model in the main controller in beforeRender method with methods from Cake\Datasource\ModelAwareTrait
/**
* Override a existing callable to generate repositories of a given type.
*
* #param string $type The name of the repository type the factory function is for.
* #param callable $factory The factory function used to create instances.
* #return void
*/
modelFactory($type, callable $factory)
Or how I can do it? I have a lot of $this->loadModel('Stuff') calls inside the project and I need to tell the app use ExtraStuff class when I need.
Thanks.
Look this
https://book.cakephp.org/3.0/en/controllers.html#loading-additional-models
If you are using a table provider other than the built-in ORM you can link that table system into CakePHP’s controllers by connecting its factory method:
// In a controller method.
$this->modelFactory(
'ElasticIndex',
['ElasticIndexes', 'factory']
);
After registering a table factory, you can use loadModel to load instances:
// In a controller method.
$this->loadModel('Locations', 'ElasticIndex');
Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html
With Symfony 2.7, you could customize a form's name in your EntityType class with the method getName()
This is now deprecated. Is there another way to do that with Symfony 3.0 ?
I have custom prototype entry_rows for collections that I would need to use in different forms.
Since the name of the rows is based on the form's name, I would need to change the later in order to use them with a different form.
You should implements the getBlockPrefix method instead of getName as described in the migration guide here.
As example:
/**
* Returns the prefix of the template block name for this type.
*
* The block prefix defaults to the underscored short class name with
* the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
*
* #return string The prefix of the template block name
*/
public function getBlockPrefix()
{
return "form_name";
}
Hope this help
Depending on how your form is built, there is different ways to set the name of your form.
If you are creating the form through $this->createForm(CustomType::class):
$formFactory = $this->get('form.factory');
$form = $formFactory->createNamed('custom_form_name', CustomType::class);
If you are building the form from the controller directly through $this->createFormBuilder():
$formFactory = $this->get('form.factory');
$form = $formFactory->createNamedBuilder('custom_form_name', CustomType::class);
Look at the FormFactory and FormBuilder APIs for more information.
You can try it, remove prefix on field name
public function getBlockPrefix()
{
return null;
}
In Symfony2, I just try recently to think in terms of traits, to create some sort of behaviors.
Let's say I have an address attribute in an entity. I externalized attributes, getters and setters related to this in an AddressableTrait.
But what if address become an entity? I started to try to define my OneToMany relation in my trait, as if it was in a regular entity :
use Doctrine\ORM\Mapping as ORM;
class AddressableTrait {
/**
* #var
* #ORM\OneToMany(targetEntity="XXXX\GlobalBundle\Entity\Address", inversedBy="What to put here" )
*/
protected $addresses;
/**
* #return ArrayCollection
*/
public function getAddresses()
{
return $this->addresses;
}
/**
* #param ArrayCollection $addresses
*/
public function setAddresses($addresses)
{
$this->addresses = $addresses;
}
}
What to put in the inversedBy? The purpose of the trait if precisely to embed all the behavior feature, so I think that at least using traditionnal annotation/YML/XML,it's not possible to achieve.
I digged a bit into it and found this very interesting link that seems to allow you to defines relation via events, but there is still logic to add to "finish" relations.
UPDATE :
Using the above link, I managed to created dynamic ManyToMany relation. the schema update works when creating, but if I comment the dynamic relation, a schema:update --dump-sql doesn't remove it. It seems to work add-only. Any clue to force the dynamic mapping to stick to the real relations addition/removal?
Thanks a lot for your answers !
Nicolas
I encountered a problem using traits in entities. For regular database values (scalar, DateTime) traits worked fine, but when I tried to define entity relations in traits the doctrine migrations bundle would convert the property to a varchar field.
The only way I could find to fix creating proper entity relation properties was by moving them out of the trait and into the entity class itself.
I am using a plugin (in this case Authake) and I would like to override/extend some of the functionality, but I'm not sure how I would go about doing this. I've managed to figure out how to customize the view (I created a folder '/app/views/plugins/authake' but I'm wondering how to modify/override/extend the Models and Compoenents of the plugin.
I'm guessing you want to extend the functionality of a model or perhaps a behavior in the plugin?
For example, we could extended the functionality of a Sequence behavior that is part of a Sequence plugin like so:
Create a new file in app/models/behaviors and call it extended_sequence.php
In this file, we'll create an ExtendedSequenceBehavior class that extends SequenceBehavior and overrides the beforeFind method. It will end up looking something like:
<?php
/**
* Import the SequenceBehavior from the Sequence Plugin
*/
App::import('Behavior', 'Sequence.Sequence');
/**
* Extended Sequence Behavior
*/
class ExtendedSequenceBehavior extends SequenceBehavior
{
/**
* Overrides the beforeFind function
*/
public function beforeFind(&$model, $queryData)
{
/**
* Do something different here such as modify the query data
*/
/**
* You could still call the original function as well
*/
parent::beforeFind(&$model, $queryData);
}
}
?>
Note, that we have to import the Sequence behavior using Cake's App::import before we define the ExtendedBehavior class.
Update your model to use the extended class:
var $actsAs = array('ExtendedSequence');