I have an Entity that pulls it's data from a REST web service. To keep thing consistent with the entities in my app that pull data from the database I have used ORM and overridden the find functions in the repository.
My problem is that ORM seems to demand a database table. When I run doctrine:schema:update it moans about needing an index for the entity then when I add one it creates me a table for the entity. I guess this will be a problem in the future as ORM will want to query the database and not the web service.
So... am I doing this wrong?
1, If I continue to use ORM how can I get it to stop needing the database table for a single entity.
2, If I forget ORM where do I put my data loading functions? Can I connect the entity to a repository without using ORM?
So... am I doing this wrong?
Yes. It doesn't make sense to use the ORM interfaces if you don't really want to use an ORM.
I think the best approach is NOT to think about implementation details at all. Introduce your own interfaces for repositories:
interface Products
{
/**
* #param string $slug
*
* #return Product[]
*/
public function findBySlug($slug);
}
interface Orders
{
/**
* #param Product $product
*
* #return Order[]
*/
public function findProductOrders(Product $product);
}
And implement them with either an ORM:
class DoctrineProducts implements Products
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function findBySlug($slug)
{
return $this->em->createQueryBuilder()
->select()
// ...
}
}
or a Rest client:
class RestOrders implements Orders
{
private $httpClient;
public function __construct(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
}
public function findProductOrders(Product $product)
{
$orders = $this->httpClient->get(sprintf('/product/%d/orders', $product->getId()));
$orders = $this->hydrateResponseToOrdersInSomeWay($orders);
return $orders;
}
}
You can even make some methods use the http client and some use the database in a single repository.
Register your repositories as services and use them rather then calling Doctrine::getRepository() directly:
services:
repository.orders:
class: DoctrineOrders
arguments:
- #doctrine.orm.entity_manager
Always rely on your repository interfaces and never on a specific implementation. In other words, always use a repository interface type hint:
class DefaultController
{
private $orders;
public function __construct(Orders $orders)
{
$this->orders = $orders;
}
public function indexAction(Product $product)
{
$orders = $this->orders->findProductOrders($product);
// ...
}
}
If you don't register controllers as services:
class DefaultController extends Controller
{
public function indexAction(Product $product)
{
$orders = $this->get('repository.orders')->findProductOrders($product);
// ...
}
}
A huge advantage of this approach is that you can always change the implementation details later on. Mysql is not good enough for search anymore? Let's use elastic search, it's only a single repository!
If you need to call $product->getOrders() and fetch orders from the API behind the scenes it should still be possible with some help of doctrine's lazy loading and event listeners.
Related
I am trying to create a user management module. I would like to get all FE users.
This is my Controller:
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
* #inject
*/
protected $feUserRepository;
then I use:
$users = $this->feUserRepository->findAll();
$this->view->assign('users', $users);
but all I get is an empty object.
EDIT:
for some reason
$this->feUserRepository->findByUId(1);
does work but findAll() not...
This is because extbase will silently disable the respectStoragePage setting on the querySettings for a findByUid($uid) call.
So, you have two options:
Provide the correct storage pid in the TypoScript configuration of your plugin (plugin.tx_myextension.persistence.storagePid). This way, you will find every frontenduser that is stored on the given page.
You could implement your own FrontendUserRepository that extends the repository from extbase but disables the respectStoragePage for all calls (this way you'll get every frontendUser regardless of the page the record is stored on). Here is how you do it:
use TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository as ExtbaseFrontendUserRepository;
class FrontendUserRepository extends ExtbaseFrontendUserRepository
{
/**
* Disable respecting of a storage pid within queries globally.
*/
public function initializeObject()
{
$defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings:class);
$defaultQuerySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($defaultQuerySettings);
}
}
In your Controller you then inject your FrontendUserRepository. Then you should do the same for the FrontendUser Model and tell extbase afterwards that you are using the fe_users table for your Model:
config.tx_extbase {
persistence {
classes {
Vendor\MyExtension\Domain\Model\FrontendUser {
mapping {
tableName = fe_users
}
}
}
}
}
Say I have tables A, B in MySQL and Doctrine entity classes with the same names. Those entities are managed by Doctrine and are basically created according to Symfony/Doctrine docs.
Now I want to create entity C with columns: x, y. Whenever this entity is created or updated, I want to set the column values:
x: select count(*) from A where (some condition)
y: select sum(y) from B where (other condition)
pull some other data from A or B and store it as column value for C.
I want to do this in PHP and not use mysql triggers. I can't achieve from inside the Entity classes, because they don't have access to entity manager. I don't want to do this in the controller, as I want insert/update operations to be standardized, and I will need to do it from multiple controllers, and I generally don't think the controller is a good place for logic like this.
So I need some kind of class which manages entity C.
My question is: How do I call this manager class and where do I place it in Symfony? I am pretty sure this is a common need in Symfony (to access multiple entities while creating another entity), but I don't know how it is called and if there is a standard practice with them.
you can define service in app/config/services.yml and pass Entity manager as argument
services:
app.service.some_service:
class: AppBundle\Service\SomeService
arguments: ["#doctrine.orm.default_entity_manager"]
place your logic inside service
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\SomeEntity;
class SomeService
{
/**
* #var EntityManagerInterface
*/
protected $entityManager;
public function __construct(EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
}
public function getSomeEntity($id) {
$entity = $this->entityManager->getRepository(SomeEntity::class);
// do some work, return result..
}
}
call it from controller
$someService = $this->get('app.service.some_service');
$someService->getSomeEntity($id);
:)
I think you should create a Doctrine Event Subscriber as described in the documentation
I'll try to explain the basics.
1) Declare the service
services:
c_entity_counter_subscriber:
class: AppBundle\EventListener\CounterSubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
2) In the Subscriber count A and B properties
namespace AppBundle\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity\A;
use AppBundle\Entity\B;
use AppBundle\Entity\C;
class CounterSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'postPersist',
'postUpdate',
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->count($args);
}
public function postPersist(LifecycleEventArgs $args)
{
$this->count($args);
}
public function count(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof C) {
return;
}
$entityManager = $args->getEntityManager();
// ... count/sum entities from A/B classes using $entityManager and update $entity
}
}
I am working on implementing a state machine for a workflow management system based on the Stateless4j API. However, I am not able to find an effective way to persist the states and transitions in Stateless4j.
As part of our usecases, we have the requirement to keep States alive for more than 3 - 4 days until the user returns to the workflow. And we will have more than one workflow running concurrently.
Can you please share your insights on the best practices to persist states in Stateless4j based State Machine implementation?
It looks like what you need to do is construct your StateMachine with a custom accessor and mutator, something like this:
public class PersistentMutator<S> implements Action1<S> {
Foo foo = null;
#Inject
FooRepository fooRepository;
public PersistentMutator(Foo foo) {
this.foo = foo;
}
#Override
public void doIt(S s) {
foo.setState(s);
fooRepository.save(foo)
}
}
Then you want to call the constructor with your accessors and mutators:
/**
* Construct a state machine with external state storage.
*
* #param initialState The initial state
* #param stateAccessor State accessor
* #param stateMutator State mutator
*/
public StateMachine(S initialState, Func<S> stateAccessor, Action1<S> stateMutator, StateMachineConfig<S, T> config) {
this.config = config;
this.stateAccessor = stateAccessor;
this.stateMutator = stateMutator;
stateMutator.doIt(initialState);
}
Alternatively, you may want to look at StatefulJ. It has built in support for atomically updating state in both JPA and Mongo out of the box. This may save you some time.
Disclaimer: I'm the author of StatefulJ
I'm developing RESTful API for a web service. And I need to expose some properties that do not belong to an entity itself.
For example I have a Pizza entity object, it has it's own size and name properties. I'm outputting it in JSON format with FOSRestBundle and JMSSerializer. I've setup properties annotations for this entity to expose needed properties via serialization groups and it's working great.
But I need to add some properties that do not belong to the entity itself. For example I want my pizza to have property: isFresh that is determined by some PizzaService::isFresh(Pizza $pizza) service. How do I do this?
Should I inject some additional logic to serialization process (if so how)?
Should I create a wrapper entity with properties that I want to expose from original entity plus additional external properties?
Should I add property isFresh to the original Pizza entity and populate in in the controller before serialization?
Should I return additional data independent of entity data (in a sibling JSON properties for example)?
In other words: what are the best practices around this issue? Could you provide examples? Thank you.
I think you can do that with the VirtualProperty annotation :
/**
* #JMS\VirtualProperty
* #return boolean
*/
public function isFresh (){
...
}
Edit : another solution with the Accessor annotation
/** #Accessor(getter="getIsFresh",setter="setIsFresh") */
private $isFresh;
// ...
public function getIsFresh()
{
return $this->isFresh;
}
public function setIsFresh($isFresh)
{
$this->isFresh= $isFresh;
}
In your controller, you call the setIsFresh method
(See http://jmsyst.com/libs/serializer/master/reference/annotation)
I've decided to create my own class to serialize an entity.
Here's the example:
class PizzaSerializer implements ObjectSerializerInterface
{
/** #var PizzaService */
protected $pizzaService;
/**
* #param PizzaService $pizzaService
*/
public function __construct(PizzaService $pizzaService)
{
$this->pizzaService = $pizzaService;
}
/**
* #param Pizza $pizza
* #return array
*/
public function serialize(Pizza $pizza)
{
return [
'id' => $pizza->getId(),
'size' => $pizza->getSize(),
'name' => $pizza->getName(),
'isFresh' => $this->pizzaService->isFresh($pizza),
];
}
}
You just have to configure DC to inject PizzaService into the object serializer and then just call it like this from the controller:
$pizza = getPizzaFromSomewhere();
$pizzaSerializer = $this->get('serializer.pizza');
return $pizzaSerializer->serialize($pizza);
The object serializer will return an array that can be easily converted to JSON, XML, YAML or any other format by using real serializer like JMS Serializer. FOSRestBundle will do this automatically if you configured it so.
I'm having a problem with passing the entity manager between two layers of controllers.
The system I'm building has the following structure:
2 Bundles:
Core Bundle (let's call it Backend Controller)
This is the bundle that contains all the Models (entities) and business rules/logic.
API Bundle (call it Frontend controller)
Is responsible for checking the permissions of passed in api keys and communicating with the Core bundle to get the info.
Here's an example with the User controllers and entities:
UserController.php in APIBundle:
<?php
namespace Acme\Bundle\APIBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Acme\Bundle\CoreBundle\Controller\UserController as User;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class UserController extends BaseController implements AuthenticatedController
{
public function readAction(Request $request) {
$user = new User($this->getDoctrine()->getManager());
$user->load(2);
return $this->response($user);
}
}
UserController.php in CoreBundle:
<?php
namespace Acme\Bundle\CoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
use Acme\Bundle\CoreBundle\Entity\User;
class UserController extends BaseController
{
function __construct($em) {
parent::__construct($em);
$this->entity = new User();
}
/**
* Get userId
*
* #return integer
*/
public function getUserId()
{
return $this->entity->userId;
}
/**
* Set firstName
*
* #param string $firstName
* #return User
*/
public function setFirstName($firstName)
{
$this->entity->firstName = $firstName;
return $this;
}
// ...
public function load($id) {
if (!$this->entity instanceof \Acme\Bundle\CoreBundle\Entity\BaseEntity) {
throw new \Exception('invalid entity argument');
}
$this->entity = $this->em->getRepository('AcmeCoreBundle:User')->find($id);
}
}
Please, tell me if I'm doing this right. It seems strange to pass the entity manager between the controllers every time.
Maybe there's a better way of doing that?
Does the idea between the separation of the bundles make sense?
Thank you, every idea is greatly appreciated.
If CoreBundle UserController is never accessed through HTTP nor do its methods return instances of Symfony\Component\HttpFoundation\Response then it's not really a controller.
You should better define it as a service, as in CoreBundle\Service\User, and inject the EntityManager through the DI container.
sevices.yml
corebundle.userservice:
class: Acme\CoreBundle\Service\User
arguments: [#doctrine.orm.entity_manager]
It will then be available from Acme\Bundle\APIBundle\Controller\UserController with the following:
$user = $this->get('corebundle.userservice');
Of course, you can also define Acme\Bundle\APIBundle\Controller\UserController as a service on its own, then inject 'corebundle.userservice', for convenience.
I suggest you read the Symfony docs on Dependency Injection.
Search to get entity manager in Entity class is a wrong way !
In CoreBundle, you use the UserController.php same as an Entity class.
Read docs to understand how to properly use repository in symfony.
In UserController of APIBundle you must call a custom repository function. This repository is declare in your entity.