This is probably pretty simple, but I can't find a way to do this.
Is there any way to get a list of class names of the entities that Doctrine manages? Something like:
$entities = $doctrine->em->getEntities();
where $entities is an array with something like array('User', 'Address', 'PhoneNumber') etc...
I know this question is old, but in case someone still needs to do it (tested in Doctrine 2.4.0):
$classes = array();
$metas = $entityManager->getMetadataFactory()->getAllMetadata();
foreach ($metas as $meta) {
$classes[] = $meta->getName();
}
var_dump($classes);
Source
Another way to get the class names of all entities (with namespace) is:
$entitiesClassNames = $entityManager->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
Unfortunately not, your classes should be organized in the file structure though. Example: a project i'm working on now has all its doctrine classes in an init/classes folder.
There is no built function. But you can use marker/tagger interface to tag entity classes that belong to your application. You can then use the functions "get_declared_classes" and "is_subclass_of" find the list of entity classes.
For ex:
/**
* Provides a marker interface to identify entity classes related to the application
*/
interface MyApplicationEntity {}
/**
* #Entity
*/
class User implements MyApplicationEntity {
// Your entity class definition goes here.
}
/**
* Finds the list of entity classes. Please note that only entity classes
* that are currently loaded will be detected by this method.
* For ex: require_once('User.php'); or use User; must have been called somewhere
* within the current execution.
* #return array of entity classes.
*/
function getApplicationEntities() {
$classes = array();
foreach(get_declared_classes() as $class) {
if (is_subclass_of($class, "MyApplicationEntity")) {
$classes[] = $class;
}
}
return $classes;
}
Please note that my code sample above does not use namespaces for the sake simplicity. You will have to adjust it accordingly in your application.
That said you did't explain why you need to find the list of entity classes. Perhaps, there is a better solution for what your are trying to solve.
Related
I have an app and I want to allow modules in it. I find nWidart /Laravel-modules is the best solution for this. I have used it in the past, but in my previous projects I was the sole developer, so when I created a model inside a module, to create the relationships between it and one of my base models, I just went and edit both files:
In App\Models\Disease I would add a new method:
public function symptoms(){
return $this->hasMany( Modules\Treatments\Entities\Symptom::class );
}
In Modules\Treatments\Entities\Symptom, the opposite:
public function disease(){
return $this->belongsTo( App\Models\Symptom::class );
}
Now, I would like to create those relationships, but without writing code in the App\Models files (of course I know there would be some modification required to make it work, I just mean without having to edit the files every time a module is created). Is there a way to do it? Is it possible to work that out?
Ok, in the module Entities I created a new model Modules\Treatments\Entities\Disease which extends from App\Models\Disease and I inserted the relationship method there.
use App\Models\Disease as BaseDisease;
class Disease extends BaseDisease{
/**
* Relationship between a Disease and it's symptoms
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function symptoms(){
return $this->hasMany( Modules\Treatments\Entities\Symptom::class );
}
}
The down side of this approach is that the relationship is only present when I load the Disease from Modules\Treatments\Entities\Disease but I couldn't find another way to do it
I have an entity called event, the event can have many rooms and a room can have many participants.
If I access all events (with a specific user) I can filter events where the user has no access right (no room with a connection to the specific user) by using extensions.
That works fine.
The response contains all events which have at least one room with access rights.
But If the event has multiple rooms and the user has only access to one room. The response includes both rooms. I created a RoomExtension, but this class will not be invoked.
Thanks
Your problem is caused by the fact that filters and extensions only work on the query that retrieves the primary entities. The related entities are retrieved using Doctrines associations wich are part of the domain model that is meant to be the single source of truth for all purposes. What you need is a user-specic view on that model, which in the context of api's usually consists of DTOs.
I think there are basically two solutions:
Query primarily for Events and convert the into EventDTOs, then either query for - or filter out - the related Rooms,
Query primarily for Rooms, then group them into EventDTOs.
I explain the second solution here because i guesss that it is simpeler and it shoud make your RoomExtension work out of the box, which makes it the better fit to your question, but also because i happen to have built and tested something similar in a tutorial so it is a lot less work to write an answer with confidence.
The downside of this solution is that it does not support pagination.
Bucause this solution primarily queries for Rooms, the the operation is on the Room resource. If it where the only collectionOperation of Room it could be like this:
(..)
* #ApiResource(
* collectionOperations={
* "get_accessible_events"={
* "method"="GET",
* "path"="/rooms/accessible-events",
* "output"=EventDTO::class,
* "pagination_enabled"=false
* }
* }
* }
*/
class Room {
(..)
(This does not have to be your only collectionOperation, you can still have "get", "post" and others).
Right now this still produces a flat collection of Rooms, you need to group them into EventDTOs. The DTOs page of the docs suggest to make a DataTransformer to produce the DTOs, but that only works if your DTOs are one to one with the entities retrieved by the query. But a CollectionDataProvider can do the trick. Because you do not need to adapt the query itself you can simply decorate the default CollectionDataProvider service:
namespace App\DataProvider;
use ApiPlatform\Core\Api\OperationType;
use App\DTO\EventDTO;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Room;
class RoomAccessibleEventCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
/** #var CollectionDataProviderInterface */
private $dataProvider;
/**
* #param CollectionDataProviderInterface $dataProvider The built-in orm CollectionDataProvider of API Platform
*/
public function __construct(CollectionDataProviderInterface $dataProvider)
{
$this->dataProvider = $dataProvider;
}
/**
* {#inheritdoc}
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Room::class === $resourceClass
&& $operationName == 'get_accessible_events';
}
/**
* {#inheritdoc}
*/
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): array
{
$rooms = $this->dataProvider->getCollection($resourceClass, $operationName, $context);
$dtos = [];
foreach ($rooms as $room) {
$key = $room->getId();
if (isset($dtos[$key])) {
$dtos[$key]->addRoom($room);
} else {
$dto = new EventDTO($room->getEvent());
$dto->addRoom($room);
$dtos[$key] = $dto;
}
}
return $dtos;
}
}
You do need to configure the service in config/services.yaml:
'App\DataProvider\RoomAccessibleEventCollectionDataProvider':
arguments:
$dataProvider: '#api_platform.doctrine.orm.default.collection_data_provider'
This does not replace the default CollectionDataProvider but adds another one that gets the default one injected.
I guess you can make the EventDTO class yourself now. Then it should work. Filters defined on Room will also work as usual, for example if rooms can be filtered by the date of their event ?event.date[gte]=2020-10-10 will only find rooms with events on or after 2020-10-10 and return their EventDTO's.
However, in the swagger docs the get_accessible_events operations summary and descriptions still come from Room. You can look up how to add a SwaggerDecorator in the docs or take a look at the chapter9-api branch of the tutorial. The latter also contains complete explanations and tested code for entities, the DTO (Report Model) and an extension for only showing data the user is authorized for, but is not taylored to your questions and would all together be way beyond what a to the point answer.
I can not give you any more hints on this site with respect the other solution because this site will probably see them as an incomplete or unclear answer and punish me for it.
I'm new to API Platform. I think it's great but I cannot find any example how to create custom endpoint that isn't based on any entity. There are a lot of examples based on an entity and usually they are all about CRUD. But what about custom operations?
I need to create custom search through database with some custom parameters which aren't related to any entity.
E.g. I want to receive POST request something like this:
{
"from": "Paris",
"to": "Berlin"
}
This data isn't saved to db and I haven't entity for it.
After I receive this data, there should be a lot of business logic including db queries through a lot of db tables and also getting data from external sources.
Then, after the business logic is finished, I want to return back result which is also custom and isn't related to any entity.
E.g.
{
"flights": [/* a lot of json data*/],
"airports": [/* a lot of json data*/],
"cities": [/* a lot of json data*/],
.......
}
So, I think I'm not the only on who does something similar. But I really cannot find a solution or best practices how to do this.
In the documentation I've found at least three approaches and I cannot implement none of them.
The best one, I guess the most suitable for me it is using Custom Operations and Controllers. But documentation says this one is not recommended. Also I think I should use DTOs for request and response, but for this approach I'm not sure I can use them.
The second one I found it's using Data Transfer Objects, but this approach requires an entity. According to the documentation, I should use DTOs and DataTransformers to convert DTO to an Entity. But I don't need entity, I don't need save it to db. I want just handle received DTO on my own.
The third one I guess it is using Data Providers, but I'm not sure it is suitable for my requirements.
So, the main question is which approach or best practice should I use to implement custom operation which isn't related to any entity. And it will be great use DTOs for request and response.
You are not forced to use entities. Classes that are marked with #ApiResource annotation may not be entities. Actually, if your application is smarter than basic CRUD you should avoid marking entities as ApiResource.
Since you want to use POST HTTP method (which is for creating resource items) you can do something like this.
1) Define class describing search fields and which will be your #ApiResource
<?php
// src/ApiResource/Search.php
namespace App\ApiResource;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use App\Dto\SearchResult;
/**
* #ApiResource(
* itemOperations={
* "get"={
* "controller"=NotFoundAction::class,
* "read"=true,
* "output"=false,
* },
* },
* output=SearchResult::class
* )
*/
class Search
{
/**
* #var string
* #ApiProperty(identifier=true)
*/
public $from;
/** #var string */
public $to;
}
2) Define DTO that will represent the output
<?php
// src/Dto/SearchResult.php
namespace App\Dto;
class SearchResult
{
public $flights;
public $airports;
public $cities;
}
3) Create class that will inplement DataPersisterInterface for handling business logic.
It will be called by framework because you make POST request.
<?php
// src/DataPersister/SearchService.php
declare(strict_types=1);
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\SearchResult;
use App\ApiResource\Search;
final class SearchService implements DataPersisterInterface
{
public function supports($data): bool
{
return $data instanceof Search;
}
public function persist($data)
{
// here you have access to your request via $data
$output = new SearchResult();
$output->flights = ['a lot of json data'];
$output->airports = ['a lot of json data'];
$output->cities = ['inputData' => $data];
return $output;
}
public function remove($data)
{
// this method just need to be presented
}
}
That way you will recieve results based on request.
I noticed that I write the database table names quite a lot, and in different files, when I use the Query Builder. If I were to change the database table names, I would have to search and change quite many rows in my project.
Is this an issue your Laravel guys noticed and come up with an solution to?
I like the Eloquent approach which uses class models, instead of database names; but for some queries I think the Query Builder is a better solution (though I am no expert in this matter).
If you already have a queryBuilder object you can obtain the table name like
$tableName = $query->getModel()->getTable();
Use this in your query :
(new YourModel())->getTable()
Example :
DB:raw('SELECT * FROM '.(new User())->getTable().' WHERE id=3');
How about using OOP concept. Laravel is a framework, so no one stops you from using basic PHP OOP concept. This is what I do:
Consider my query is like :
$result=DB::table('myTable')->select()->get();
What I do is make a class that holds all the tablenames :
class TableName
{
private $tableName= "myTable";
public function getTableName()
{
return $this->tableName;
}
public function setTableName($table_name)
{
$this->tableName = $table_name;
}
}
Now all i have to do is call a method using an object in the file I want to use the table like :
$name = new TableName() ;
$result=DB::table($name->getTableName())->select()->get();
Use wherever you want. I don't think its the best solution however it works for me. Hope it helps
Maybe you can extend the model class.
CModel extend Model {
protected static $tableName;
public static getTableName(){
if(static::$tableName)
return static::$tableName;
/* if you create a "reference break" you don't have to *
/* create "protected static $tableName" row in your all model */
$table = (new static())->getTable();
return static::$tableName = &$table;
}
}
YourModel extends CModel {...}
than you can use
YourModel::getTableName()
I'm not have better idea.
I have an Entity that has bidirectional ManyToOne/OneToMany relationship with another entity:
class BookShelf {
/**
* #OneToMany(targetEntity="Book", mappedBy="shelf", cascade={"persist"})
*/
public $books;
}
class Book {
/**
* #ManyToOne(targetEntity="BookShelf", inversedBy="books", cascade={"persist"})
*/
public $shelf;
}
I'm trying to create a new book, and have that object be listed in the bookshelf.
$book = new Book();
$book->shelf = $shelf;
$em->persist($book); $em->flush();
$shelf->showBooks();
After that, the $shelf->books does not contain the book, but instead it contains NULL. However the book is correctly inserted into the database, and when I run $shelf->showBooks() on another pageload, the book is listed properly.
I tried adding $em->refresh($book) and $em->refresh($shelf) but it doesn't help, the association still isn't refreshed.
Doctrine manual does suggest that I could use $shelf->books->add($book) to manually synchronize the association, but since there are initially no books, $shelf->books is NULL and I cannot call any methods on it.
How can I make Doctrine reload the association to include the newly created associated entity?
(Related: "Doctrine and unrefreshed relationships")
And as I later noticed, the very same Doctrine manual I linked to tells me to set the property as an ArrayCollection in the constructor, excatly so that the $shelf->books->add($book) does work. That is:
public function __construct() {
$this->books = new \Doctrine\Common\Collections\ArrayCollection();
}
Stupid me. I'll post the answer here in case someone else happens to come looking for the same issue, after being just as stupid. Which is unlikely, I guess.