I have a Symfony app with a User entity with a many-to-many relation to a Cat entity. I also have a PhpUnit test which checks that deleting a cat (that belongs to 2 users) from 1 user doesn't actually delete the cat:
public function testDeletingACatBelongingToTwoUsersOnlyDeletesTheAssociationNotTheCat()
{
$cat = $this->createCat();
// Associate with user 1
$user1 = new User();
$user1->setEmail('test#example.com');
$user1->setPassword('pwdpwd');
$user1->addCat($cat);
$this->em->persist($user1);
// Associate with user 2
$user2 = new User();
$user2->setEmail('another#example.com');
$user2->setPassword('pwdpwd');
$user2->addCat($cat);
$this->em->persist($user2);
$this->em->flush();
// Sanity check:
$this->assertCount(1, $user1->getCats()); // PASS
$this->assertCount(1, $user2->getCats()); // PASS
$this->assertCount(2, $cat->getUsers()); // FAIL (0)
// ... perform the test (not shown here)
}
private function createCat(): Cat
{
$cat = new Cat();
$cat->setName($this->name);
$this->em->persist($cat);
$this->em->flush();
return $cat;
}
My question is, why does $cat->getUsers() return 0 in my test? At runtime it doesn't, it returns the correct value. It's only in the test that it returns 0.
Here are the relevant excerpts from my entities, auto-generated by Symfony:
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\ManyToMany(targetEntity=Cat::class, inversedBy="users")
*/
private $cats;
/**
* #return Collection|Cat[]
*/
public function getCats(): Collection
{
return $this->cats;
}
public function addCat(Cat $cat): self
{
if (!$this->cats->contains($cat)) {
$this->cats[] = $cat;
}
return $this;
}
public function removeCat(Cat $cat): self
{
$this->cats->removeElement($cat);
return $this;
}
}
/**
* #ORM\Entity(repositoryClass=CatRepository::class)
*/
class Cat
{
/**
* #ORM\ManyToMany(targetEntity=User::class, mappedBy="cats")
*/
private $users;
/**
* #return Collection|User[]
*/
public function getUsers(): Collection
{
return $this->users;
}
}
The reason is, that collections are not synchronized with the database and synchronization between owning and inverse side is not automatically done either.
The category entries of your user entity probably will be persisted to the database (although I'm missing some cascade statements, but what do I know). When the category is created, it's collection of users is empty (obviously), then users add the category to the many-to-many relation in database.
BUT, the collection is a plain collection. If you loaded the category from the database, it would be a lazy-loaded PersistentCollection (or something alike), which would - only at the moment of access - fetch the items from the database (definition of lazy loading). Your test code has the plain collection (since you created the object yourself).
Not quite sure, if it'll work, but you could try refreshing the cat ($em->refresh($cat);) I'm not quite certain though, if that will replace the collection. Alternatively, you could make your User::addCat that it also calls $cat->addUser($this) (which you might have to add, beware the infinite recursion, which already should be prevented by the "contains" checks.).
Related
I've got an entity called Logs that has a ManyToOne relation to an HourlyRates entity. Both Logs and HourlyRates have date properties. When adding a log with a specific date, an hourlyRate is assigned to it if the log-date fits within the rate's time range. I'm using the Doctrine Extensions Bundle, so the data in each entity can be soft-deleted.
What needs to be done:
After soft-deleting an HourlyRate the related Log has to be updated, so that the nearest existing past HourlyRate takes the place of the deleted one.
I tried to use preSoftDelete, postSoftDelete, preRemove and postRemove methods inside an HourlyRate entity listener. The code was being executed and the setters were working properly, but the database hasn't been updated in any of said cases. An "EntityNotFoundException" was being thrown everytime.
My second approach was to use the preRemove event along with setting the cascade option to "all" by using annotations in the HourlyRate class. As a result, soft-deleting an hourlyRate caused soft-deleting of the related log.
The Log entity:
class Log
{
use SoftDeleteableEntity;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\HourlyRate", inversedBy="logs")
* #ORM\JoinColumn(nullable=false)
*/
private $hourlyRate;
public function setHourlyRate(?HourlyRate $hourlyRate): self
{
$this->hourlyRate = $hourlyRate;
return $this;
}
}
The HourlyRate entity:
class HourlyRate
{
use SoftDeleteableEntity;
//other code
/**
* #ORM\OneToMany(targetEntity="App\Entity\Log", mappedBy="hourlyRate", cascade={"all"})
*/
private $logs;
}
The HourlyRate entity listener:
class HourlyRateEntityListener
{
public function preRemove(HourlyRate $hourlyRate, LifecycleEventArgs $args)
{
$entityManager = $args->getObjectManager();
/** #var HourlyRateRepository $HRrepo */
$HRrepo = $entityManager->getRepository(HourlyRate::class);
foreach ($hourlyRate->getLogs() as $log)
{
$rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser(), $hourlyRate);
$log->setHourlyRate($rate);
}
}
}
The repository method:
class HourlyRateRepository extends ServiceEntityRepository
{
public function findHourlyRateByDate(?\DateTimeInterface $datetime, User $user, ?HourlyRate $ignore = null): ?HourlyRate
{
$qb = $this->createQueryBuilder('hr')
->where('hr.date <= :hr_date')
->andWhere('hr.user = :user')
->orderBy('hr.date', 'DESC')
->setMaxResults(1)
->setParameters(array('hr_date' => $datetime, 'user' => $user));
//ignore the "deleted" hourlyRate
if($ignore){
$qb->andWhere('hr.id != :ignored')
->setParameter('ignored', $ignore->getId());
}
return $qb->getQuery()
->getOneOrNullResult()
;
}
}
Thank you in advance for any of your help.
EDIT:
Okay so after a whole week of trials and errors i finally managed to achieve the result I wanted.
I removed the One-To-Many relation between the hourlyRates and the logs from the entities, but left the $hourlyRate property inside the Log class. Then I got rid of the HourlyRateEntityListener and the preRemove() method from the LogEntityListener. Instead, I implemented the postLoad() method:
class LogEntityListener
{
public function postLoad(Log $log, LifeCycleEventArgs $args)
{
$entityManager = $args->getObjectManager();
$HRrepo = $entityManager->getRepository(HourlyRate::class);
/** #var HourlyRateRepository $HRrepo */
$rate = $HRrepo->findHourlyRateByDate($log->getDate(), $log->getUser());
$log->setHourlyRate($rate);
}
}
This approach allows me to set the proper hourlyRate for each log without involving the database. Idk if this solution is acceptable though.
I have a Parents form embedded into another form Student containing the data of the parents of a student with an association of Many to one.
When a new student registration are recorded his parents in another table in the database. Then if a new student who is brother of an existing need to register, meaning that parents are already registered in the database, should be prevented from parents to register again in the database, could only upgrade .
I'm told that this is solved using data transformers, but I do not know how to use it. If someone could help me I would appreciate it. Here I leave the code:
StudentType.php
//...
->add('responsible1', new ParentsType(),array('label' => 'Mother'))
->add('responsible2', new ParentsType(),array('label'=> 'Father'))
Entity Parents
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//National identity document
//we have removed "#UniqueEntity(fields={"NID"}, message="...")"
//so you can put any NID on the form and then check its existence to insert or not.
/**
* #var string
*
* #ORM\Column(name="NID", type="string", length=10)
* #Assert\NotBlank()
*/
private $nid;
//more properties...
/**
* #ORM\OneToMany(targetEntity="Student", mappedBy="$responsible1")
* #ORM\OneToMany(targetEntity="Student", mappedBy="$responsible2")
*/
private $students;
//...
public function addStudent(\Cole\BackendBundle\Entity\Student $students)
{
$this->students[] = $students;
return $this;
}
public function removeStudent(\Cole\BackendBundle\Entity\Student $students)
{
$this->students->removeElement($students);
}
public function getStudents()
{
return $this->students;
}
Entity Student
//...
/**
* #ORM\ManyToOne(targetEntity="Parents", inversedBy="students", cascade={"persist"})
*/
private $responsible1;
/**
* #ORM\ManyToOne(targetEntity="Parents", inversedBy="students", cascade={"persist"})
*/
private $responsible2;
//...
public function setResponsible1($responsible1)
{
$this->responsible1 = $responsible1;
return $this;
}
public function getResponsible1()
{
return $this->responsible1;
}
public function setResponsible2($responsible2)
{
$this->responsible2 = $responsible2;
return $this;
}
public function getResponsible2()
{
return $this->responsible2;
}
ParentsRepository.php
class ParentsRepository extends EntityRepository
{
public function findResponsible($nid)
{
return $this->getEntityManager()->createQuery(
'SELECT p FROM BackendBundle:Parents p WHERE p.nid=:nid')
->setParameter('nid',$nid)
->setMaxResults(1)
->getOneOrNullResult();
}
}
StudentController.php
/**
* Creates a new Student entity.
*
*/
public function createAction(Request $request)
{
$entity = new Student();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$responsible1 = $em->getRepository('BackendBundle:Parents')->findResponsible($entity->getResponsible1()->getNid());
$responsible2 = $em->getRepository('BackendBundle:Parents')->findResponsible($entity->getResponsible2()->getNid());
if($responsible1){
$entity->setResponsible1($responsible1->getId());
}
if($responsible2){
$entity->setResponsible2($responsible2->getId());
}
$entity->getResponsible1()->setUsername($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setUsername($entity->getResponsible2()->getNid());
$entity->getResponsible1()->setPassword($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setPassword($entity->getResponsible2()->getNid());
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('student_show', array('id' => $entity->getId())));
}
return $this->render('BackendBundle:Student:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
With the above code attempts to solve the problem but it gives me error to persist data to the database and will not let me add to the database, but if you use the following code to test the new student creates and assigns parents corresponding not create them again (assuming you were already created earlier).
$responsible1 = $em->getRepository('BackendBundle:Parents')->findResponsible(4); //The number corresponds to the id of the parent
$responsible2 = $em->getRepository('BackendBundle:Parents')->findResponsible(5);
$entity->setResponsible1($responsible1->getId());
$entity->setResponsible2($responsible2->getId());
I do not know if what I'm doing is right.I read something to use Data Transformers or event listener as PrePersist and Preupdate, but I don't know how to use this.
Thanks in advance for your answers.
Instead of
if($responsible1){
$entity->setResponsible1($responsible1->getId());
}
if($responsible2){
$entity->setResponsible2($responsible2->getId());
}
$entity->getResponsible1()->setUsername($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setUsername($entity->getResponsible2()->getNid());
$entity->getResponsible1()->setPassword($entity->getResponsible1()->getNid());
$entity->getResponsible2()->setPassword($entity->getResponsible2()->getNid());
you can write
if($responsible1){
$entity->setResponsible1($responsible1);
}
if($responsible2){
$entity->setResponsible2($responsible2);
}
And it should work.
But I think a better solution will be to add an event listener to the FormEvents::SUBMIT event. This event allows you to change data from the normalized representation of the form data. So all you need to do is something like this:
public function onSubmit(FormEvent $event)
{
$student = $event->getData();
if ($student->getResponsible1()) {
$parentNid = $student->getResponsible1()->getNid();
// here you check the database to see if you have a parent with this nid
// if a parent exists, replace the current submitted parent data with the parent entity existing in your db
}
Hope this helps. Let me know if I have to give more details.
Judging from your relationship, you want to avoid that the same student is added twice to the Parents entity. There is a simple trick for that, ArrayCollaction class has a method named contains it returns true if a value or object is already found in the collection. A better in_array.
So, you need to check inside the adder if the $parent already contains the $student that is about to be added and act accordingly. Like shown below:
public function addStudent(\Cole\BackendBundle\Entity\Student $student)
{
if (!$this->students->contains($student)) {
$this->students[] = $students;
}
return $this;
}
Here's my thoughts, from the comments, you said you are using a national identity document(hopefull its an integer representation), make this the primary key of the parent table and make this unique, so when the second student who is the sibling of a another student enters the same details and submits, the database will throw an error, handle that error and continue on
edit: it may not even be required to make the national identity the primary key, just make it unique, you were supposed to do this regardless, you missed this one.
you can use symfony entity form type to load (ajax) the parent entity when the student enters the national identity
I have a many-to-many relationship between users (the owning side) and user groups, and am having issues using doctrine module's hydrator to create a new user group.
When I create a new user group and hydrate, persist, and flush it, the records change in the database, but the entity variable itself representing the user group doesn't end up with any users in it post-hydration.
Context: We have a REST controller route that we use to create a new user group via POST. It accepts parameters to initialize it with some users via hydration. This operation successfully updates the database, but its response is incorrect. It is supposed to extract the data from the now-persistent entity and echo it back to the client. However, it fails to extract any users, so the response incorrectly returns as an empty group. Not using the hydrator's extract method and instead using more basic doctrine commands fails too--it seems like the entity variable itself is just not kept up to date after being persisted.
So my question really is: why is the hydrator not extracting users? If we've messed up the owner/inverse assocation, why is it working at all (i.e. persisting the users to the database but not to the entity).
Here is the relevant code, probably only the first two blocks are needed.
public function create($data) {
...
$hydrator = $this->getHydrator();
$em = $this->getEntityManager();
$entity = $this->getEntity();
$entity = $hydrator->hydrate($data, $entity);
// Persist the newly created entity
$em->persist($entity);
// Flush the changes to the database
$em->flush();
return $this->createResponse(
201,
true,
'Created item',
$this->getHydrator()->extract($entity)
);
Here is are the setters and getters the hydrator is using:
... more fields...
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="groups")
*/
protected $users;
...
/**
* Getter for users
*
* #return mixed
*/
public function getUsers() {
return $this->users;
}
public function addUsers(Collection $users) {
foreach ($users as $user) {
$user->addGroups(new ArrayCollection(array($this)));
}
}
I think the above is the only relevant code, but I'll include some more in case I'm wrong. Here is the getHydrator method:
public function getHydrator() {
if(null === $this->hydrator) {
$hydrator = new DoctrineObject($this->getEntityManager(), $this->getEntityName());
// Use Entity metadata to add extraction stategies for associated fields
$metadata = $this->em->getClassMetadata($this->getEntityName());
foreach ($metadata->associationMappings as $field => $mapping) {
// Use our custom extraction strategies for single and collection valued associations
if($metadata->isSingleValuedAssociation($field)) {
$hydrator->addStrategy($field, new RestfulExtractionStrategy());
}
else if($metadata->isCollectionValuedAssociation($field)) {
$hydrator->addStrategy($field, new RestfulExtractionCollectionStrategy());
}
}
$this->hydrator = $hydrator;
}
return $this->hydrator;
}
Here is the RestfulExtractionCollectionStrategy (the other strategy isn't being used here, I have verified this).
namespace Puma\Controller;
use DoctrineModule\Stdlib\Hydrator\Strategy\AllowRemoveByValue;
use Doctrine\Common\Collections\Collection;
/**
* You can use this strategy with fields that are collections,
* e.g. one to many. You need to use the RestfulExtractionStrategy
* if you want to get extract entities from a singleton field, e.g. manyToOne.
**/
class RestfulExtractionCollectionStrategy extends AllowRemoveByValue
{
public function extract($value)
{
if ($value instanceof Collection) {
$return = array();
foreach ($value as $entity) {
if(method_exists($entity, 'getId')){
$return[] = $entity->getId();
}
else {
$return[] = $entity;
}
}
return $return;
}
return $value;
}
}
I am not quite familiar with hydration, etc., so your code looks kind of strange to me and I cannot guarantee this will work, but have you tried to refresh the entity after flushing (i.e. $em->refresh($entity)) and maybe return the entity instead of $this->getHydrator()->extract($entity)?
I think I've finally solved it--I added a line to the "setter" method, addUsers which manually updates the users property of the group after updating the related users. I would be a bit surprised if this was best practice, though. I had thought that updating the owning side (the users) would automatically update the inverse side (the user group). Perhaps I was wrong. If anyone else has a better idea I'll gladly give the answer credit to them.
public function addUsers(Collection $users) {
foreach ($users as $user) {
$user->addGroups(new ArrayCollection(array($this)));
// This is the new line (updating the usergroup property manually)
$this->users->add($user);
}
}
I have an entity that needs to return an instance of another entity - the stored record or a new one if one has not been stored.
<?php
/**
* #Entity
*/
class User {
/**
* #OneToOne(targetEntity="Basket")
* #JoinColumn(nullable=true)
*/
protected $activeBasket;
public function getActiveBasket() {
if (!isset($this->activeBasket)) {
$this->activeBasket = new Basket($this);
// $em->persist($this->activeBasket);
// $em->flush();
}
return $this->activeBasket;
}
}
My problem is that I don't have an EntityManager to use to persist this new Basket (and obviously don't want one in the Model). I'm not sure as to the best way to do this. I do want to be able to call $user->getActiveBasket() and retrieve a basket, whether it's previously created or a new one.
It feels like this should be a common problem, or that there's a better way to structure this (I'm hoping there's a way to hook one in in an EntityRepository or something). How can I do this?
I wouldn't return a new Basket() when it is null or not set. The model (entity) should only serve for setting and getting its properties.
The logic when $user->getActiveBasket() returned null should be elsewhere - in controller or in entity's reporitory object...
So I would move public function getActiveBasket() { ... } into just public function getActiveBasket() { return $this->activeBasket; } and somewhere in the controller I would do:
$if(!$user->getActiveBasket()) {
$user->setActiveBasket(new Basket($user));
$this->_em->persist($user);
$this->_em->flush();
}
// now do something with that activeBasket...
I'm programming in PHP procedurally (is this even a word?) for about five years now and decided to try an OOP approach but ran into some concept/design problems. Let's say you have some modules in the program, every module has the possibility to list, add, edit and delete an entity. An entity can be..dunno, user, client, product etc.
How would you design the classes to manipulate these entityes?
Two possibilities came in my mind:
create classes for every entity with methods like getUsersList, addUser, editUser, delUser
This approach seems resource consumingbecause in the script for the listing you only need the getUsersList and maybe delUser methods, while in the add user popup script, you only need the addUser method and in the edit user popup script only the editUser method. So, you have to instanciate an object and only use two or one of it's methods...
create general classes: listing, add, edit and delete and extend them for every entity this way you only have to instanciate one class at a time (the one you really need)
Thanks in advance,
I would create an interface defining your list, add, edit, and delete methods. This gives you a class "template". If your classes (User, Client, Product, etc.) implement this interface, then the methods in the interface must be defined in those classes.
This will give you a similar "API" to access all the functionality of every class that implements your interface. Since each of your listed objects contains different data, the details of the methods will be different, and thus separate, but the interface will be the same.
Aside:
Your inclusion of "list" in your list of methods concerns me a little. It seems to imply that you are seeing your objects as collections of Users, Clients, Products, etc, where there should most likely be a User class that represents a single user, a Client class that represents a single client, etc.
On the other hand, "list" may be handled as a static method - a method that can be called without an instance of the class.
$bob = new User('bob');
$bob->add(); // Adds bob to the database
$fred = new User('fred');
$fred->add(); // Adds fred to the database
$users = User::list(); // Gives an array of all the users in the database
That's how I would handle things, anyway.
You will need to create a solid architecture and framework for managing your data model. This is not easy and will only get more complex as the data model grows. I would highly recommend using a PHP framework (Symfony, CakePHP, etc), or at least, an ORM (Doctrine, Propel, etc).
If you still want to roll your own, I would start with an architecture similar to below.
You will want a DbRecord class that is used for individual record operations (like saving, deleting, etc). This DbRecord class will be extended by specific entities and will provide the foundation for basic entity operations.
class DbRecord {
public function save() {
// save logic (create or update)
}
public function delete() {
// delete logic
}
// other record methods
}
class User extends DbRecord {
private $name;
private $email;
public function setName($name_) {
$this->name = $name_;
}
public function setEmail($email_) {
$this->email = $email_;
}
}
From which, you can perform individual record operations:
$user = new User();
$user->setName('jim');
$user->setEmail('jim#domain.com');
$user->save();
You will now want a DbTable class that is used for bulk operations on the table (like reading all entities, etc).
class DbTable {
public function readAll() {
// read all
}
public function deleteAll() {
// delete all logic
}
public function fetch($sql) {
// fetch logic
}
// other table methods
}
class UserTable extends DbTable {
public function validateAllUsers() {
// validation logic
}
// ...
}
From which, you can perform bulk/table operations:
$userTable = new UserTable();
$users = $userTable->readAll();
foreach ($users as $user) {
// etc
}
Code architecture is the key to a website scaling properly. It is important to divide the data model into the appropriate classes and hierarchy.
Again, as your website grows, it can get very complicated to manage the data model manually. It is then when you will really see the benefit of a PHP framework or ORM.
NOTE: DbRecord and DbTable are arbitrary names - use w/e name you like.
Use your first method, where you create a reusable object with methods. It is not a waste of time as you only code it once.
class User {
function __construct() { /* Constructor code */ }
function load($id) { ... }
function save() { ... }
function delete() { ... }
}
You're on the right track with 'general classes' (also called base classes, or abstract classes in case their behaviour NEEDS to be complemented by child classes before they can be put to use).
The OOP approach would be to put all behavior that is common to all entities in the base classes.
If you use something akin to ActiveRecord, you already have a general (abstract) interface for create-update-delete operations. Use that to your advantage, and let your base classes operate ONLY on those interface methods. They don't need to know they are updating a Product, or a a User, they just need to know they can call the update() method on an entity.
But even without using something quite feature-heavy like an AR framework (check out Doctrine if you're interested in a very flexible ORM..) you can use interfaces to abstract behavior.
Let me give you a more elaborate example...
/**
* Interface for all entities to use
*/
interface Entity {
static function newEntity();
static function fetch($id);
function save();
function setProperties(array $properties);
function delete();
}
/**
* A concrete product entity which implements the interface
*/
class Product implements Entity {
public $productId;
public $name;
public $price;
public $description;
/**
* Factory method to create a new Product
*
* #param integer $id Optional, if you have auto-increment keys you don't need to set it
* #return Product
*/
public static function newEntity($id=NULL) {
$product = new Product();
$product->productId = $id;
return $product;
}
/**
* Factory method to fetch an existing entity from the database
*
* #param integer $id
* #return Product
*/
public static function fetch($id) {
// make select with supplied id
// let $row be resultset
if (!$row) {
return NULL; // you might devise different strategies for handling not-found cases; in this case you need to check if fetch returned NULL
}
$product = new Product();
$product->productId = $id;
$product->name = $row['name'];
$product->price = $row['price'];
$product->description = $row['description'];
return $product;
}
/**
* Update properties from a propreties array
* #param array $properties
* #return void
*/
public function setProperties(array $properties) {
$this->name = $properties['name'];
$this->price = $properties['price'];
$this->description = $properties['description'];
}
public function save() {
// save current product properties to database
}
public function delete() {
// delete product with $this->productId from database
}
}
/**
* An abstract CRUD controller for entities
*/
abstract class EntityCrudController {
protected $entityClass = 'UNDEFINED'; // Override this property in child controllers to define the entity class name
protected $editTemplate = NULL; // Override this to set an edit template for the specific entity
protected $templateEngine; // Pseudo-Templating engine for this example
/**
* Display the edit form for this entity
* #param integer $entityId
* #return string
*/
public function editAction($entityId) {
// Fetch entity - this is not the most clean way to fetch, you should probably consider building a factory that encapsulates this.
$entity = call_user_func($this->entityClass, 'fetch', $entityId);
// Assign entity to your edit template, in this example I'm assuming we're using a template engine similar to Smarty
// You can generate the HTML output in any other way you might like to use.
$this->templateEngine->setTemplate($this->editTemplate);
$this->templateEngine->assign('entity', $entity);
return $this->template->render();
}
/**
* Update an existing entity
*
* #param integer $entityId
* #param array $postArray
* #return string
*/
public function updateAction($entityId, array $formArray) {
// Be sure to validate form data first here, if there are errors call $this->editAction() instead and be sure to set some error information
$entity = call_user_func($this->entityClass, 'fetch', $entityId);
$entity->setProperties($formArray);
$entity->save();
// Again, using our imaginary templating engine to display...
$this->templateEngine->setTemplate($this->editTemplate);
$this->templateEngine->assign('entity', $entity);
$this->templateEngine->assign('message', 'Saved successfully!');
return $this->template->render();
}
// Devise similar generic methods for newAction/insertAction here
}
/**
* Concrete controller class for products
* This controller doesn't do much more than extend the abstract controller and override the 2 relevant properties.
*/
class ProductCrudController extends EntityCrudController {
protected $entityClass = 'Product';
protected $editTemplate = 'editProduct.tpl';
}
// Usage example:
// Display edit form:
$controller = new ProductCrudController();
$htmlOutput = $controller->editAction(1);
// Save product:
$htmlOutput = $controller->updateAction(1, array('name' => 'Test Product', 'price' => '9.99', 'description' => 'This is a test product'));
Of course, there is much to improve.. e.g. you generally don't want to make a query everytime you call fetch() on an entity, but instead only query once and store the resulting object in an IdentityMap, which also ensures data integrity.
Hope this helps, got a bit more than I intended, but I think it's commendable you try to tackle this without throwing a framework on the problem :)