I have User entity which has country field as many to one reference:
/**
* #ORM\ManyToOne(targetEntity="Country")
*/
private $country;
/**
* Set country
*
* #param string $country
* #return User
*/
public function setCountry($country)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
I need current user to be represented as array, so in controller I get current user as $user = $this->getUser(); and use JMSSerializer and json_decode to convert object to array:
$userJSON = $serializer->serialize($user, 'json');
$user = json_decode($userJSON, true);
Now I have user object as an array, but instead of having country just as ID I get entire country object. What is the correct way to get country as ID in user object?
You have to use getter annotation for that purpose:
/** #Accessor(getter="getCountryName") */
private $country;
public function getCountryName()
{
return $this->country->getName(); // or which property for country entity is used to take its name
}
And do not forget to add JMS Annotations in use:
use JMS\Serializer\Annotation\Accessor;
Good luck.
Related
I am trying to pass id from one country table to user table but I can't' pass this error..
Expected value of type "ProjectBundle\Base\Entity\Country" for association field "ProjectBundle\Base\Entity\User#$country", got "string" instead.
My User entity class
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #Groups({"user_data"})
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Country")
* #JoinColumn(name="country_id", referencedColumnName="id")
*/
private $country;
/**
* #return mixed
*/
public function getCountry()
{
return $this->country;
}
/**
* #param mixed $country
*/
public function setCountry($country)
{
$this->country = $country;
}
My User service
public function registerUser($country)
{
$user = new User();
$user->setCountry($country);
$this->em->persist($user);
$this->em->flush();
return $user;
}
My User controller
public function registerUserAction()
{
$this->requirePostParams(['country_id']);
$country = $this->data['country_id'];
$user = $this->get('member')->registerUser($country);
return $this->success($user);
}
So I am passing country_id value via postman and I get this error.
The Problem is that
$user->setCountry()
expects an instance of your Country Entity. But you try to call it with the ID of an country. Doctrine does not fetch the Entity from the given ID.
To fix this you have two options:
fetch the country with the ID
in your user Service:
public function registerUser($countryId)
{
$country = $this->countryRepository->findById($countryId);
$user = new User();
$user->setCountry($country);
$this->em->persist($user);
$this->em->flush();
return $user;
}
You have to add the CountryRepository as a dependency in your service.
add the countryID column as a attribute to your UserEntity
in your User entity:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer", nullable=true)
*/
protected $countryId;
/**
* #return int
*/
public function getCountryId()
{
return $this->countryId;
}
/**
* #param int $countryId
*/
public function setCountryId($countryId)
{
$this->countryId = $countryId;
}
in your user Service:
public function registerUser($countryId)
{
$user = new User();
$user->setCountryId($countryId);
$this->em->persist($user);
$this->em->flush();
return $user;
}
$user->setCountry($country);
You have defined country as class in your USER entity so you need to assign country as object instead of string. You are passing string instead of country object.
Pass the country object in registerUser($country) instead of string, it won't give this exception.
<?php
/**
* #link http://www.yiiframework.com/
* #copyright Copyright (c) 2008 Yii Software LLC
* #license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\Component;
use yii\base\InvalidParamException;
/**
* BaseDataProvider provides a base class that implements the [[DataProviderInterface]].
*
* #property integer $count The number of data models in the current page. This property is read-only.
* #property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is
* uniquely identified by the corresponding key value in this array.
* #property array $models The list of data models in the current page.
* #property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination
* is disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and
* [[setPagination()]] for details.
* #property Sort|boolean $sort The sorting object. If this is false, it means the sorting is disabled. Note
* that the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details.
* #property integer $totalCount Total number of possible data models.
*
* #author Qiang Xue <qiang.xue#gmail.com>
* #since 2.0
*/
abstract class BaseDataProvider extends Component implements DataProviderInterface
{
/**
* #var string an ID that uniquely identifies the data provider among all data providers.
* You should set this property if the same page contains two or more different data providers.
* Otherwise, the [[pagination]] and [[sort]] may not work properly.
*/
public $id;
private $_sort;
private $_pagination;
private $_keys;
private $_models;
private $_totalCount;
/**
* Prepares the data models that will be made available in the current page.
* #return array the available data models
*/
abstract protected function prepareModels();
/**
* Prepares the keys associated with the currently available data models.
* #param array $models the available data models
* #return array the keys
*/
abstract protected function prepareKeys($models);
/**
* Returns a value indicating the total number of data models in this data provider.
* #return integer total number of data models in this data provider.
*/
abstract protected function prepareTotalCount();
/**
* Prepares the data models and keys.
*
* This method will prepare the data models and keys that can be retrieved via
* [[getModels()]] and [[getKeys()]].
*
* This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
*
* #param boolean $forcePrepare whether to force data preparation even if it has been done before.
*/
public function prepare($forcePrepare = false)
{
if ($forcePrepare || $this->_models === null) {
$this->_models = $this->prepareModels();
}
if ($forcePrepare || $this->_keys === null) {
$this->_keys = $this->prepareKeys($this->_models);
}
}
/**
* Returns the data models in the current page.
* #return array the list of data models in the current page.
*/
public function getModels()
{
$this->prepare();
return $this->_models;
}
/**
* Sets the data models in the current page.
* #param array $models the models in the current page
*/
public function setModels($models)
{
$this->_models = $models;
}
/**
* Returns the key values associated with the data models.
* #return array the list of key values corresponding to [[models]]. Each data model in [[models]]
* is uniquely identified by the corresponding key value in this array.
*/
public function getKeys()
{
$this->prepare();
return $this->_keys;
}
/**
* Sets the key values associated with the data models.
* #param array $keys the list of key values corresponding to [[models]].
*/
public function setKeys($keys)
{
$this->_keys = $keys;
}
/**
* Returns the number of data models in the current page.
* #return integer the number of data models in the current page.
*/
public function getCount()
{
return count($this->getModels());
}
/**
* Returns the total number of data models.
* When [[pagination]] is false, this returns the same value as [[count]].
* Otherwise, it will call [[prepareTotalCount()]] to get the count.
* #return integer total number of possible data models.
*/
public function getTotalCount()
{
if ($this->getPagination() === false) {
return $this->getCount();
} elseif ($this->_totalCount === null) {
$this->_totalCount = $this->prepareTotalCount();
}
return $this->_totalCount;
}
/**
* Sets the total number of data models.
* #param integer $value the total number of data models.
*/
public function setTotalCount($value)
{
$this->_totalCount = $value;
}
/**
* Returns the pagination object used by this data provider.
* Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
* of [[Pagination::totalCount]] and [[Pagination::pageCount]].
* #return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled.
*/
public function getPagination()
{
if ($this->_pagination === null) {
$this->setPagination([]);
}
return $this->_pagination;
}
/**
* Sets the pagination for this data provider.
* #param array|Pagination|boolean $value the pagination to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the pagination object. The "class" element defaults
* to 'yii\data\Pagination'
* - an instance of [[Pagination]] or its subclass
* - false, if pagination needs to be disabled.
*
* #throws InvalidParamException
*/
public function setPagination($value)
{
if (is_array($value)) {
$config = ['class' => Pagination::className()];
if ($this->id !== null) {
$config['pageParam'] = $this->id . '-page';
$config['pageSizeParam'] = $this->id . '-per-page';
}
$this->_pagination = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Pagination || $value === false) {
$this->_pagination = $value;
} else {
throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.');
}
}
/**
* Returns the sorting object used by this data provider.
* #return Sort|boolean the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort()
{
if ($this->_sort === null) {
$this->setSort([]);
}
return $this->_sort;
}
/**
* Sets the sort definition for this data provider.
* #param array|Sort|boolean $value the sort definition to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the sort definition object. The "class" element defaults
* to 'yii\data\Sort'
* - an instance of [[Sort]] or its subclass
* - false, if sorting needs to be disabled.
*
* #throws InvalidParamException
*/
public function setSort($value)
{
if (is_array($value)) {
$config = ['class' => Sort::className()];
if ($this->id !== null) {
$config['sortParam'] = $this->id . '-sort';
}
$this->_sort = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Sort || $value === false) {
$this->_sort = $value;
} else {
throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.');
}
}
/**
* Refreshes the data provider.
* After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
* they will re-execute the query and return the latest data available.
*/
public function refresh()
{
$this->_totalCount = null;
$this->_models = null;
$this->_keys = null;
}
}
The code above is the BaseDataProvider for yii2. My question is how i can set the _models and _keys in yii2? Which file do i need to change to link to that? Sorry i am quite new to yii. Please provide an example if possible thank you.
That what's You pasted here is abstract Yii2 class, which You should NEVER edit.
To use this thing i suggest You to read about ActiveDataProvider here: Docs
$query = Post::find()->where(['status' => 1]);
$provider = new ActiveDataProvider([
'query' => $query,
]);
Here's an example how to use it, first line defines data which will be used to populate ActiveDataProvider (it's a SQL query), and then You create ActiveDataProvider instance with query as config parameter.
I am trying to figure out if it is possible to use PHPdoc to define the object properties being returned by a function or a object method.
Say I have the following class:
class SomeClass {
public function staffDetails($id){
$object = new stdClass();
$object->type = "person";
$object->name = "dave";
$object->age = "46";
return $object;
}
}
Now, it is easy enough to define input parameters.
/**
* Get Staff Member Details
*
* #param string $id staff id number
*
* #return object
*/
class SomeClass {
public function staffDetails($id){
$object = new stdClass();
$object->type = "person";
$object->name = "dave";
$object->age = "46";
return $object;
}
}
The question is is there a similar thing for defining properties of the output object (of a stdClass) returned by the method in question. So that another programmer does not have to open this class and manually look into the method to see what the return object is returning?
Here it is 4 years later, and there still does not appear to be a way to annotate the properties of a stdClass object as originally described in your question.
Collections had been proposed in PSR-5, but that appears to have been shot down: https://github.com/php-fig/fig-standards/blob/211063eed7f4d9b4514b728d7b1810d9b3379dd1/proposed/phpdoc.md#collections
It seems there are only two options available:
Option 1:
Create a normal class representing your data object and annotate the properties.
class MyData
{
/**
* This is the name attribute.
* #var string
*/
public $name;
/**
* This is the age attribute.
* #var integer
*/
public $age;
}
Option 2:
Create a generic Struct type class as suggested by Gordon and extend it as your data object, using the #property annotation to define what generic values are possible to access with __get and __set.
class Struct
{
/**
* Private internal struct attributes
* #var array
*/
private $attributes = [];
/**
* Set a value
* #param string $key
* #param mixed $value
*/
public function __set($key, $value)
{
$this->attributes[$key] = $value;
}
/**
* Get a value
* #param string $key
* #return mixed
*/
public function __get($key)
{
return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
}
/**
* Check if a key is set
* #param string $key
* #return boolean
*/
public function __isset($key)
{
return isset($this->attributes[$key]) ? true : false;
}
}
/**
* #property string $name
* #property integer $age
*/
class MyData extends Struct
{
// Can optionally add data mutators or utility methods here
}
You have only two way to document the structure of the result class.
1.One can describe the structure in a comment text. For example:
class SomeClass
{
/**
* Getting staff detail.
* Result object has following structure:
* <code>
* $type - person type
* $name - person name
* $age - person age
* </code>
* #param string $id staff id number
*
* #return stdClass
*
*/
public function staffDetails($id){
$object = new stdClass();
$object->type = "person";
$object->name = "dave";
$object->age = "46";
return $object;
}
}
2.One can create a data type that will inheritance stdClass and it will have an annotation of a result object. For example:
/**
* #property string $type Person type
* #property string $name Person name
* #property integer $age Person age
*/
class DTO extends stdClass
{}
And use it in your other classes
class SomeClass {
/**
* Getting staff detail.
*
* #param string $id staff id number
*
* #return DTO
*
*/
public function staffDetails($id){
$object = new DTO();
$object->type = "person";
$object->name = "dave";
$object->age = "46";
return $object;
}
}
In my opinion, this way is better than a description in the text comment because it makes the code more obvious
If you are using PHP 7, you can define anonymous class.
class SomeClass {
public function staffDetails($id){
$object = (new class() extends stdClass {
public /** #var string */ $type;
public /** #var string */ $name;
public /** #var int */ $age;
});
$object->type = "person";
$object->name = "dave";
$object->age = 46;
return $object;
}
}
It is working for my IDE (tested in NetBeans)
With for example json_decode it's harder to use own classes instead of stdClass, but in my case I just created dummy file with class definitions, which really isn't loaded and I'm adding own classes as #return (works for intelephense on vscode).
PHPdocObjects.php
/**
* class only for PHPdoc (do not include)
*/
class Member {
/** #var string */
public $type;
/** #var string */
public $name;
/** #var string */
public $age;
}
/**
* Other format
*
* #property string $type;
* #property string $name;
* #property string $age;
*/
class MemberAlt {}
SomeClass.php
/**
* Get Staff Member Details
*
* #param string $id staff id number
*
* #return Member I'm in fact stdClass
*/
class SomeClass {
public function staffDetails($id){
$object = json_decode('{"type":"person","name":"dave","age":"46"}');
return $object;
}
}
The hack I use for autocomplete in PhpStorm:
Create some meta file which will contain some classes to describe your structures. The file is never included and structures have their own name rules in order not to mess them with real existing classes:
<?php
/*
meta.php
never included
*/
/**
* #property string $type
* #property string $name
* #property string $age
*/
class StaffDetails_meta {}
Use the meta class as a return value in your real code PHPDoc:
<?php
/*
SomeClass.php
eventually included
*/
class SomeClass
{
/**
* Get Staff Member Details
*
* #param string $id staff id number
*
* #return StaffDetails_meta
*/
public function staffDetails($id)
{
$object = new stdClass();
$object->type = "person";
$object->name = "dave";
$object->age = "46";
return $object;
}
}
Congratulations, this will make your IDE autocomplete your code when you'd typing something like (new SomeClass)->staffDetails('staff_id')->
P.S.: I know, almost 10 years passed but still actual
I do not understad why with some Entity objects I can set the Id and for others objects I get an error and says me that the Id can't be null and I have to pass an object instead.
e.g.:
$log = new Log();
$log->setTypeId(1);
$log->setUserId(1);
$entityManager->persist($log);
$entityManager->flush();
If I try the code above I get error that says: Integrity constraint violation: 1048 Column 'user_id' cannot be null. And I have to first create the Type Object and de User object and the pass them:
$log->setType($TypeObject)
$log->setUser($UserObject)
But for other entity objects I have no problem assigning the value directly, why is that?
This is my Entity Log:
<?php
/**
* #Entity
* #Table(name="log")
* #HasLifecycleCallbacks
*/
class Log
{
/**
* #var type
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
*
* #var type
* #Column(type="integer")
*/
protected $user_id;
/**
*
* #var type
* #Column(type="integer")
*/
protected $type_id;
/**
*
* #var type
* #Column(type="datetime")
*/
protected $created;
/**
*
* #var type
* #ManyToOne(targetEntity="User", inversedBy="logs")
*/
protected $user;
/**
*
* #ManyToOne(targetEntity="Type", inversedBy="logs")
*/
protected $type;
public function getId()
{
return $this->id;
}
public function getUserId()
{
return $this->user_id;
}
public function getTypeId()
{
return $this->type_id;
}
public function getCreated()
{
return $this->created;
}
public function setUserId($userId)
{
$this->user_id = $userId;
}
public function setTypeId($typeId)
{
$this->type_id = $typeId;
}
public function setCreated($created)
{
$this->created = $created;
}
public function setUser($user)
{
$this->user = $user;
}
public function setType($type)
{
$this->type = $type;
}
/**
* #PrePersist
*/
public function prePersist()
{
$this->setCreated(new DateTime());
}
}
?>
The existing answer never did sit well with me. There are many valid scenarios where loading an object just to define the relationship while already having the FK handy just does not make any sense at all.
A better solution is to use Doctrine's EntityManager's getRefrence method.
Reference Proxies...
The method EntityManager#getReference($entityName, $identifier) lets
you obtain a reference to an entity for which the identifier is known,
without loading that entity from the database. This is useful, for
example, as a performance enhancement, when you want to establish an
association to an entity for which you have the identifier. You could
simply do this:
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference(\MyProject\Model\Item::class, $itemId);
$cart->addItem($item);
Maybe this was not available when this question was first posted - I don't know.
EDIT
I found this statement on the website of Doctrine2. It's a best practice that you might want to follow when coding your models.
Doctrine2 Best Practices
25.9. Don’t map foreign keys to fields in an entity
Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do
EDIT
Doctrine does the mapping from your objects to their respective Ids.
What you've done here is a bit redundant.
You've essentially told doctrine the same thing twice.
You've told it that it has a 'user_id' column AND that it also has a User object, which are the same thing. But doctrine can already guess that this relationship will have a user_id column based on the fact that the log class has a user object inside.
You should simply do the following instead
<?php
/**
* #Entity
* #Table(name="log")
* #HasLifecycleCallbacks
*/
class Log
{
/**
* #var type
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
*
* #var type
* #Column(type="datetime")
*/
protected $created;
/**
*
* #var type
* #ManyToOne(targetEntity="User", inversedBy="logs")
*/
protected $user;
/**
*
* #ManyToOne(targetEntity="Type", inversedBy="logs")
*/
protected $type;
public function getId()
{
return $this->id;
}
public function getCreated()
{
return $this->created;
}
public function setCreated($created)
{
$this->created = $created;
}
public function setUser($user)
{
$this->user = $user;
}
public function setType($type)
{
$this->type = $type;
}
/**
* #PrePersist
*/
public function prePersist()
{
$this->setCreated(new DateTime());
}
}
Doctrine will worry about the user_id and type_id on it's own. You don't have to worry about it. This way you get to work with full fledged objects, making it easier to program, instead of having to worry about id's. Doctrine will handle that.
If ALL you have is an id, because that's what you're using on the front end, then just fetch the object associated with that id using the Entitymanager.
$user = $em->getEntity( 'User', $idFromWeb );
$log = new Log();
$log->setUser( $user );
I have created a form that appears to be correct, it has a few text fields and a select box with a list of countries pulled from a table of countries I have. The select box displays correctly using the the correct values for it's 'value' and display text. When I submit the form however I get an exception:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'countryid' cannot be null
If I set the database table (in PHPMyAdmin) to allow a null value for the countryid field it enters the record with no exception but the entry for the countryid is null.
my controller has the following code:
$duck = new \Wfuk\DuckBundle\Entity\Ducks();
$form = $this->createFormBuilder($duck)
->add('city', 'text')
->add('countryid', 'entity', array('class' => 'WfukDuckBundle:Country', 'property' => 'country'))
// cut other fields
->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
$errors = $this->get('validator')->validate( $form );
echo $duck->getCountryid();
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($duck);
$em->flush();
return $this->redirect($this->generateUrl('upload_duck_success'));
}
the echo in there returns the __toString function of the country object which seems a bit odd - but it is the full country info for the country chosen in the form.
in the Ducks.php class:
/**
* #var string $countryid
*
* #ORM\Column(name="countryid", type="string", length=2, nullable=false)
*/
private $countryid;
/**
* Set countryid
*
* #param string $countryId
*/
public function setCountryid($countryid)
{
$this->countryid = $countryid;
}
/**
* Get countryid
*
* #return string
*/
public function getCountryid()
{
return $this->countryid;
}
This is my first symfony project, but I've been over the docs several times and think I have everything set up ok...
edit:
I have a join set up as follows:
Ducks.php
/**
* #ORM\ManyToOne(targetEntity="Country", inversedBy="ducks")
* #ORM\JoinColumn(name="countryid", referencedColumnName="id")
*/
private $country;
/**
* Set country
*
* #param string $country
*/
public function setCountry($country)
{
$this->country = $country;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
and on the Country.php side:
/**
* #ORM\OneToMany(targetEntity="Ducks", mappedBy="country")
*/
protected $ducks;
public function __construct()
{
$this->ducks = new ArrayCollection();
}
/**
* Get ducks
*
* #return Doctrine\Common\Collections\Collection
*/
public function getDucks()
{
return $this->ducks;
}
What's happening is that the form is sending an actual Country object to ducks. You can confirm this with:
public function setCountryid($countryid)
{
if (is_object($countryid)) die('Yep, got a country object.');
$this->countryid = $countryid;
}
It sounds like you only want to store a 2 char country code? You don't want an actual relation? If so then this might do the trick:
public function setCountryid($countryid)
{
if (is_object($countryid)) $countryid = $countryid->getId();
$this->countryid = $countryid;
}
If you want an actual normal Doctrine managed relation between duck and country then something like:
/**
* #ORM\ManyToOne(targetEntity="Country")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
*/
private $country;
And adjust your getter/setters accordingly.
It's a bit strange that you seem to have both yml and annotations. From what I understood, you could use one or the other in a given bundle.