Extending Doctrine entity in another Symfony bundle - php

I have three bundles:
MainBundle - holds entities and all generic functionality
BundleA & BundleB which will extend Entities in MainBundle and each will also implement interface in that bundle.
With this solution i want to keep MainBundle unaware on other bundles and only have default functionality that Entity should have in it. And other two bundles are aware of MainBundle (but unaware of each other) and have some extend functionality.
Example entity in MainBundle:
<?php
namespace Random\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Person
{
protected $id;
protected $first_name;
protected $last_name;
public function getId()
{
return $this->id;
}
public function getFirstName()
{
return $this->first_name;;
}
public function getLastName()
{
return $this->last_name;;
}
}
Example class in BundleA extending Entity in MainBundle:
<?php
namespace Random\BundleA\Model;
use Random\MainBundle\Entity\Person as BasePerson;
use Random\BundleA\Model\PersonInterface;
class Person extends BasePerson implements PersonInterface
{
public function foo()
{
$data = array(
'object_id' = $this->getId(),
'name' = $this->getFirstName(),
'extra' = 'foo'
);
return json_encode($data);
}
}
Example class in BundleB extending Entity in MainBundle:
<?php
namespace Random\BundleB\Model;
use Random\MainBundle\Entity\Person as BasePerson;
use Random\BundleB\Model\PersonInterface;
class Person extends BasePerson implements PersonInterface
{
public function bar()
{
$data = array(
'person_id' = $this->getId(),
'last_name' = $this->getLastName(),
'random' = 'bar'
);
return $data;
}
}
The problem with this setup is that Symfony/Doctrine expects Random\MainBundle\Entity\Person to be "mapped superclass" which i want it not to be.
EDIT:
In Doctrine1 i was able to extend any Doctrine_Record without having to define simple, concrete or column aggregation inheritance:
<?php
class Person extends BasePerson
{
// some functionality
}
class TestPerson extends Person
{
// additional functionality
}
And in controller i was able to simply:
<?php
$person = new TestPerson();
$person->save();
and TestPerson (which was actually Person) was saved into database (which i was later able to access using PersonTable).
Any ideas?

Related

Is there a way to decouple Laravel Eloquent models from Service or Controller level?

There is a simple Laravel Eloquent Model below:
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
}
and it's normal to use repository pattern to work with model, like:
use Product;
class ProductRepository implement ProductRepositoryInterface
{
public function __construct(Product $model)
{
$this->model = $model;
}
public function findById($id)
{
return $this->model->find($id);
}
...
}
The controller use the repository to get Prodcut data:
class ProductController extends Controller
{
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getSomeInfoOfProduct($id)
{
$product = $this->productRepository->findById($id);
return [
'name' => $product->name,
'alias' => $product->alias,
'amount' => $product->amount,
];
}
}
In the method getSomeInfoOfProduct, when I am deciding what kind of information should I return, I don't know there are how many properties the $product object has until I look at the schema of table products or migration files.
It's look like that the controller is tightly coupled with Eloquent models and the database. If one day, I store the raw data of products in Redis or other places, I still need to create a Eloquent model object, and fill in the object with the data from Redis.
So I am considering to create a pure data object to replace the Eloquent Model object, like below:
class ProductDataObject
{
private $name;
private $alias;
private $amount;
private $anyOtherElse;
public function getName() {
return $this->name;
}
....
}
and let the repository return this object:
use Product;
use ProductDataObject;
class ProductRepository implement ProductRepositoryInterface
{
public function __construct(Product $model)
{
$this->model = $model;
}
public function findById($id)
{
$result = $this->model->find($id);
// use some way to fill properties of the object
return new ProductDataObject(...);
}
...
}
In the controller or service level, I can just look at ProductDataObject to get all information I need. And it also looks like easier to change data storage without affecting the controllers and services.
Does this way make sense?
I think what you're looking for is the Factory Pattern. You're kind of on the right track already. Basically you have a middle-man class that your Controller or Repository basically asks to supply them with the appropriate Model. Through either parsing conditions or a config file using .envs, it figures out which one to serve up, so long as anything it returns all implements the same Interface.

How to dry-up similar Doctrine functions from two Entity Repositories?

I'm using Symfony 4.
namespace App\Repository;
use ...
class CountryRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Country::class);
}
...
public function deleteMultipleByIds(array $ids): void
{
$builder = $this->createQueryBuilder('l');
$builder->delete()
->where('l.id IN(:ids)')
->setParameter(
':ids',
$ids,
Connection::PARAM_INT_ARRAY
);
$query = $builder->getQuery();
$query->execute();
}
Same method exists in CountryI18nRepository class.
I'd want there to be just one function like that, which will just use correct Entity (Country v CountryI18n).
How and where do I create a new class? Should that class be of ServiceEntitiyRepository class or otherwise which?
If your problem is about duplication, you can make a GenericRepo (not necessarily a doctrine repository; please choose a better name) that you can inject and use where you need.
Something like
class GenericRepo
{
public function deleteMultipleByIds(QueryBuilder $qb, string $rootAlias, array $ids): void
{
$qb->delete()
->where(sprintf('%s.id IN(:ids)', $rootAlias))
->setParameter(':ids', $ids, Connection::PARAM_INT_ARRAY);
$qb->getQuery()->execute();
}
}
And in your, for instance CountryI18nRepository
class CountryI18nRepository
{
private $genericRepo;
public function __construct(GenericRepo $genericRepo)
{
$this->genericRepo = $genericRepo;
}
public function deleteMultipleByIds(array $ids): void
{
$builder = $this->createQueryBuilder('l');
$this-> genericRepo->deleteMultipleByIds($builder, 'l', $ids);
}
}
You could also extend from GenericRepo but, since PHP only supports single inheritance, is better (at least in my opinion) to use the composition as shown above.
Disclaimer
I didn't tested this code so it is possible that some adjustment will needed. Concepts shown btw are valid.
create an abstract repository with the deleteMultipleByIds like :
abstract class BaseCountryRepository extends ServiceEntityRepository
and extend it instead of ServiceEntityRepository in the other CountryRepositories
class CountryRepository extends BaseCountryRepository
class CountryI18nRepository extends BaseCountryRepository
you can remove the deleteMultipleByIds definition from these classes

Zend Framework 2 - Showing the content of a database

I'm making some kind of market site with Zend Framework 2. The home got a slider showing all the products (realized with CSS3 keyframes) and some text. Both the sliding pictures and the text are read from a MySQL database. But as result, i get no output but also no errors. The slider gets as many pictures as database rows, but still no content is echoed; plus if i try to change things (like db credentials or getter functions in model) it throws errors as expected, so it clearly reads the db and the problem is elsewhere.
Db for text has 3 fields:
id
name
text
Model for text (Home.php; there's an HomeInterface.php defining all the functions)
<?php
namespace Site\Model;
class Home implements HomeInterface {
protected $id;
protected $name;
protected $text;
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function getText() {
return $this->text;
}
}
?>
Mapper for text
<?php
namespace Site\Mapper;
use Site\Model\HomeInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Stdlib\Hydrator\HydratorInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\Sql\Sql;
use Zend\Stdlib\Hydrator\ClassMethods;
class TextMapper implements TextMapperInterface {
protected $homePrototype;
protected $adapter;
protected $hydrator;
public function __construct(AdapterInterface $adapter, HomeInterface $homePrototype, HydratorInterface $hydrator) {
$this->adapter = $adapter;
$this->homePrototype = $homePrototype;
$this->hydrator = $hydrator;
}
public function find($name) {
$sql = new Sql($this->adapter);
$select = $sql->select();
$select->from("mono");
$select->where(array("name = ?" => $name));
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult() && $result->getAffectedRows()) {
return $this->hydrator->hydrate($result->current(), $this->homePrototype);
}
throw new \InvalidArgumentException("{$name} non esiste.");
}
}
?>
Mapper for text has a factory, since it has dependencies:
<?php
namespace Site\Factory;
use Site\Mapper\TextMapper;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Site\Model\Home;
use Zend\Stdlib\Hydrator\ClassMethods;
class TextMapperFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return new TextMapper($serviceLocator->get("Zend\Db\Adapter\Adapter"), new Home(), new ClassMethods(false));
}
}
?>
Service for text:
<?php
namespace Site\Service;
use Site\Model\Home;
use Site\Model\HomeInterface;
use Site\Mapper\TextMapperInterface;
class HomeService implements HomeServiceInterface {
protected $textMapper;
public function __construct (TextMapperInterface $textMapper) {
$this->textMapper = $textMapper;
}
public function findText($name) {
return $this->textMapper->find($name);
}
}
?>
Factory for this service:
<?php
namespace Site\Factory;
use Site\Service\HomeService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class HomeServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
$textMapper = $serviceLocator->get("Site\Mapper\TextMapperInterface");
return new HomeService($textMapper);
}
}
?>
Controller
<?php
namespace Site\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Site\Service\HomeServiceInterface;
use Zend\View\Model\ViewModel;
class SkeletonController extends AbstractActionController {
protected $homeService;
public function __construct(HomeServiceInterface $homeService) {
$this->homeService = $homeService;
}
public function indexAction() {
return new ViewModel(array (
"home" => $this->homeService->findText("home")
));
}
}
?>
Finally, the view:
<?php echo $this->home->getText(); ?>
Code for slider is similar and both the parts of this page are likely having the same problem. As i said, db is detected, tables and columns too, they aren't empty but nothing gets echoed. Interfaces are properly written, defining all the functions. All views are in the Site\view\Site\Skeleton folder. Any clues about where the problem is? Thank you.
Your code looks good. The only issue I can see is that you are using the ClassMethods hydrator and you have no setters on your entity.
The hydrator will use the entity API to hydrate the entity, if the setId, setName or setText are not callable then the values will not be set.
Although I recommend adding the missing methods you can also use the Zend\Stdlib\Hydrator\Reflection to set the entity properties without setters (via SPL ReflectionProperty)

Symfony2 / FOSUserBundle: Change render variables in response without altering parent class

One of my classes currently extends the BaseController on the FOSUserBundle, and returns the parent action. However, due to project spec, I shouldn't have the need to edit the parent class. Is there a way of sending additional variables, for twig to render, through the child response?
Child Class:
class ChangePasswordController extends BaseController
{
public function changePasswordAction(Request $request)
{
$response = parent::changePasswordAction($request);
return $response; // and 'myVariable' => $myVariable
}
}
Parent Class:
class ChangePasswordController extends ContainerAware
{
/**
* Change user password
*/
public function changePasswordAction(Request $request)
{
//lots of code.....
return $this->container->get('templating')
->renderResponse(
'FOSUserBundle:ChangePassword:changePassword.html.'
.$this->container->getParameter('fos_user.template.engine'),
array(
'form' => $form->createView()
//and 'myVariable' => $myVariable
)
);
}
}
So to summarise, is there a way of passing something to the parent class, without changing the parent class... whilst rendering the twig view with an additional variable.
-- Update --
Essentially I want to render a form using the FOSUserBundle changePassword action, therefore this works fine:
return $this->container
->get('templating')
->renderResponse(
'FOSUserBundle:ChangePassword:changePassword.html.'.$this->container->getParameter('fos_user.template.engine'),
array('form' => $form->createView())
);
However, I want to pass more variables to the view, just like the 'form' is passed as shown above, without altering the FosUserBundle ChangePassword Controller. Therefore I have a class which inherits the that controller, adds some additional functionality and returns the parent change password action:
class ChangePassController extends ChangePasswordController
{
public function changePasswordAction(Request $request)
{
// more code......
$response = parent::changePasswordAction($request);
return $response;
}
}
But, like with most applications, I want to add more than just the form variable to a view template. So is there a way of passing an additional variable to the view, without altering the parent controller / action? Like (but not like) pushing 'myVariable' => $myVariable to the parent changePasswordAction return statement?
There is a section in FOSUserBundle documentation that describes exactly how to do that, and from Symfony2's Cookbook, How to use Bundle Inheritance to Override parts of a Bundle.
In summary, create a Bundle class to override FOSUserBundle in src:
// src/Acme/UserBundle/AcmeUserBundle.php
<?php
namespace Acme\UserBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
Then, override the ChangePasswordController class:
use FOS\UserBundle\Controller\ChangePasswordController as BaseController;
class ChangePasswordController extends BaseController
{
public function changePasswordAction(Request $request)
{
$response = parent::changePasswordAction($request);
return $response; // and 'myVariable' => $myVariable
}
}
--UPDATE--
Ok I think I misread you question. Anyway what renderResponse() of the templating service does is essentially:
$response->setContent($this->render($view, $parameters));
You can see the Class of the templating service by running app/console container:debug which is actually the TwigEngine class.
So you can just re-invoke renderResponse() and supply you own extra parameters. eg:
return $this->container->get('templating')->renderResponse(
'FOSUserBundle:ChangePassword:changePassword.html.'.$this->container->getParameter('fos_user.template.engine'),
array(
'form' => $form->createView(),
'myVariable' => $myVariable', // There you go
),
$response // The previous response that has been rendered by the parent class, by this is not necessary
);
Think bottom up.
You can access your data without passing it through action, using Twig Extension http://symfony.com/doc/current/cookbook/templating/twig_extension.html
twig.extension.user_profile:
class: 'MyBundle\UserProfileExtension'
arguments:
- '#doctrine.orm.entity_manager'
tags:
- { name: twig.extension }
Extension class
class UserProfileExtension extends \Twig_Extension
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param UserProfileDataService $userProfileDataService
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #return array
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('get_my_custom_var', array($this, 'getMyCustomVar')),
);
}
/**
* #return array
*/
public function getMyCustomVar()
{
$var = $this->entityManager->getRepository('MyCustomRepository')->findOneBy(['id' => 1]);
return $var;
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'user_profile_extension';
}
Template usage
{dump(get_my_custom_var())}
if I am understanding your question correctly you should be able to set additional variables on the response like this:
use FOS\UserBundle\Controller\ChangePasswordController as BaseController;
class ChangePasswordController extends BaseController
{
public function changePasswordAction(Request $request)
{
$response = parent::changePasswordAction($request);
$response['myVariable'] = $myVariable;
return $response;
}
}
Hope this helps!

Zend framework 2 translator in model

How to get translator in model?
Inside view we can get translator using this code
$this->translate('Text')
Inside controller we can get translator using this code
$translator=$this->getServiceLocator()->get('translator');
$translator->translate("Text") ;
But how to get translator in model?
I'd tried so many ways to get service locator in models
2 of those
1)Using MVC events
$e=new MvcEvent();
$sm=$e->getApplication()->getServiceManager();
$this->translator = $sm->get('translator');
if i pring $sm it is showing null. but it works fine in Model.php onBootstrap
2)Created one model which implements ServiceLocatorAwareInterface
SomeModel.php
<?php
namespace Web\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class SomeModel implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
and used that inside my model
$sl = new SomeModel();
$sm=$sl->getServiceManager();
var_dump($sm); exit;
$this->translator = $sm->get('translator');
this is also printing null.
If you don't need the servicemanager instance in your model, simply inject translator instance to it.
For example:
// Your model's constructor
class MyModel {
// Use the trait if your php version >= 5.4.0
use \Zend\I18n\Translator\TranslatorAwareTrait;
public function __construct( $translator )
{
$this->setTranslator( $translator );
}
public function modelMethodWhichNeedsToUseTranslator()
{
// ...
$text = $this->getTranslator()->translate('lorem ipsum');
// ...
}
}
When you creating your model first time on service or controller level
class someClass implements ServiceLocatorAwareInterface {
public function theMethodWhichCreatesYourModelInstance()
{
// ...
$sm = $this->getServiceLocator();
$model = new \Namespace\MyModel( $sm->get('translator') )
// ...
}
}
If you need to instantiate your model (new MyModel();) on multiple methods/classes, consider to writing a factory for it.
Here is a nice article about Dependency Injection and PHP by Ralph Schindler for more detailed comments about this approach.
For your Model class to be ServiceLocatorAware, you not only need to implement the interface, you also need to make your model a service of the service manager, and fetch the model from there.
Add your model to the service manager, since it doesn't appear to need any constructor params, it's invokable, so you can add it to the invokables array in service manager config. You can do that by using the getServiceConfig() method in your Module class...
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'SomeModel' => 'Fully\Qualified\ClassName\To\SomeModel',
),
);
}
}
Then, instead of calling the new keyword to create your model instance, you fetch it from the service manager, for instance, by calling the getServiceLocator() method in a controller action...
public function fooAction()
{
$sm = $this->getServiceLocator();
$model = $sm->get('SomeModel');
}
When your model is fetched from the service manager, a service initializer will look to see if it implements the ServiceLocatorAwareInterface and automatically call setServiceLocator() if it does, passing it an instance of the service manager.

Categories