Using Symfony2/Doctrine ORM
I have an entity "ProductCategory".
ProductCategory ( fields )
id (int)
displayName (string)
I'm wanting to create a route with param converters. This is what I have. According to the documentation here it should work.
/**
* #Route("/products/{category_name}")
* #ParamConverter("c", class="AppBundle:ProductCategory", options={"mapping": { "displayName" = "category_name"}})
*/
public function viewProductPageAction(ProductCategory $c)
{
return $this->render('templates/view-product.html.twig');
}
So what's going on up there? I want to convert the route key "category_name" to a ProductCategory entity by it's field DisplayName and assign it to $c.
Seems easy enough. Assign the category_name to the displayName field in the mapping option. But I'm getting this error.
Unable to guess how to get a Doctrine instance from the request information. 500 - Logic exception
What is the reason I'm getting the error, and how am I misinterpreting the documentation? Thanks in advance.
Well you were close enough. Arguments passed to mapping are in reversed order. The right syntax you're looking for here is like this:
/**
* #Route("/products/{category_name}")
* #ParamConverter("c", class="AppBundle:ProductCategory", options={"mapping": { "category_name" = "displayName"}})
*/
Related
I would like give permission update/delete post/category for author who created post/category
I don't know, what I must give here as second parameter. I tried:
$post=new Post();
if (Yii::$app->user->can('updatePost',['Post'=>$post]))
but gets error Getting unknown property: common\models\Post::createdBy
My Class AuthorRule:
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* #param string|integer $user the user ID.
* #param Item $item the role or permission that this rule is associated with
* #param array $params parameters passed to ManagerInterface::checkAccess().
* #return boolean a value indicating whether the rule permits the role or permission it is associated with.
*/
public function execute($user, $item, $params)
{
return isset($params['Post']) ? $params['Post']->CreatedBy->id == $user : false;
}
}
UPDATE:
Post Model
RBAC Controller
AuthorRule
AuthItem Table
AuthItemChild Table
Auth Assignment
You're doing the right thing, passing the object to the rule.
Are you sure your Post model actually has the createdBy property? In your other piece of code you have CreatedBy.
Most likely that typo is the problem, or your Post model does not have that field, or it's called differently (created_by?)
Oh, one more thing, if CreatedBy is a relation, and the object does not exist, trying to get its attribute (id) will produce an error. Try something like if (isset($params['Post']->CreatedBy) && $params['Post']->CreatedBy->id == $user).
I have a very simply structured entity that contains a simple association
Database_Entity_Tenant
id (primary key)
parentId (id of the parent entry)
code (a simple identifier for the tenant, unique)
I defined parentId in my entity accordingly:
/**
* #Column(type="integer")
* #OneToOne(targetEntity="Tenant")
* #JoinColumn(name="parentTenantId", referencedColumnName="id")
* **/
protected $parentId;
This works fine - the generated database schema resembles my choices and its good.
Now i am writing my first method which basically has to return an array of all the tenants that are chained together, in reverse order (i use this for walking backward through a chain of tenants).
In order to do that i came up with the idea to use a while() loop.
$currentTenant = {DATABASE_ENTITY_TENANT}; // In my real code i fetch the entity object of the current tenant
$chain[] = $currentTenant;
$repository = Database::entityManager()->getRepository('Database_Entity_Tenant');
while(!$currentTenant->getParentId()){
$currentTenant = $repository->findOneBy(array(
'id' => $currentTenant->getParentId()
));
$chain[] = $currentTenant;
}
Any tenant that has no parent (such as the base tenant) will have no parent id (or null), so that would end the while loop.
Now all this may work, but it seems really rough to me. I am fairly new to Doctrine so i don't know much about it but i am sure there is some way to do this more elegantly.
QUESTION
Does Doctrine 2 provide me with any set of functions i could use to solve the above problem in a better way?
If not, then is there any other way to do this more elegantly?
If I'm not getting your problem wrong, you just need to find all the entries in your association table ordered by the parentId. In Doctrine2 you can do the following:
$currentTenant = {DATABASE_ENTITY_TENANT}; // assuming a valid entity
$repository = Database::entityManager()
->getRepository('Database_Entity_Tenant')
->createQueryBuilder('t')
->where('t.parentId IS NOT NULL')
->andWhere('t.parentId < :current') /* < or > */
->setParameter('current', $currentTenant->getParentId()->getId())
->orderBy('t.parentId', 'ASC') /* ASC or DESC, no array_reverse */
->getQuery()
->getResult();
/* At this point $repository contains all what you need because of Doctrine,
* but if you want a chain variable: */
$chain = array();
foreach ($repository as $tenant) {
$chain[] = $tenant->getCode(); // your tenant entity if your entity is mapped correctly
}
Hope this helps!
To keep the field level constraints at a central place (not replicate it in each form), I added the constraints in the entity. Like below (lets say its one of the fields of a user entity):
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, nullable=false)
*
* #Constraints\NotBlank(
* groups={"register", "edit"},
* message="email cannot be blank."
* )
* #Constraints\Email(
* groups={"register", "edit"},
* message="Please enter a valid email address."
* )
*
* #Expose
* #Groups({"list", "details"})
*/
private $email;
Now I need a way to expose this validation constraints for each field which is an annotation of "Symfony\Component\Validator\Constraints". Is there a way that I can get all the constraints for all fields in the entity, like:
$em->getValidationConstraints('MyBundle:EntityUser'); //em is the entity manager
//and it returns me all the fields with its name, type and any constraints
//attached to it as any array
Thanks in advance.
Gather Information
Before fixing a problem, it's good to know what you are talking about and gather some information.
Doctrine is an ORM, something that does nice things between a database and an object. It has nothing to do with validation, that is done by the Symfony2 Validator Component. So you need something else than the $em.
All constraints of a class are called 'metadata' and they are usually stored in Symfony\Component\Validator\Mapping\ClassMetadata. We have to find a class which accepts the name of a class and returns a ClassMetadata instance.
To load the constraints, the Symfony2 Validator component uses loaders.
The Solution
We can see that there is a Symfony\Component\Validator\Mapping\ClassMetadataFactory. A factory is always used to build a class from a specific argument. In this case, we know it will create a ClassMetadata and we can see that it accepts a classname. We have to call ClassMetadataFactory::getMetadataFor.
But we see it needs some loaders. We aren't going to do the big job of initializing this factory, what about using the service container? We can see that the container has a validator.mapping.class_metadata_factory service, which is exactly the class we need.
Now we have all of that, let's use it:
// ... in a controller (maybe a seperated class is beter...)
public function someAction()
{
$metadataFactory = $this->get('validator.mapping.class_metadata_factory');
$metadata = $metadataFactory->getMetadataFor('Acme\DemoBundle\Entity\EntityUser');
}
Now we have the metadata, we only need to convert that to an array:
// ...
$propertiesMetadata = $metadata->properties;
$constraints = array();
foreach ($propertiesMetadata as $propertyMetadata) {
$constraints[$propertyMetadata->name] = $property->constraints;
}
Now, $constraints is an array with all fields and their constraint data, something like:
Array (
...
[email] => Array (
[0] => <instance of NotBlank>
[1] => <instance of Email>
),
)
i am using the latest version of doctrine: 2.3
when you call a generated association function, the first time everything is fine:
$authors = $book->getBookToAuthors();
//$authors = array(5)
but the second time instead of returning the array of all associations it returns the last hydrated entity:
$authors = $book->getBookToAuthors();
//$authors = BookToAuthor entity
that happens even when there is nothing else happening:
$authors = $book->getBookToAuthors(); //will work
$authors = $book->getBookToAuthors(); //won't work
the function of getBookToAuthors() is:
public function getBookToAuthors()
{
return $this->bookToAuthors;
}
and the mapping is as follows:
/**
* #var BookToAuthor[]
*
* #OneToMany(targetEntity="BookToAuthor", mappedBy="book", cascade={"persist"})
* #JoinColumn(name="id", referencedColumnName="book_id", onDelete="cascade")
*/
private $bookToAuthors;
please advise. i don't know what to do... :-(
sorry sorry sorry
it was a mistake in the association target side.
the target had One-To-One association instead of Many-To-One
if you have this problem make sure the association type in both sides is matching
I have an model with a relation, and I want to instantiate a new object of the relations type.
Example: A person has a company, and I have a person-object: now I
want to create a company-object.
The class of the companyobject is defined in the relation, so I don't think I should need to 'know' that class, but I should be able to ask the person-object to provide me with a new instance of type company? But I don't know how.
This is -I think- the same question as New model object through an association , but I'm using PHPActiveRecord, and not the ruby one.
Reason behind this: I have an abstract superclass person, and two children have their own relation with a type of company object. I need to be able to instantiate the correct class in the abstract person.
A workaround is to get it directly from the static $has_one array:
$class = $this::$has_one[1]['class_name'];
$company = new $class;
the hardcoded number can of course be eliminated by searching for the association-name in the array, but that's still quite ugly.
If there is anyone who knows how this is implemented in Ruby, and how the phpactiverecord implementation differs, I might get some Ideas from there?
Some testing has revealed that although the "search my classname in an array" looks kinda weird, it does not have any impact on performance, and in use it is functional enough.
You can also use build_association() in the relationship classes.
Simplest way to use it is through the Model's __call, i.e. if your relation is something like $person->company, then you could instantiate the company with $company = $person->build_company()
Note that this will NOT also make the "connection" between your objects ($person->company will not be set).
Alternatively, instead of build_company(), you can use create_company(), which will save a new record and link it to $person
In PHPActiveRecord, you have access to the relations array. The relation should have a name an you NEED TO KNOW THE NAME OF THE RELATIONSHIP/ASSOCIATION YOU WANT. It doesn't need to be the classname, but the classname of the Model you're relating to should be explicitly indicated in the relation. Just a basic example without error checking or gritty relationship db details like linking table or foreign key column name:
class Person extends ActiveRecord\Model {
static $belongs_to = array(
array('company',
'class_name' => 'SomeCompanyClass')
);
//general function get a classname from a relationship
public static function getClassNameFromRelationship($relationshipName)
foreach(self::$belongs_to as $relationship){
//the first element in all relationships is it's name
if($relationship[0] == $relationshipName){
$className = null;
if(isset($relationship['class_name'])){
$className = $relationship['class_name'];
}else{
// if no classname specified explicitly,
// assume the clasename is the relationship name
// with first letter capitalized
$className = ucfirst($relationship);
}
return $className
}
}
return null;
}
}
To with this function, if you have a person object and want an object defined by the 'company' relationship use:
$className = $person::getClassNameFromRelationship('company');
$company = new $className();
I'm currently using below solution. It's an actual solution, instead
of the $has_one[1] hack I mentioned in the question. If there is a
method in phpactiverecord I'm going to feel very silly exposing
msyelf. But please, prove me silly so I don't need to use this
solution :D
I am silly. Below functionality is implemented by the create_associationname call, as answered by #Bogdan_D
Two functions are added. You should probably add them in the \ActiveRecord\Model class. In my case there is a class between our classes and that model that contains extra functionality like this, so I put it there.
These are the 2 functions:
public function findClassByAssociation($associationName)
Called with the name of the association you are looking for.
Checks three static vars (has_many,belongs_to and has_one) for the association
calls findClassFromArray if an association is found.
from the person/company example: $person->findClassByAssociation('company');
private function findClassFromArray($associationName,$associationArray)
Just a worker-function that tries to match the name.
Source:
/**
* Find the classname of an explicitly defined
* association (has_one, has_many, belongs_to).
* Unsure if this works for standard associations
* without specific mention of the class_name, but I suppose it doesn't!
* #todo Check if works without an explicitly set 'class_name', if not: is this even possible (namespacing?)
* #todo Support for 'through' associations.
* #param String $associationName the association you want to find the class for
* #return mixed String|false if an association is found, return the class name (with namespace!), else return false
* #see findClassFromArray
*/
public function findClassByAssociation($associationName){
//$class = $this::$has_one[1]['class_name'];
$that = get_called_class();
if(isset($that::$has_many)){
$cl = $this->findClassFromArray($associationName,$that::$has_many);
if($cl){return $cl;}
}
if(isset($that::$belongs_to)){
$cl = $this->findClassFromArray($associationName,$that::$belongs_to);
if($cl){return $cl;}
}
if(isset($that::$has_one)){
$cl = $this->findClassFromArray($associationName,$that::$has_one);
if($cl){return $cl;}
}
return false;
}
/**
* Find a class in a php-activerecord "association-array". It probably should have a specifically defined class name!
* #todo check if works without explicitly set 'class_name', and if not find it like standard
* #param String $associationName
* #param Array[] $associationArray phpactiverecord array with associations (like has_many)
* #return mixed String|false if an association is found, return the class name, else return false
* #see findClassFromArray
*/
private function findClassFromArray($associationName,$associationArray){
if(is_array($associationArray)){
foreach($associationArray as $association){
if($association['0'] === $associationName){
return $association['class_name'];
}
}
}
return false;
}