I have a Post entity in my Blog bundle. Posts can have many comments. When I create a new comment entity to attach to a post I have to set a bunch of properties such as,
$comment->setTimestamp( new \DateTime() );
$comment->setUserId( $this->getUser()->getId() );
$comment->setHost( $this->getClientIP() );
default timezone is easy in the constructor of the entity. How do I automatically set the userid and clientip when constructing the entity? getClientIP is a function in the controller at the moment. This should be service. Can I have a factory that creates comments for me?
Seems to me that your best bet would be a class CommentFactory extends EntityFactory.
The factory would be responsible for creating your Entities for you, you pass the required entities (Such as the user entity) and it would return new objects for you:
$commentFactory = new CommentFactory($user, $client, $whatever);
$comment = $commentFactory->getNewComment();
You can call any entity method from an entity construct, and you can pass anything from controller to a new instance of the comment.
For instance, get timestamp, userid and host in controller action, and pass those as parameters to a construct of Comment entity.
Make calls to setter methods in a construct.
You can create helper function in controller for that
protected function getNewComment() {
$comment = new Comment();
$comment->setTimestamp( new \DateTime() );
$comment->setUserId( $this->getUser()->getId() );
$comment->setHost( $this->getClientIP() );
return $comment;
}
And then
$comment = $this->getNewComment();
Related
So, in my framework X, let it be Phalcon, I often create models objects.
Let's assume that all fields already validated. Questions related only about creation logic.
A simple example of creating Users object and save it to DB:
<?php
$user = new Users();
$user->setName($name);
$user->setLastName($lastname);
$user->setAge($age);
$user->create();
For simplicity, I show here only 3 fields to setup, in the real world they always more.
I have 3 questions:
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
Example:
<?php
$factory = new UsersFactory();
$factory->make($name, $lastname, $address, $phone, $status, $active);
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
3) And even more, what if i will need to create Users objects with relations, with other related objects?
Thank you for any suggestions.
Your question starts out simple and then builds with complexity. Reading your post it sounds like your concerned about the number of arguments you would have to pass to the method to build the object. This is a reasonable fear as you should try to avoid functions which take more than 2 or 3 args, and because sometimes you will need to pass the 1st 3rd and 5th arg but not the 2nd and 4th which just gets uncomfortable.
I would instead encourage you to look at the builder pattern.
In the end it will not be that much different than just using your User object directly however it will help you prevent having a User object in an invalid state ( required fields not set )
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
This is why I recommended the builder pattern. To avoid passing a large number of params to a single function. It also would allow you to validate state in the build method and handle or throw exceptions.
class UserBuilder {
protected $data = [];
public static function named($fname, $lname) {
$b = new static;
return $b
->withFirstName($fname)
->withLastName($lname);
}
public function withFirstName($fname) {
$this->data['first_name'] = $fname;
return $this;
}
public function withFirstName($lname) {
$this->data['last_name'] = $lname;
return $this;
}
public function withAge($age) {
$this->data['age'] = $age;
return $this;
}
public function build() {
$this->validate();
$d = $this->data;
$u = new User;
$u->setFirstName($d['first_name']);
$u->setLastName($d['last_name']);
$u->setAge($d['age']);
return $u;
}
protected function validate() {
$d = $this->data;
if (empty($d['age'])) {
throw new Exception('age is required');
}
}
}
then you just do..
$user = UserBuilder::named('John','Doe')->withAge(32);
now instead of the number of function arguments growing with each param, the number of methods grows.
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
no it should not insert. it should just help you build the object, not assume what your going to do with it. You may release that once you build it you will want to do something else with it before insert.
3) And even more, what if i will need to create Users objects with relations, with other related objects?
In Phalcon those relationships are part of the entity. You can see in their docs this example:
// Create an artist
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';
// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; // Assign the artist
$album->year = 2008;
// Save both records
$album->save();
So to relate this back to your user example, suppose you wanted to store address information on the user but the addresses are stored in a different table. The builder could expose methods to define the address and the build method would create both entities together and return the built User object which has a reference to the Address object inside it because of how Phalcon models work.
I don't think it's entirely necessary to use a builder or "pattern" to dynamically populate your model properties. Though it is subjective to what you're after.
You can populate models through the constructor like this
$user = new Users([
'name' => $name,
'lastName' => $lastname,
'age' => $age,
]);
$user->create();
This way you can dynamically populate your model by building the array instead of numerous method calls.
It's also worth noting that if you want to use "setters" and "getter" methods you should define the properties as protected. The reason for this is because Phalcon will automatically call the set/get methods if they exist when you assign a value to the protected property.
For example:
class User extends \Phalcon\Mvc\Model
{
protected $name;
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$user= new MyModel();
$user->name = 'Cameron'; // This will invoke User::setName
echo $user->name; // This will invoke User::getName
It is also worth noting that the properties will behave as you'd expect a protected property to behave the same as a traditional protected property if the respective method is missing. For example, you cannot assign a value to a protected model property without a setter method.
It's more a "global understanding" question.
To save a model instance in the Database, we can use both:
SAVE()
$model = new Model;
$model->attribute = value;
$model->save();
https://laravel.com/docs/5.4/eloquent#inserts
and
::CREATE()
App\Model::create(['attribute'=>'value']);
https://laravel.com/docs/5.4/eloquent#mass-assignment
I supposed both of these methods belong to Illuminate\Database\Eloquent\Model, but I have found only function save there:
public function save(array $options = [])
{
$query = $this->newQueryWithoutScopes();
//......
return $saved;
}
But I haven't found any function Create in that file.
My QUESTIONS are:
1) what is the fundamental difference between
->method()
and
::method()
(is the last one a query builder?)
2) where can I find "::create()" method declared?
Thank you very much!
::method() is static calling without the need of creating an object of the class beforehand. ->method() you have to create an object before.
$car = new Car();
$car->color = 'red';
$car->save();
vs.
$car = Car::create(['color' => 'red']);
The create method can be found:
\Illuminate\Database\Eloquent\Builder::create
1)
->mehtod() is calling a Non-Static or Instantiated object method. Where as ::method() is calling on a static public method of a class.
To help describe this in your context. Take a look at how ::create() Operates. It returns an object that you can now use the save() method on after making changes. In the inverse, you cannot 'create' a model object from the save() method. You must have a model object first before executing ->save(). Which where ::create() comes in.
Eloquent ORM - Laravel : Insert, Update, Delete
2)
the create method is declared, I believe, in a higher level.
I need to update the Client table's budget column after inserting a new Budget into the database, but it doesn't. This is how I am doing inside of BudgetController::addAction():
if ($form->isValid()) {
$manager->persist($form->getData());
$manager->flush();
$Client = $manager->getReference('PanelBundle:Client', $form['client_id']->getData()->getId());
$Client->setBudget($manager->getRepository('PanelBundle:Budget')->getLastId());
$this->addFlash('success', 'Novo orçamento adicionado');
return $this->redirect($this->generateUrl('panel_budgets'));
}
The $Client declaration returns the Client name successfully, but the line where it sets the setBudget() seem not to work. I don't know how to make an update like this. I need it to update after inserting into Budget according to the selected Client id in Budget form.
Client and Budget are related to oneToMany and manyToOne, respectively, am I missing something because of this relationship?
If the Budget entity is a ManyToOne association of the Client, then you should be using ->addBudget() instead of a setter. It's also probably better to do a ->find() for the Client entity instead of a ->getReference(). If you really want to save the extra trip to the database, use the setter on the Budget entity instead to set the $client proxy created by the ->getReference(), i.e. $budget->setClient($client);. But it's not that expensive to find the Client and it ensures that the Client of that id exists. It would then also be a good idea to flush the manager again, just to make sure things are wrapped up cleanly, instead of assuming it will all happen without interruption as the kernel terminates. A complete rendition of your controller and action should look something like this:
namespace PanelBundle\Controller;
use PanelBundle\Entity\Budget;
use PanelBundle\Form\Type\BudgetType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class BudgetController extends Controller
{
public function addAction(Request $request)
{
$budget = new Budget();
$budgetForm = $this->createForm(new BudgetType(), $budget);
$budgetForm->handleRequest($request);
if ($budgetForm->isValid()) {
$manager = $this->getDoctrine()->getManager();
$manager->persist($budget);
$manager->flush();
$client = $manager->getRepository('PanelBundle:Client')
->find($budgetForm->get('client_id')->getData())
;
$client->addBudget($budget);
$manager->flush();
$this->addFlash('success', 'Novo orçamento adicionado');
return $this->redirect($this->generateUrl('panel_budgets'));
}
return $this->render(
'PanelBundle:Budget:add.html.twig',
array(
'budgetForm' => $budgetForm->createView(),
)
);
}
}
Disclaimer: n00b to Doctrine, not ORMs in general, or the data mapper pattern, just Doctrine. Not sure if I'm missing something (references to the documentation are welcome if they're specific! I've read thoroughly though) or there is another preferred approach. A big part of this question is how to do it the right way, not necessarily how to do it in general.
The deets: I've got a one to many association between two entities: Calendar and Event. One calendar can have many events. Relevant code:
Calendar Entity
<?php namespace MyPackage\Src {
use \Doctrine\Common\Collections\ArrayColleciton;
class Calendar {
/**
* #OneToMany(targetEntity="MyPackage\Src\Event", mappedBy="calendarInstance", cascade={"all"})
*/
protected $associatedEvents;
public function __construct(){
$this->associatedEvents = new ArrayCollection();
}
public function addEvent( Event $eventObj ){
$eventObj->setCalendarInstance($this);
$this->associatedEvents->add($eventObj);
}
public function getEvents(){
return $this->associatedEvents;
}
// Assume entityManager() returns... an entity manager instance
public function save(){
$this->entityManager()->persist($this);
$this->entityManager()->flush();
return $this;
}
}
}
Event Entity
<?php namespace MyPackage\Src {
class Event {
/**
* #ManyToOne(targetEntity="MyPackage\Src\Calendar", inversedBy="associatedEvents")
* #JoinColumn(name="calendarID", referencedColumnName="id", nullable=false)
*/
protected $calendarInstance;
public function setCalendarInstance( Calendar $calObj ){
$this->calendarInstance = $calObj;
}
// Assume entityManager() returns... an entity manager instance
public function save(){
$this->entityManager()->persist($this);
$this->entityManager()->flush();
return $this;
}
}
}
Semantics of the ORM state that "Doctrine will only check the owning side of an association for changes" (<- proof of reading docs). So doing the following works fine when creating a new event (assuming cascade persist is enforced by the Calendar, which I have set with "all"), and then gett'ing the ArrayCollection from the calendar instance.
<?php
// Assume this returns a successfully persisted entity...
$cal = Calendar::getByID(1);
// Create a new event
$event = new Event();
$event->setTitle('Wowza');
// Associate the event to the calendar
$cal->addEvent($event);
$cal->save();
// Yields expected results (ie. ArrayCollection is kept in-sync)
echo count($cal->getEvents()); // "1"
Meow then... Lets say for some funny reason I want to create an association from the event side, like so:
<?php
// Assume this returns a successfully persisted entity...
$cal = Calendar::getByID(1);
// Create a new event
$event = new Event();
$event->setTitle('Dewalt Power Tools');
// Associate VIA EVENT
$event->setCalendarInstance($cal);
$event->save();
// ArrayCollection in the Calendar instance is out of sync
echo count($cal->getEvents()); // "0"
Persisting the event in this manner does work (it gets saved to the DB) - but now the entity manager is out of sync when trying to access associated events from the Calendar. Further, its understandable why: in the setCalendarInstance() method of the Event class, all thats happening is
$this->calendarInstance = $calendar;
I've tried the following (which actually works, it just feels really naughty)
$this->calendarInstance = $calendar;
if( ! $this->calendarInstance->getEvents()->contains($this) ){
$this->calendarInstance->getEvents()->add($this);
}
BUT, this feels very strongly like I'm breaking proper encapsulation, which is important to me. Am I being silly, and its OK to ->add() to the returned ArrayCollection of the Calendar instance, outside of the Calendar class? (Thats what I mean this feels wrong; the ArrayCollection is a protected property of Calendar and I feel like it shouldn't be modified externally).
---- OR ----
Should I enforce creating the association only from the owning side (the Calendar), and not allow doing $event->setCalendarInstance() and then saving the event. Which again brings up another point on encapsulation, that setCalendarInstance must be a public method in order for the Calendar's addEvents() method to work (so how would I prevent some poor soul inheriting my codebase from doing it improperly)?
Thanks, interested to hear approaches to this.
** Edit **
Further funny business: Going the route of creating a new event, passing the calendar instance, and then persisting the new event - here is where discrepancies seem to exist:
// Get an existing Calendar that has one Event
$calObj = Calendar::getByID(1);
echo count($calObj->getEvents()); // "1"
// Add from event side
$event = new Event();
$event->setTitle('Pinkys Brain');
$event->setCalendarInstance($calObj);
$event->save();
// I would think this would be "2" now...
echo count($calObj->getEvents()); // Still "1"
BUT... this works, and feels shloppy.
// Get an existing Calendar that has one Event
$calObj = Calendar::getByID(1);
echo count($calObj->getEvents()); // "1"
// Add from event side
$event = new Event();
$event->setTitle('Pinkys Brain');
$event->setCalendarInstance($calObj);
$event->save();
// Clear the entity manager on the existing $calObj
$calObj->entityManager()->clear();
// Now get a NEW instance of the same calendar
$calObjAgain = Calendar::getByID(1);
echo count($calObjAgain->getEvents()); // "2"
i have two table:
News:
id
title
body
and
NewsCopy:
id
title
body
if i add new News i would like also add this same all data for table NewsCopy?
class News extends BaseNews
{
public function save(Doctrine_Connection $conn = null)
{
return parent::save($conn);
}
}
how can i make this simply?
Well, one possible way is to hook up into the Doctrine saving mechanism:
class News{
//..other declarations//
//executed after Save
public function postSave(){
$newsCopy = new NewsCopy();
//set the parameters manually
$newsCopy->id = $this->id;
$newsCopy->title = $this->title;
$newsCopy->body = $this->body;
//OR, even better, create a "cast constructor" the same idea
//$newsCopy = new NewsCopy($this);
$newsCopy->save();
}
}
See "Event Listeners" chapter for more detailed explanation
You can utilize the toArray() method of the existing and populated "News" record object and populate a separate CopyNews object. With the now newly configured object you can do the save with.
I assume doctrine 1.2 - and I do not have a testing environment - so no code :).
You could probably also play with the clone() method and set a new table name ...
All untested - sorry.
The best you can do is to use triggers