I've made a simple symfony2 console script which is supposed to convert data from old model to the new one.
Here's what it looks like:
class ConvertScreenshotsCommand extends Command
{
[...]
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$output->writeln('<info>Conversion started on ' . date(DATE_RSS) . "</info>");
$output->writeln('Getting all reviews...');
$reviews = $em->getRepository('ACCommonBundle:Review')->findAll(); // Putting all Review entities into an array
$output->writeln('<info>Got ' . count($reviews) . ' reviews.</info>');
foreach ($reviews as $review) {
$output->writeln("<info>Screenshots for " . $review->getTitle() . "</info>");
if ($review->getLegacyScreenshots()) {
foreach ($review->getLegacyScreenshots() as $filename) { // fn returns array of strings
$output->writeln("Found " . $filename);
$screenshot = new ReviewScreenshot(); // new object
$screenshot->setReview($review); // review is object
$screenshot->setFilename($filename); // filename is string
$em->persist($screenshot);
$em->flush(); // this is where it dies
$output->writeln("Successfully added to the database.");
}
} else $output->writeln("No legacy screenshots found.");
}
$output->writeln('<info>Conversion ended on ' . date(DATE_RSS) . "</info>");
}
}
The script breaks on $em->flush(), with the following error:
[ErrorException]
Warning: spl_object_hash() expects parameter 1 to be object, string given in
/[...]/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1324
Obviously I'm doing something wrong, but can't figure out what it is. Thanks in advance!
** Update **
Review Entity mapping:
class Review
{
[...]
/**
* #ORM\OneToMany(targetEntity="ReviewScreenshot", mappedBy="review")
*/
protected $screenshots;
/**
* Won't be stored in the DB
* #deprecated
*/
private $legacyScreenshots;
/**
* New method to get screenshots, currently calls old method for the sake of compatibility
* #return array Screenshot paths
*/
public function getScreenshots()
{
// return $this->getLegacyScreenshots(); // Old method
return $this->screenshots; // New method
}
/**
* Get Screenshot paths
* #return array Screenshot paths
* #deprecated
*/
public function getLegacyScreenshots()
{
$dir=$this->getUploadRootDir();
if (file_exists($dir)) {
$fileList = scandir($dir);
$this->screenshots = array();
foreach ($fileList as $fileName)
{
preg_match("/(screenshot-\d+.*)/", $fileName, $matches);
if ($matches)
$this->screenshots[]=$matches[1];
}
return $this->screenshots;
}
else return null;
}
ReviewScreenshot mapping:
class ReviewScreenshot
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $filename
*
* #ORM\Column(name="filename", type="string", length=255)
*/
private $filename;
/**
* #ORM\ManyToOne(targetEntity="Review", inversedBy="screenshots")
* #ORM\JoinColumn(name="review_id", referencedColumnName="id")
*/
protected $review;
/**
* #var integer $priority
*
* #ORM\Column(name="priority", type="integer", nullable=true)
*/
protected $priority;
/**
* #var string $description
*
* #ORM\Column(name="description", type="string", nullable=true)
*/
protected $description;
/**
* #Assert\File(maxSize="2097152")
*/
public $screenshot_file;
protected $webPath;
UnitOfWork.php
/**
* Gets the state of an entity with regard to the current unit of work.
*
* #param object $entity
* #param integer $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
* This parameter can be set to improve performance of entity state detection
* by potentially avoiding a database lookup if the distinction between NEW and DETACHED
* is either known or does not matter for the caller of the method.
* #return int The entity state.
*/
public function getEntityState($entity, $assume = null)
{
$oid = spl_object_hash($entity); // <-- Line 1324
if (isset($this->entityStates[$oid])) {
return $this->entityStates[$oid];
}
if ($assume !== null) {
return $assume;
}
// State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
// Note that you can not remember the NEW or DETACHED state in _entityStates since
// the UoW does not hold references to such objects and the object hash can be reused.
// More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
$class = $this->em->getClassMetadata(get_class($entity));
$id = $class->getIdentifierValues($entity);
if ( ! $id) {
return self::STATE_NEW;
}
switch (true) {
case ($class->isIdentifierNatural());
// Check for a version field, if available, to avoid a db lookup.
if ($class->isVersioned) {
return ($class->getFieldValue($entity, $class->versionField))
? self::STATE_DETACHED
: self::STATE_NEW;
}
// Last try before db lookup: check the identity map.
if ($this->tryGetById($id, $class->rootEntityName)) {
return self::STATE_DETACHED;
}
// db lookup
if ($this->getEntityPersister($class->name)->exists($entity)) {
return self::STATE_DETACHED;
}
return self::STATE_NEW;
case ( ! $class->idGenerator->isPostInsertGenerator()):
// if we have a pre insert generator we can't be sure that having an id
// really means that the entity exists. We have to verify this through
// the last resort: a db lookup
// Last try before db lookup: check the identity map.
if ($this->tryGetById($id, $class->rootEntityName)) {
return self::STATE_DETACHED;
}
// db lookup
if ($this->getEntityPersister($class->name)->exists($entity)) {
return self::STATE_DETACHED;
}
return self::STATE_NEW;
default:
return self::STATE_DETACHED;
}
}
I think the problem lies within Review::$screenshots:
You map it as a OneToMany association, so the value should be a Collection of ReviewScreenshot entities. But the method Review::getLegacyScreenshots() will change it into an array of strings.
You're probably using the change-tracking policy DEFERRED_IMPLICIT (which is the default). So when the property Review::$screenshots changes, Doctrine will try to persist that change, encounters strings where it expects entities, so throws the exception.
Related
I have the structure like below.
----------------
MESSAGE
----------------
id
subject
body
----------------
----------------
USER
----------------
id
name
category
region
----------------
----------------
RECIPIENT
----------------
user_id
message_id
is_read
read_at
----------------
So Message 1:n Recipient m:1 User.
Recipient is not an #ApiResource.
A Backoffice user will "write" a message and choose the audience by a set of specific criteria (user region, user category, user tags...).
To POST the message i'm using a Dto
class MessageInputDto
{
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $subject;
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $body;
/**
* #var bool
*
* #Groups({"msg_message:write"})
*/
public bool $isPublished;
/**
* #var DateTimeInterface
*
* #Groups({"msg_message:write"})
*/
public DateTimeInterface $publishDate;
/**
* #var DateTimeInterface|null
*
* #Groups({"msg_message:write"})
*/
public ?DateTimeInterface $expiryDate = null;
/**
* #var MessageCategory|null
*
* #Groups({"msg_message:write"})
*/
public ?MessageCategory $category = null;
/**
* #var array
*/
public array $criteria = [];
}
The $criteria field is used to choose the audience of that message and is skipped by the DataTransformer as it is not a mapped field, a property of Message Entity that is returned by the transformer.
class MessageInputDataTransformer implements \ApiPlatform\Core\DataTransformer\DataTransformerInterface
{
/**
* #var MessageInputDto $object
* #inheritDoc
*/
public function transform($object, string $to, array $context = [])
{
$message = new Message($object->subject, $object->body);
$message->setIsPublished($object->isPublished);
$message->setPublishDate($object->publishDate);
$message->setExpiryDate($object->expiryDate);
$message->setCategory($object->category);
return $message;
}
/**
* #inheritDoc
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
// in the case of an input, the value given here is an array (the JSON decoded).
// if it's a book we transformed the data already
if ($data instanceof Message) {
return false;
}
return Message::class === $to && null !== ($context['input']['class'] ?? null);
}
}
As side effect, will be performed a bulk insert in the join table (Recipient) that keeps the m:n relations between Message and User.
My problem is how/where to perform this bulk insert and how pass the $criteria to the service that will manage it.
The only solution that i've found now (and it's working but i don't think is a good practice) is to put the bulk insert procedure in the POST_WRITE event of the Message, get the Request object and process the $criteria contained there.
class MessageSubscriber implements EventSubscriberInterface
{
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
['handleCriteria', EventPriorities::POST_WRITE]
],
];
}
public function handleCriteria(ViewEvent $event)
{
/** #var Message $message */
$message = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
$e = $event->getRequest();
$collectionOperation = $e->get('_api_collection_operation_name');
if (!$message instanceof Message ||
$method !== Request::METHOD_POST ||
$collectionOperation !== 'post') {
return;
}
$content = json_decode($event->getRequest()->getContent(), true);
if(array_key_exists('audienceCriteria', $content)){
$criteria = Criteria::createFromArray($content['audienceCriteria']);
// Todo: Create the audience
}
}
}
So the idea is that, when the Message is persisted, the system must generate the "relations" public.
This is why i think that the post write event could be a good choice, but as i said i'm not sure this could be a good practice.
Any idea? Thanks.
As the docs on DTO's state: "in most cases the DTO pattern should be implemented using an API Resource class representing the public data model exposed through the API and a custom data provider. In such cases, the class marked with #ApiResource will act as a DTO."
IOW specifying an Input or an Output Data Representation and a DataTransformer is the exception. It does not work if the DTO holds more data then the entity or if the dto's are not one to one with the entities (for example with a report that does a group by).
Here is your DTO class as a resource:
namespace App\DTO;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Entity\Message;
/**
* Class defining Message data transfer
*
* #ApiResource(
* denormalizationContext= {"groups" = {"msg_message:write"}},
* itemOperations={
* },
* collectionOperations={
* "post"={
* "path"="/messages",
* "openapi_context" = {
* "summary" = "Creates a Message",
* "description" = "Creates a Message"
* }
* }
* },
* output=Message::class
* )
*/
class MessageInputDto
{
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $subject;
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $body;
/**
* #var bool
*
* #Groups({"msg_message:write"})
*/
public bool $isPublished;
/**
* #var \DateTimeInterface
*
* #Groups({"msg_message:write"})
*/
public \DateTimeInterface $publishDate;
/**
* #var \DateTimeInterface|null
*
* #Groups({"msg_message:write"})
*/
public ?\DateTimeInterface $expiryDate = null;
/**
* #var MessageCategory|null
*
* #Groups({"msg_message:write"})
*/
public ?MessageCategory $category = null;
/**
* #var array
* #Groups({"msg_message:write"})
*/
public array $criteria = [];
}
Make sure the folder your class is in is in the paths list in api/config/packages/api_platform.yaml. There usually is the following configuration:
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
If MessageInputDto is in /src/DTO make it like:
api_platform:
mapping:
paths:
- '%kernel.project_dir%/src/Entity'
- '%kernel.project_dir%/src/DTO'
The post operation may have the same path as dhe default post operation on your Message resource. Remove that by explicitly defining collectionOperations for your Message resource without "post".
The post operation of MessageInputDto will deserialize the MessageInputDto. Your DataTransformer will not act on it so that it will arrive as is to the DataPersister:
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\DTO\MessageInputDto;
use App\Entity\Message;
use Doctrine\Persistence\ManagerRegistry;
use App\DataTransformer\MessageInputDataTransformer;
use ApiPlatform\Core\Exception\InvalidArgumentException;
class MessageDataPersister implements ContextAwareDataPersisterInterface
{
private $dataPersister;
private $entityManager;
private $dataTransformer;
public function __construct(ContextAwareDataPersisterInterface $dataPersister, ManagerRegistry $managerRegistry, MessageInputDataTransformer $dataTransformer)
{
$this->dataPersister = $dataPersister;
$this->entityManager = $managerRegistry->getManagerForClass(Message::class);
$this->dataTransformer = $dataTransformer;
}
public function supports($data, array $context = []): bool
{
$transformationContext = ['input' => ['class' => Message::class]];
return get_class($data) == MessageInputDto::class
&& $this->dataTransformer->supportsTransformation($data, Message::class, $transformationContext)
&& null !== $this->entityManager;
}
public function persist($data, array $context = [])
{
$message = $this->dataTransformer->transform($data, Message::class);
// dataPersister will flush the entityManager but we do not want incomplete data inserted
$this->entityManager->beginTransaction();
$commit = true;
$result = $this->dataPersister->persist($message, []);
if(!empty($data->criteria)){
$criteria = Criteria::createFromArray($data->criteria);
try {
// Todo: Create the audience, preferably with a single INSERT query SELECTing FROM user_table WHERE meeting the criteria
// (Or maybe better postpone until message is really sent, user region, category, tags may change over time)
} catch (\Exception $e) {
$commit = false;
$this->entityManager->rollback();
}
}
if ($commit) {
$this->entityManager->commit();
}
return $result;
}
public function remove($data, array $context = [])
{
throw new InvalidArgumentException('Operation not supported: delete');
}
}
(Maybe it should have been called MessageInputDtoDataPersister - depending on how you look at it)
Even with service autowiring and autoconfiguration enabled, you must still configure it to get the right dataPersister to delegate to:
# api/config/services.yaml
services:
# ...
'App\DataPersister\MessageDataPersister':
arguments:
$dataPersister: '#api_platform.doctrine.orm.data_persister'
This way you do not need MessageSubscriber.
Be aware that all the other phases inbetween deserialization and data persist (validation, security post denormalize) work on the MessageInputDto.
One solution when you have to generate multiple custom entities is to use data persisters: https://api-platform.com/docs/core/data-persisters/
There you have 2 options:
Decorate the doctrine persister - meaning the message will still be saved by Doctrine, but you can do something before or afterwards.
Implement a custom persister - saving both message and other related entities that you like. Or doing something completely custom, without calling Doctrine at all.
I generated flag link
$flag_link = [
'#lazy_builder' => ['flag.link_builder:build', [
$product->getEntityTypeId(),
$product->id(),
'product_like',
]],
'#create_placeholder' => TRUE,
];
Flag link is generated successfully. But while I click flag link , I got error message as response
{message: "'csrf_token' URL query argument is invalid."}
message: "'csrf_token' URL query argument is invalid."
I found a temporary solution. Not sure if this is a bug in Flag that needs to be addressed by the module maintainers, or if this is working as intended, since this is a REST response, and not a typical Drupal call for a view or display mode.
In the ModuleRestResource.php file (In my case, the ModuleRestResource.php file is located at:
{{DRUPAL_ROOT}}/web/modules/custom/{{Module_Name}}/src/Plugin/rest/resource/{{Module_Name}}RestResource.php):
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\flag\FlagService;
use Drupal\Core\Render\RenderContext;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class ModuleRestResource extends ResourceBase {
/**
* A current user instance.
*
* #var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* #var $entityTypeManager \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* #var \Drupal\flag\FlagService
*/
protected $flagService;
/**
* #var Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfService;
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->logger = $container->get('logger.factory')->get('module');
$instance->currentUser = $container->get('current_user');
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->flagService = $container->get('flag');
$instance->csrfService = $container->get('csrf_token');
return $instance;
}
/**
* Responds to GET requests.
*
* #param string $payload
*
* #return \Drupal\rest\ResourceResponse
* The HTTP response object.
*
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
* Throws exception expected.
*/
public function get($payload) {
// You must to implement the logic of your REST Resource here.
// Use current user after pass authentication to validate access.
if (!$this->currentUser->hasPermission('access content')) {
throw new AccessDeniedHttpException();
}
if (!is_numeric($payload)) {
throw new BadRequestHttpException();
}
/*
* This is the object that will be returned with the node details.
*/
$obj = new \stdClass();
// First load our node.
/**
* #var \Drupal\Core\Entity\EntityInterface
*/
$node = $this->entityTypeManager->getStorage('node')->load($payload);
/**
* FIX STARTS HERE !!!!!
*/
/**
* Because we are rending code early in the process, we need to wrap in executeInRenderContext
*/
$render_context = new RenderContext();
$fl = \Drupal::service('renderer')->executeInRenderContext($render_context, function() use ($node, $payload) {
/**
* Get the flag we need and check if the selected node has been flagged by the current user
*
* Set the path to create a token. This is the value that is missing by default that creates an
* invalid CSRF Token. Important to note that the leading slash should be left off for token generation
* and then added to to the links href attribute
*
*/
$flag = $this->flagService->getFlagById('bookmark');
$is_flagged = (bool) $this->flagService->getEntityFlaggings($flag, $node, \Drupal::currentUser() );
$path = 'flag/'. ($is_flagged ? 'un' : '') .'flag/bookmark/' . $node->id();
$token = $this->csrfService->get($path);
$flag_link = $flag->getLinkTypePlugin()->getAsFlagLink($flag, $node);
$flag_link['#attributes']['href'] = '/' . $path . '?destination&token=' . $token;
/**
* Render the link into HTML
*/
return \Drupal::service('renderer')->render($flag_link);
});
/**
* This is required to bubble metadata
*/
if (!$render_context->isEmpty()) {
$bubbleable_metadata = $render_context->pop();
\Drupal\Core\Render\BubbleableMetadata::createFromObject($fl)
->merge($bubbleable_metadata);
}
/*
* !!!!! FIX ENDS HERE !!!!!
*/
$obj->flag_link = $fl;
return new ResourceResponse((array)$obj, 200);
}
}
Anyone who can get module maintainers to address this would be nice.
I'm trying to save a collection of files.
When I save a collection, it goes into the DB without a hitch. Same with files.
But when I add a collection and a new file in the same request (like here in the upload function).
Whenever I then ask doctrine to get me the files in the collection (in this case the one new file).
It always responds with an empty ArrayCollection.
If I do a seperate get HTTP request and ask for the collection afterwards, it shows me the correct arrayCollection containing my one new file.
I have tried every way of persisting and flushing the entities, as well as changing cascade annotations, so far nothing has worked.
I have tried clearing the doctrine cache as well.
The Annotations seem to be correct because calling a getCollection()->getFiles() results in an ArrayCollection containing the files linked to the collection. It just doesn't seem to work right after creating both entities and linking them together.
Thank you very much for your help, code is down below.
This is my collection. Which contains files as an ArrayCollection.
/**
* #Entity #Table(name="LH_FileCollections")
**/
class LhFileCollection extends RootModel
{
/**
* #Column(type="string")
*/
protected $title;
/**
* #OneToMany(targetEntity="LhFile", mappedBy="collection")
*/
protected $files;
//Getters and Setters
}
This is my File class.
/**
* #Entity #Table(name="LH_Files")
**/
class LhFile extends RootModel
{
/**
* #Column(type="string")
*/
protected $name;
/**
* #Column(type="string")
*/
protected $type;
/**
* #Column(name="file_hash", type="string")
*/
protected $fileHash;
/**
* #ManyToOne(targetEntity="LhFileCollection", inversedBy="files", cascade={"persist"})
* #JoinColumn(name="collection_id", referencedColumnName="id")
*/
protected $collection;
//Getters and Setters
}
This is my save filecollection function.
/**
* #return array|string
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
* #throws \Exception
*/
public function fileUpload(
$title,
$attachment = null,
$allowedFileTypes = null,
$maxAllowedFileSize = 5000000
) {
//Create collection
$collection = $this->fileCollectionRepository->add($title);
foreach ($_FILES as $file) {
if ($allowedFileTypes !== null) {
$errors = $this->fileCheck($file, $allowedFileTypes, $maxAllowedFileSize);
if (!empty($errors)) {
return $errors;
}
}
$this->saveFile($file, $collection);
}
return $collection;
}
This is my save file function.
/**
* #param $file
* #param $collection
* #return LhFile
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
private function saveFile($file, $collection)
{
$currentDir = getcwd();
$uploadDir = $currentDir . '/Data/Uploads/';
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$uniqueName = uniqid() . "_" . time() . '.' . $extension;
move_uploaded_file($file['tmp_name'], $uploadDir . $uniqueName);
$metaData = "...";
$file = $this->fileRepository->add(
$file['name'],
$file['size'],
$extension,
$metaData,
$uniqueName,
$collection
);
return $file;
}
And lastly the repository functions:
Filecollection:
/**
* #param $fileCollection
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
public function add($title)
{
$fileCollection = new LhFileCollection();
$fileCollection->setTitle($title);
$this->em->persist($fileCollection);
$this->em->flush();
return $fileCollection;
}
And File
/**
* #param $file
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
public function add($name, $size, $type, $metaData, $uniqueName, $collection)
{
$file = new LhFile();
$file->setName($name);
$file->setSize($size);
$file->setType($type);
$file->setMetadata($metaData);
$file->setFileHash($uniqueName);
$file->setCollection($collection);
$this->em->persist($file);
$this->em->flush();
return $file;
}
It seems to me that in the current request from what I can make out that files are not being added to the collection->files array. The database is receiving the correct relationships which is why on the second request it is fine but the act of saving to the database doesn't auto populate the relationships.
I think you need to explicitly add the file to the collection->files, assuming you have getFiles in LhFileCollection you could add after $this->saveFile($file, $collection);:
$collection->getFiles()->add($file);
There are of course a number of ways it can be done but ultimately you need to add the files to the collection->files.
Personally, I would build up the collection adding each file to the files array and only then save the collection. I wouldn't persist and flush on every file because database actions could be expensive. You have cascade on so it should cascade to all files.
I'm struggling with fixing this class exception error. If anyone could help it would be greatly appreciated, Thanks!
BACKGROUND INFO:
After installing a Dashnex plugin on my wordpress site & then uninstalling WP Quick cache I am getting this error message. Please note that quick cache is fully uninstalled & the wp-config.php file includes no quick cache instructions.
ERROR MESSAGE:
Fatal error: Uncaught exception 'ReflectionException' with message 'Class \quick_cache does not exist' in /home/cal108/public_html/wp-content/plugins/dashnex-plugin/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php:246 Stack trace: #0 /home/cal108/public_html/wp-content/plugins/dashnex-plugin/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php(246): ReflectionClass->__construct('\quick_cache') #1 /home/cal108/public_html/wp-content/plugins/dashnex-plugin/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php(113): Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver->getAllClassNames() #2 /home/cal108/public_html/wp-content/plugins/dashnex-plugin/DashNex/Doctrine/MagicSchema.php(18): Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->getAllMetadata() #3 /home/cal108/public_html/wp-content/plugins/dashnex-plugin/DashNex/Doctrine/MagicSchema.php(37): DashNex\Doctrine\MagicSchema->Get in /home/cal108/public_html/wp-content/plugins/dashnex-plugin/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php on line 246
AnnotationDriver.php Line 246
$rc = new \ReflectionClass($className);
AnnotationDriver.php File
<?php
abstract class AnnotationDriver implements MappingDriver
{
/**
* The AnnotationReader.
*
* #var AnnotationReader
*/
protected $reader;
/**
* The paths where to look for mapping files.
*
* #var array
*/
protected $paths = array();
/**
* The paths excluded from path where to look for mapping files.
*
* #var array
*/
protected $excludePaths = array();
/**
* The file extension of mapping documents.
*
* #var string
*/
protected $fileExtension = '.php';
/**
* Cache for AnnotationDriver#getAllClassNames().
*
* #var array|null
*/
protected $classNames;
/**
* Name of the entity annotations as keys.
*
* #var array
*/
protected $entityAnnotationClasses = array();
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
*
* #param AnnotationReader $reader The AnnotationReader to use, duck-typed.
* #param string|array|null $paths One or multiple paths where mapping classes can be found.
*/
public function __construct($reader, $paths = null)
{
$this->reader = $reader;
if ($paths) {
$this->addPaths((array) $paths);
}
}
/**
* Appends lookup paths to metadata driver.
*
* #param array $paths
*
* #return void
*/
public function addPaths(array $paths)
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* #return array
*/
public function getPaths()
{
return $this->paths;
}
/**
* Append exclude lookup paths to metadata driver.
*
* #param array $paths
*/
public function addExcludePaths(array $paths)
{
$this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
}
/**
* Retrieve the defined metadata lookup exclude paths.
*
* #return array
*/
public function getExcludePaths()
{
return $this->excludePaths;
}
/**
* Retrieve the current annotation reader
*
* #return AnnotationReader
*/
public function getReader()
{
return $this->reader;
}
/**
* Gets the file extension used to look for mapping files under.
*
* #return string
*/
public function getFileExtension()
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* #param string $fileExtension The file extension to set.
*
* #return void
*/
public function setFileExtension($fileExtension)
{
$this->fileExtension = $fileExtension;
}
/**
* Returns whether the class with the specified name is transient. Only non-transient
* classes, that is entities and mapped superclasses, should have their metadata loaded.
*
* A class is non-transient if it is annotated with an annotation
* from the {#see AnnotationDriver::entityAnnotationClasses}.
*
* #param string $className
*
* #return boolean
*/
public function isTransient($className)
{
$classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
foreach ($classAnnotations as $annot) {
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
return false;
}
}
return true;
}
/**
* {#inheritDoc}
*/
public function getAllClassNames()
{
if ($this->classNames !== null) {
return $this->classNames;
}
if (!$this->paths) {
throw MappingException::pathRequired();
}
$classes = array();
$includedFiles = array();
foreach ($this->paths as $path) {
if ( ! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::LEAVES_ONLY
),
'/^.+' . preg_quote($this->fileExtension) . '$/i',
\RecursiveRegexIterator::GET_MATCH
);
foreach ($iterator as $file) {
$sourceFile = $file[0];
if ( ! preg_match('(^phar:)i', $sourceFile)) {
$sourceFile = realpath($sourceFile);
}
foreach ($this->excludePaths as $excludePath) {
$exclude = str_replace('\\', '/', realpath($excludePath));
$current = str_replace('\\', '/', $sourceFile);
if (strpos($current, $exclude) !== false) {
continue 2;
}
}
require_once $sourceFile;
$includedFiles[] = $sourceFile;
}
}
$declared = get_declared_classes();
foreach ($declared as $className) {
$rc = new \ReflectionClass($className);
$sourceFile = $rc->getFileName();
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
$classes[] = $className;
}
}
$this->classNames = $classes;
return $classes;
}
}
I WAS ABLE TO SOLVE MY PROBLEM. HELP NOT NEEDED ANYMORE :)
FOR ANYONE THIS MAY STILL HELP:
I DISABLED ALL OF MY PLUGINS BY RENAMING THEM IT IN CPANEL, THEN BROUGHT EACH ONE ONLINE AGAIN TO KNOW IF THE DASHNEX PLUGIN WAS THE CULPRIT. IT WAS. THEN RENAMED ALL PLUGINS AGAIN & ALL WORKING FINE.
I DON'T THINK THE DASHNEX PLUGIN LIKED MY UNINSTALLING WP QUICK CACHE. BUT REINSTALLING IT AFTER WP QUCIK CACHE HAD ALREADY BEEN UNINSTALLED WORKED FINE. THIS IS THE 2ND TIME I'VE HAD PROBLEMS WHEN QUICK CACHE HAS BEEN UNINSTALLED SO BE WARY OF IT. MAYBE BEST TO JUST DEACTIVATE IT & ONLY UNINSTALL IT ON A BRAND NEW SITE BEFORE INSTALLING ANY OTHER PLUGINS.
DIDN'T END UP NEEDING TO TWEAK THE PHP CODE.
Context
I need to hold an entity into a session using Doctrine 2.3 (with PHP 5.4), and I'm having a problem once the $_SESSION variable is set.
Code
I have the following classes:
Persistente
Superclass for holding information about persistent classes.
/**
* #MappedSuperclass
*/
abstract class Persistente
{
public function __construct()
{}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
}
Persona
Holds basic information about a person.
/**
* #Entity
* #AttributeOverrides({
* #AttributeOverride(name="id",
* column=#Column(
* name="Persona_Id",
* type="integer"
* )
* )
* })
*/
class Persona extends Persistente
{
...
public function getContInformacion()
{
return $this->contInformacion;
}
public function setContInformacion(ContenedorInformacion $contInformacion)
{
$this->contInformacion = $contInformacion;
}
...
/**
* #OneToOne(targetEntity="ContenedorInformacion", cascade={"all"} )
* #JoinColumn(name="ContInfo_Id", referencedColumnName="ContInfo_Id")
*/
private $contInformacion;
}
ContenedorInformacion
Class that contains information about the person, which can be dynamically added to the object depending on some validation rules.
/**
* #Entity
* #AttributeOverrides({
* #AttributeOverride(name="id",
* column=#Column(
* name="ContInfo_Id",
* type="integer"
* )
* )
* })
*/
class ContenedorInformacion extends Persistente
{
...
/**
* #OneToMany(targetEntity="UnidadInformacion", mappedBy="contInformacion", cascade={"all"}, indexBy="clave")
*/
private $unidadesInformacion;
/**
* #OneToMany(targetEntity="Rol", mappedBy="contInformacion", cascade={"all"}, indexBy="clave")
*/
private $roles;
}
Issue
Whenever I add Persona to a session, the following code gets executed:
public function login(Persona $t)
{
if ($this->autorizar($t) === false) {
return false;
}
$dao = new DAOManejadorMsSql();
$daoPersona = $dao->fabricarDAO("\Codesin\Colegios\Personas\Persona");
$t = $this->buscarPersona($t);
$daoPersona->soltar($t);
$dao->cerrar();
$_SESSION['usuario'] = $t;
if ($t->getContInformacion()->existeRol('SYSADMIN') === true) {
return 'SYSADMIN';
}
}
soltar() executes the detach() method from the EntityManager, effectively leaving the entity unmanaged. However, the ContenedorInformacion object inside Persona is a proxy generated by Doctrine instead of the wanted object. Why does this happen? Thank you beforehand.
EDIT: This is the error.
Warning: require(C:\xampp\htdocs/Zeus/lib/vendor/DoctrineProxies/__CG__/Codesin/Colegios/Personas/ContenedorInformacion.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Zeus\Common\Utils\autoload.php on line 8
Fatal error: require(): Failed opening required 'C:\xampp\htdocs/Zeus/lib/vendor/DoctrineProxies/__CG__/Codesin/Colegios/Personas/ContenedorInformacion.php' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\Zeus\Common\Utils\autoload.php on line 8
I had to use a very crude approach.
I figured out the following: given I'm not going to reattach the information immediately, I remade another ContenedorInformacion which contains the exact same information than the proxy. And given the ArrayCollections aren't using proxies but rather the whole objects, I did this.
public function login(Persona $t)
{
if ($this->autorizar($t) === false) {
return false;
}
$dao = new DAOManejadorMsSql();
$daoPersona = $dao->fabricarDAO("\Codesin\Colegios\Personas\Persona");
$t = $this->buscarPersona($t);
$daoPersona->soltar($t);
$dao->cerrar();
/***** NEW LINES START HERE *****/
$contInfo = new ContenedorInformacion();
$contInfo->setId($t->getContInformacion()->getId());
$contInfo->setUnidadesInformacion(new ArrayCollection($t->getContInformacion()->getUnidadesInformacion()->toArray()));
$contInfo->setRoles(new ArrayCollection($t->getContInformacion()->getRoles()->toArray()));
$t->setContInformacion($contInfo);
/***** NEW LINES END HERE *****/
$_SESSION['usuario'] = $t;
if ($t->getContInformacion()->existeRol('SYSADMIN') === true) {
return 'SYSADMIN';
}
}
It's quite dirty, but it works like a charm.