Symfony - Request data as integer to relational database - php

I have created a project that have three tables (hardwarePlacement , HardwareUnitType, hardwareUnit)
And created the entities / controllers for them, with get, post, put and delete.
And it works perfectly when i test the methods for hardwarePlacement and HardwareUnitType, but the last table "hardwareUnit" is a relational table to the other two. so i have Forign keys (hardwarePlacementId and HardwareUnitTypeId).
So when i from postman try to make a post request, i get the error: "that my setHardwareUnitTypeId and hardwarePlacementId must be of type integer".
In my HardwareUnit entity i have the following for the other tables:
#[ORM\ManyToOne(inversedBy: 'hardwareUnits')]
#[ORM\JoinColumn(nullable: false)]
private ?HardwareUnitType $hardwareUnitTypeId = null;
#[ORM\ManyToOne(inversedBy: 'hardwareUnits')]
#[ORM\JoinColumn(nullable: false)]
private ?HardwarePlacement $hardwarePlacementId = null;
public function getHardwareUnitTypeId(): ?HardwareUnitType
{
return $this->hardwareUnitTypeId;
}
public function setHardwareUnitTypeId(?HardwareUnitType $hardwareUnitTypeId): self
{
$this->hardwareUnitTypeId = $hardwareUnitTypeId;
return $this;
}
public function getHardwarePlacementId(): ?HardwarePlacement
{
return $this->hardwarePlacementId;
}
public function setHardwarePlacementId(?HardwarePlacement $hardwarePlacementId): self
{
$this->hardwarePlacementId = $hardwarePlacementId;
return $this;
}
And my create method in HardwareUnit controller:
#[Route('/hardwareUnit', name: 'hardwareUnit_new', methods: ['POST'])]
public function new(ManagerRegistry $doctrine, Request $request): JsonResponse
{
$entityManager = $doctrine->getManager();
$hardwareUnit = new HardwareUnit();
$hardwareUnit->setHardwareUnitTypeId($request->request->get('hardwareUnitTypeId'));
$hardwareUnit->setHardwarePlacementId($request->request->get('hardwarePlacementId'));
$hardwareUnit->setName($request->request->get('name'));
$hardwareUnit->setCreatedDate(new \DateTime());
$hardwareUnit->setEditedDate(new \DateTime());
$entityManager->persist($hardwareUnit);
$entityManager->flush();
return $this->json('Oprettet ny hardware unit id: ' . $hardwareUnit->getId());
}
I have tried retrieving request as intval:
$hardwareUnit->setHardwareUnitTypeId($request->request->get(intval('hardwareUnitTypeId')));
$hardwareUnit->setHardwarePlacementId($request->request->get(intval('hardwarePlacementId')));
But then i get the error that my post value for setHardwareUnitTypeId and setHardwarePlacementId is null
Any suggestions on how i can convert my request to int?
Here is an image of my postman, if it helps:

You're reading the error wrong. It states that the argument of setHardwareUnitTypeId should be of type HardwareUnitType, but that you're providing a string:
App\Entity\HardwareUnit::setHardwareUnitTypeId(): Argument #1 ($hardwareUnitTypeId) must be of type ?App\Entity\HardwareUnitType, string given ...
Looking at your code, the error is quite clear. In your "new" route, you're calling the functions like this:
$hardwareUnit->setHardwareUnitTypeId($request->request->get('hardwareUnitTypeId'));
$hardwareUnit->setHardwarePlacementId($request->request->get('hardwarePlacementId'));
The $request->request->get() method returns a string (or int|float|bool|null), as it's parsing request parameters. It's not "magically" returning objects of the correct type. You need to take that ID and fetch the correct entity.
So what you need to do is the following:
Get the repository for HardwareUnitType and HardwarePlacement. This can be done through dependency injection of the repository directly into your controller action "new". See the docs for more info. You could also use $entityManager that you already have, like this example from docs, to get the repository.
In your controller, use the repository to fetch the entity based on $request->request->get('hardwareUnitTypeId') and $request->request->get('hardwarePlacementId'). You should be able to use the repository's built in find method (see previous example from docs). You'd get something like this:
$entity = $repository->find((int) $request->request->get('...'));
Use the result from find as argument to your setters. Building on on the example at the previous list item: you'd get something like: $hardwareUnit->setHardwareUnitTypeId($entity)
I hope this will help you figuring out your problem. Let me know if you need more help!

Related

How should I check for dupplicate entity in an object collection?

I've been reading this post : Doctrine - how to check if a collection contains an entity
But I actually don't like the solution, as, doctrine already provide the contains() method, which have the advantage to keep logic directly into the object, and then to not load EXTRA_LAZY collections entirely.
So here a Cart Entity own a CartProduct collection as is :
/**
* ...
* #ORM\Entity(repositoryClass="App\Repository\CartRepository")
*/
abstract class Cart implements InheritanceInterface{
...
/**
* #ORM\OneToMany(targetEntity="CartProduct", mappedBy="cart", fetch="EXTRA_LAZY", cascade={"persist"})
*/
private Collection $cartProducts;
...
public function __construct()
{
$this->cartProducts = new ArrayCollection();
}
...
}
(CartProduct have to be an Entity look at this simplify EA model. That's a standard way to proceed for related entity holding extra fields)
Now I want to add a new ProductCart Entity to my Cart class.
So I'm adding this method (generated by Symfony make:entity) :
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->contains($cartProduct)) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
And then I test this code :
public function testAddCartProduct()
{
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
}
But when I run this test, it fail :
Failed asserting that actual size 2 matches expected size 1.
So I check, and the Cart.cartProducts Collection have two product which are exactly the same objects.
As it's an ArrayCollection, I suppose that it just use this method :
namespace Doctrine\Common\Collections;
class ArrayCollection implements Collection, Selectable {
...
public function contains($element)
{
return in_array($element, $this->elements, true);
}
So well, of course in this case it is just return false, And the objects are considered to be different.
So now, I wish I could use PersistentCollection instead of ArrayCollection when implementing the Collection object , because the PersistentCollection.contains() method looks better.
abstract class Cart implements InheritanceInterface{
...
public function __construct()
{
-- $this->cartProducts = new ArrayCollection();
++ $this->cartProducts = new PersistentCollection(...);
}
}
But this require an EntityManager as a parameter, so, seams a little bit overkill to give an EntityManager to an Entity object...
So I finally, I don't know what is the better way to check for a dupplicate entity inside a collection.
Of course, I could implement myself a thing like :
abstract class Cart implements InheritanceInterface{
...
public function addCartProduct(CartProduct $cartProduct): self
{
if(!$this->getCartProducts()->filter(
function (CartProduct $cp)use($cartProduct){
return $cp->getId() === $cartProduct->getId();
})->count()) {
$this->cartProducts->add($cartProduct);
$cartProduct->setCart($this);
}
return $this;
}
...
But it'll require to load every Entity and I really don't like the idea.
Personally I agree with your comment, I don't think the entity itself should have the responsibility to ensure there is no duplicate.
The entity cannot make a request like a repository could, and I don't see how you can be sure there is no duplicate in the database without querying it.
Calling contains will not trigger a fetch in your case, this means the collection will stay as is, which is not what you want anyway because you could have a previously persisted duplicate that will not be part of the collection because you marked it as EXTRA_LAZY.
You also don't want to fetch all the entities of the collection (and transform the results into objects) just to check if you have a collision.
So IMHO you should create a method in the repository of the entity to check for duplicates, a simple SELECT COUNT(id).
Then there is your real problem.
The way you make your test will never find a collision. When you do:
$cart = new ShoppingCart($this->createMock(ShoppingCartState::class));
$cart_product = new CartProduct();
$cart_product->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product);
$cart_product2 = new CartProduct();
$cart_product2->setProduct(new Product(self::NO_.'1', new Group('1')));
$cart->addCartProduct($cart_product2);
$this->assertCount(1, $cart->getCartProducts());
You are creating two instances of CartProduct, that's why the call to contains doesn't find anything.
Because contains checks for the object reference, not the content, like you can see in its implementation:
public function contains($element)
{
return in_array($element, $this->elements, true);
}
So in your test case what you're really testing is:
in_array(new CartProduct(), [new CartProduct()], true);
which will always return false.

Laravel - Passing parameter for patch request

Hi i am working on a update method for updating a profile, right now i am updating the profile by passing the model as a parameter but i am wanting to pass in the id of a profile so the route is patch('/profiles/podcast/{id}'' i am quite new to laravel so im wondering how do i modify the controller and phpunit test to update this way when grabbing objects in laravel?
Update function in the controller:
public function update(PodcastProfile $podcastProfile)
{
$this->user = Auth::user();
$this->podcastProfile = $podcastProfile;
if (!$this->hasPodcastProfile()) {
abort(400, "You don't have a podcast profile configured");
}
$this->validation();
$this->transaction();
return $this->podcastProfile->toJson();
}
This is the current route for the update method
Route::patch('/profiles/podcast/{podcastProfile}', 'PodcastProfileController#update');
This is the phpunit test case for the function
/**
* #test
*/
public function it_should_update_podcast_profile()
{
$podcastDetails = $this->handlePostRequestToController();
$this->json('patch', '/profiles/podcast/' . $podcastDetails['id'], $this->updateData)
->assertSuccessful();
$this->checkPodcastProfile($this->updateData, $podcastDetails['id']);
$this->checkGuestFormats($this->updateData['guest_format']);
$this->checkAvailability($this->updateData['availability']);
$this->checkEquipment($this->updateData['equipment']);
$this->checkCategories($this->updateData['categories']);
$this->checkLocation($this->updateData['country'], $this->updateData['city']);
}
Hej,
if I understand your question correctly you would just pass the id of that profile instead, just like you said:
Route::patch('/profiles/podcast/{podcastProfileId}','PodcastProfileController#update');
and then fetch the profile by that given id.
so something like:
$this->podcastProfile = App\PodcastProfile::find($podcastProfileId)
Also i feel like choosing the route-model-binding approach like #Remul described would be the better approach.

How to implement models factory in frameworks which using active records?

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.

Doctrine won't save one-to-many relationship

I'm being bugged by an issue that seems very very puzzling. FYI - I know and I have read most of the doctrine questions around here, so I know the basics of doctrine and specifying relationships.
Below is how my data model looks (posting relevant sections of code)
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*
protected $analyses
public function addAnalysis(Analysis $analysis)
{
$analyses->setSample($this);
$this->analyses[] = $analyses;
}
}
And Analysis
class Analysis
{
/**
* #ORM\ManyToOne(targetEntity="Sample", inverseBy="analyses", cascade={"persist"})
* #ORM\JoinColumn(name="sample_id", referencedColumnName="id")
*
protected $sample
public function setSample(Sample $sample)
{
$this->sample = $sample;
}
}
So one Sample can have multiple Analysis. While creating a new Analysis however, it is not letting me create one. It is throwing a NOT NULL constraint exception.
Below is the code I tried.
$analysis = new Analysis
$analysis->setUUID("seeebbbfg");
$analysis->setStatus(Analysis::STATUS_DONE);
$sample = $sample->addAnalysis($analysis)
$em->persist($sample);
$em->flush();
I have gone through many links and the doctrine documentation
Doctrine One-To-Many Relationship Won't Save - Integrity Constraint Violation
many-relationship-wont-save-integrity-constraint-violation
Symfony 2 doctrine persist doesn't work after updating Relationship Mapping
Doctrine entities relationship
After going through this Doctrine "A new entity was found through the relationship" error, I tried to persist $analysis before persisting sample, but it gave an 'a new entity was found' error and then this official doctrine documentation
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/association-mapping.html
Not sure what I'm missing. Can anyone shed any light on this?
UPDATE 1
[Doctrine\DBAL\Exception\NotNullConstraintViolationException]
An exception occurred while executing
INSERT INTO analysis (id, uuid, type, status, submission_at, sample_id) VALUES (?,?,?,?,?,?) with params [202066, "seeebbbfg", "temp", 2, "2016-5-22 12:16:39", null]
null value in column "sample_id" violates not-null constraint
I think you should add analysisto the Analyses collection before set Sample.
I guess $this->analyses is an ArrayCollection so, use the ArrayCollection::add() method to add a new object.
Please, try this part of code and let me know the result
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*/
protected $analyses
public function __construct()
{
$this->analyses = new ArrayCollection();
}
public function addAnalysis(Analysis $analysis)
{
$this->analyses->add($analysis);
$analysis->setSample($this);
return $this;
}
}
Credreak...
Thanks for this... Doctrine is not very clear how these are constructed.
I actually modified your code to create a instance of "related field" (analyese) as an Array Collection # _construct and then you can populate that Array with the actual Entity by passing it to the addAnalysis() function.
So the order of operations is: (as I understand it)
Construct an instance of Sample w/ an "related field" (analyese) Array
Construct an instance of Analysis and populate the Array
Pass the Analysis Array to the Sample analyese Array
Then flush the sample which will save the Analysis data to the database.
This worked for me.
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*/
protected $analyses
public function __construct()
{
$this->analyses = new ArrayCollection();
}
public function addAnalysis(Analysis $analysis)
{
$this->analyses->add($analysis);
$analyses->setSample($this);
return $this;
}
}

save for two table - doctrine and symfony

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

Categories