Working with Symfony2 and trying to figure out how best to incorporate Vendor libraries. Calling vendor library methods is very easy using the routing configs: use the appropriate namespace, generate the class name, the method to call, and the arguments from a couple path components and query vars, voila, instant integration.
I'm having trouble with forms, though. My goal seems like it should be very easy. I want to make forms in Symfony2 from vendor classes. As a specific example, consider the google-api-php-client. It seems like ->createForm() would work best, because it bridges to the ORM and validation so nicely. However, it relies on a a MyBundle\Form\ThingType file and class. To create this class, I need an entity within my bundle. I can't (or haven't been able to figure out how to) just use existing "Entities" from vendor libraries. Creating the "Entity" in the Symfony nomenclature when a "Model" already exists in the API lingo seems to be inflexible and very un-D.R.Y.
The other method I've gotten to work is using
$formBuilder = $this->createFormBuilder(new GoogleApi\Contrib\Event);
then
foreach(get_object_vars($event) as $prop) { $formBuilder->add($prop); }
but this does not utilize the seemingly ready-made bridge between the API documentation and the built-in validation tools, and it also means each individual data type is going to have to be declared individual or array to decide whether to include a collection of class-based forms or a single, class-based form.
In short, I want to use the properties and dataType information available in the API, and, if necessary, the Resource representations like this one to create a simple function (like my call function) for creating nested, self-validating forms for the classes in the Google API. I want to accomplish this without creating a bunch of "Entities" and "FormTypes" that simply rewrite what is already written in the library.
Did the vendor library not have any installation details? You should generally calls vendor things from the controller using service calls like $this->get('vendor.name.form_object') rather than calling the class at the service will include any needed dependencies.
Also the entity that you would create in your bundle would only be the basic entity that would then extend their premade abstract classes. For example (taken from https://github.com/FriendsOfSymfony/FOSUserBundle)
<?php
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
If you want to add any of your own entity items then you can do it in this, but still keep the methods and properties from the base entity/model.
Related
I'm looking into using DataFactory's in Codeception for seeding of data, and for use in our acceptance tests. In the documentation there's mention of 2 approaches, one using the helper file and one using factories files.
We load both options using this snippet from our acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- Db
- WebDriver
- \Helper\Acceptance
- Doctrine2:
connection_callback: getEntityManager
- DataFactory:
factories: tests/_support/factories
depends: Doctrine2
- \Helper\Factory
Both of the options seem to load correctly. As per the documentation I can then define factories like this, which will allow interaction with Doctrine.
// tests/_support/Helper/Factory.php
class Factory extends Module
{
/**
* #param array $settings
* #throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
* #throws \Codeception\Exception\ModuleException
*/
public function _beforeSuite($settings = [])
{
/** #var Module\DataFactory $factory */
$factory = $this->getModule('DataFactory');
/** #var EntityManager $em */
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(User::class,[
// generate random user name
'name' => Faker::name(),
]);
parent::_beforeSuite($settings);
}
}
As per the other option, I can also create factories by loading all files from within tests/_support/factories, such as below:
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
/** #var \League\FactoryMuffin\FactoryMuffin $fm */
$user = $fm->create(User::class);
dd($user);
However, the seed.php version cannot seem to share the Factory, and errors with:
The model definition 'User' is undefined.
I wondered if maybe this could be solved by moving the Factory.php logic into the initialize() method but this seems to be called before FactoryMuffin has been initiliazed.
The documentation for this with codeception seems a bit sparse, and the FactoryMuffin docs, while better, don't cover Codeception integration. Just trying to work out if i'm missing something, or I just need to repeat the code in each place if I want to use both files/methods.
This is an old question and technology moves fast so the documentation has likely changed since this was originally asked but I'll make an attempt in case anyone else stumbles across it like I did.
You're using the DataFactory module which is great as it comes with the integration for Codeception out of the box. The two methods you've described are actually ways of integrating DataFactory with your data. By creating factory files, you've given DataFactory a means of generating data. But what if you have some data already in the database that you'd like to use in your tests as well? That's where you would use the Helper class. According to the DataFactory Module docs:
In cases you want to use data from database inside your factory definitions you can define them in Helper. For instance, if you use Doctrine, this allows you to access EntityManager inside a definition.
As for your issue of seed.php not finding the User model, you need to specify it according to the definition given in your factory. For example, if your factory file looks similar to this
<?php
use League\FactoryMuffin\Faker\Facade as Faker;
$fm->define('app\models\User')->setDefinitions([
'name' => Faker::name(),
... // the rest of your properties here
]);
Then seed.php would look like
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
$user = $fm->create('app\models\User');
Once you have the DataFactory module installed and configured, you can simply call it within the appropriate testing suite via have, haveMultiple, or make. See the Codeception Docs
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
I have created several modular applications, which shared Bundles. One of these bundles responsible for handling all entity/repository related logic (lets call it MyBundle).
This MyBundle contains several custom hydrators. How can I register these hydrators in the bundle given that the doctrine configuration is contained in the specific applications and I don't wish to have to register the bundle hydrators in their config.yml files?
I've tried creating a CompilerPass in my bundle, then calling the addCustomHydrationMode on the doctrine orm configuration service but this doesn't work.
I also tried creating a 'HydrationManager' class, injecting the entity manager, then calling an addHydrator method on the manager in the compiler pass which in turn calls getConfiguration()->addCustomHydrationMode(...) but this also did not work
I was looking for a similar solution, and stumbled upon this example:
https://github.com/vivait/user-bundle/blob/master/src/Vivait/UserBundle/DependencyInjection/DoctrineCompilerPass.php
<?php
namespace Vivait\UserBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class DoctrineCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*
* #api
*/
public function process(ContainerBuilder $container)
{
$ormConfigDef = $container->getDefinition('doctrine.orm.configuration');
$ormConfigDef->addMethodCall(
'addCustomHydrationMode',
['UserCustomerHydrator', 'Vivait\UserBundle\Adapter\Hydrator\CustomerHydrator']
);
}
}
I am novice Laravel developer and I am trying to understand and apply the SOLID principles like a good programmer. So I recently learnt and applied the repository pattern in laravel.
To do this, I created a directory archive and loaded it with psr-4 like so:
"Archive\\": "archive/"
Then I created a folder called Repositories and another in it called Contracts. Now in the Contracts folder I have interfaces like UserRepositoryInterface and ServicesRepositoryInterface and so on, and outside in the Repositories folder, I have implementation like DbUserRepository and DbServiceRepository and so on.
I am using a service provider called DataServiceProvider where I bind these like so:
$this->app->bind(UserRepositoryInterface::class, DbUserRepository::class);
$this->app->bind(ServiceRepositoryInterface::class, DbServiceRepository::class);
So this way I can inject the Contacts like UserRepositoryInterface and ServiceRepositoryInterface in my controllers and Laravel automatically resolves my dependencies out of the IoC container. So if in the future I need a FileUserRepository, I just need to create that class and change the binding in my service provider and nothing will break in my controllers.
This is what I have learnt from Taylor and Jeffrey. But now I am trying to use a package https://github.com/andersao/l5-repository for my project.
According to this, I will extend my DbUserRepository with the BaseRepository which comes with it like so:
namespace App;
use Prettus\Repository\Eloquent\BaseRepository;
class UserRepository extends BaseRepository {
/**
* Specify Model class name
*
* #return string
*/
function model()
{
return "App\\Post";
}
}
Now in this I am obviously reusing all the code and power that comes with the BaseRepository like all(), panginate($limit = null, $columns = ['*']), find($id) and so on but now am I breaking the Inversion of Control Principle because now I will have to inject concrete implementations into my controller?
I am still a novice developer and trying to understand all this and may have gone wrong somewhere in the question when explaining things. What is the best way to go around using the package while also maintaining a loose coupling in the controllers?
There is no reason why you still can't implement your interface:
namespace App;
use Prettus\Repository\Eloquent\BaseRepository;
class DbUserRepository extends BaseRepository implements UserRepositoryInterface {
/**
* Specify Model class name
*
* #return string
*/
function model()
{
return "App\\Post";
}
}
However, you are now faced with a problem; if you swap out your implementation, there is nothing in your UserRepositoryInterface to say that the BaseRepository methods must also be implemented. If you take a look at the BaseRepository class, you should see that it implements two interfaces of it's own: RepositoryInterface and RepositoryCriteriaInterface and 'luckily' php allows for multiple interface inheritence which means you can extend your UserRepositoryInterface as follows:
interface UserRepositoryInterface extends RepositoryInterface, RepositoryCriteriaInterface {
// Declare UserRepositoryInterface methods
}
and you can then bind and use your interface as normal:
$this->app->bind(UserRepositoryInterface::class, DbUserRepository::class);
I am using a framework that allows adding new components to the framework's base class. Is there any way to document these new methods without changing the frameworks files so I can click through to the method in my IDE.
I highly recommend against trying to inject a subclass. If the framework instantiates the class you're extending directly, you'll need to find a way to get it to use your subclass instead.
NetBeans and PhpStorm (and probably many others) will combine elements from multiple definitions of the same class/interface. This allows you to add properties and methods to any existing class without modifying the original source.
/**
* Framework controller base class.
* Provides helpers via __call().
*/
class Framework_Controller { ... }
Now create a file in your code base that you never require containing the same class definition. Your IDE should still parse it and merge its elements with the class above:
if (false) { // Safety first!
/**
* ACME Co. controller base class.
*
* #method ACME_Model_User getUser Load user via authentication helper
*/
class Framework_Controller { /* nothing to add */ }
}
You can try using an interface and declaring the methods normally instead of with #method. That is what we did with Zend_View since it's already an interface. I haven't tried mixing class with interface to see if PhpStorm likes it.
It depends mainly on IDE you are using. I think you should extend base class and add there some new methods/properties with phpdoc comments. Of course changing framework's files is no solution as you already mentioned
You could extend the original class, redefine the function, with new doc, and call the parent function.
e.g.
class newClass extends originalClass
{
/**
* New PHPDoc
*/
function functionName($a)
{
return parent::functionName($a);
}
}
Try to use #see or inline #link directives.