I'm learning Symfony following The Book. In the tutorial, I successfully configured prePersist events (to set the createdAt field at insert time).
Now I'm trying to do the same but with YAML files instead of annotations. Here my orm.yml file:
AppBundle\Entity\Chronicle:
type: entity
table: chronicles
id:
id:
type: integer
generator: {strategy: AUTO}
fields:
name:
type: string
length: 256
createdAt:
type: datetime
manyToOne:
creator:
targetEntity: User
inversedBy: chronicles
joinColumn:
name: user_id
referencedColumnName: id
game:
targetEntity: Game
joinColumn:
name: game_id
referencedColumnName: id
oneToMany:
characters:
targetEntity: Character
mappedBy: chronicle
lifeCycleCallbacks:
prePersist: [ setCreatedAtValue ]
And this is a snippet of my entity class:
class Chronicle
{
private $id;
private $name;
private $createdAt;
// Associations
private $game;
private $creator;
private $characters;
// TODO: users or user relationships
// Constructor
public function __construct(User $creator=null) {
$this->characters = new ArrayCollection();
$this->creator = $creator;
}
/**
* Set createdAt at the current time.
*
* (Lifecycle callback)
*/
public function setCreatedAtValue()
{
$this->createdAt = new \DateTime();
}
// ...
}
But the setCreatedAtValue() method is never called. So, I get an exception when I try to insert an object.
I have noticed that when using annotations I have to tell about the existence of lifecycle callbacks with the #ORM\HasLifecycleCallbacks() annotation, but I have not found anywhere an equivalent to that in yml, or if that is needed.
In a tutorial I have found I should register the callback in services.yml, but the tutorial never mention it, and I haven't found it anywhere else.
It will be called if you change "lifeCycleCallbacks" to "lifecycleCallbacks".
Related
can you help me please how generate correct doc if i want post data and add relation to other entity ?
I have workspace and notes. I want create new note, add it to workspace and in same time edit layout in workspace. This procedure working. But i can not figure how to show what is needed in doc.
This is what i write to body and it working
This is what showing doc if open doc
Group: 'post:write' is for write POST
StickyNote entity
#[Post(
normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'd.m.Y H:i:s', 'groups' => ['post:read']],
denormalizationContext: ['groups' => ['post:write']],
)]
class StickyNote extends Dates
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\CustomIdGenerator(class: UuidV4::class)]
#[Groups(['post:read', 'getSingle:workspace:read'])]
private UuidV4 $id;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['post:read', 'getSingle:workspace:read', 'post:write'])]
private ?string $noteData = null;
#[ORM\ManyToOne(inversedBy: 'stickyNotes')]
#[ORM\JoinColumn(nullable: false)]
#[Groups(['post:write'])]
#[ApiProperty(types: 'object')]
private Workspace $workspace;
Workspace entity
class Workspace extends Dates
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\CustomIdGenerator(class: UuidV4::class)]
#[Groups(['getCollection:workspace:read', 'getSingle:workspace:read'])]
private UuidV4 $id;
#[ORM\Column(length: 255, nullable: false)]
#[Groups(['getCollection:workspace:read', 'getSingle:workspace:read'])]
private string $title;
#[ORM\Column(type: 'json', nullable: true)]
#[Groups(['getCollection:workspace:read', 'getSingle:workspace:read', 'post:write'])]
private array $layout = [];
#[ORM\OneToMany(mappedBy: 'workspace', targetEntity: StickyNote::class)]
#[Groups(['getCollection:workspace:read', 'getSingle:workspace:read'])]
private Collection $stickyNotes;
#[ORM\ManyToOne(inversedBy: 'workspaces')]
private User $user;
#[ORM\Column]
#[Groups(['getCollection:workspace:read'])]
private int $ord;
Tried search in doc, google and stack .. etc .. I can get it.
I will be grateful for your help
so I have a class Observer which needs to have a list of notifications I am using a OneToMany relation between the two classes, but for some reason when I want to insert a notification I get a foreign key constraint failure. I would greatly appreciate it if someone could help me or point me in the right direction, Thanks in advance
Observer class
#Entity
abstract class Observer {
#Id
#GeneratedValue
open var id: Long = -1;
#OneToMany
open var notifications: MutableList<Notification> = mutableListOf()
abstract fun update(podcast: Podcast);
}
// different file
#Entity
class User(
var name: String,
var password: String,
#OneToMany
var podcasts : MutableList<PodcastInfo> = mutableListOf(),
) : Observer() {
override fun update(podcast: Podcast) {
val notification = Notification(message = "There is a new episode of ${podcast.name}");
this.notifications.add(notification)
print("new episode for podcast ${podcast.name}")
}
}
Notification class
#Entity
class Notification(
#Id
#GeneratedValue
val id : Long = -1,
val date : LocalDateTime = LocalDateTime.now(),
val seen : Boolean = false,
val message: String
) {
}
The project is about podcasts and when a new episode is added I want to notify the observers
fun addEpisode(#ModelAttribute episodeRequest : EpisodeRequest, #PathVariable("id") id: Long) : String {
....
podcast.addEpisode(episode); // this line adds the notification to the observer
podcast.updateObservers(observerService)
...
}
// different file
fun updateObservers(observerService: ObserverService){
this.observers.forEach{observer -> observerService.updateObserver(observer) }
}
// different file, this is the only descendant of the observer class
fun updateObserver(observer: Observer) : Observer{
val notifications = mutableListOf<Notification>()
for (notification: Notification in observer.notifications){
notifications.add(notificationService.update(notification))
}
observer.notifications = notifications;
return observerService.save(observer)
}
// different file
fun update(notification: Notification) : Notification {
return notificationRepository.save(notification) // this line throws the error
}
So I thought since I am first inserting/updating the notifications and then updating the observer class, I wouldn't get the foreign key error since the notifications would have valid id's.
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (podcasts.observer_notifications, CONSTRAINT FKgw8m1isyux27iyu8m0l57kdd6 FOREIGN KEY (notifications_id) REFERENCES notification (id))
You can either use #OneToMany(cascade = CascadeType.MERGE), so that notifications are flushed properly before the observer, or you invoke notificationRepository.flush() right after saving the notifications e.g. use:
fun updateObserver(observer: Observer) : Observer{
val notifications = mutableListOf<Notification>()
for (notification: Notification in observer.notifications){
notifications.add(notificationService.update(notification))
}
entityManager.flush(); // <--- This here
observer.notifications = notifications;
return observerService.save(observer)
}
I want to extend a directive #modelClass from the lighthouse library.
I'm working on a modular architecture and I don't have an Eloquent Model, I have several, and I'm extending my first Model version, that's why I'm using an interface to bind the last Model that I need.
What I need to do it is to use an interface instead a model class to resolve my type object.
Using the directive #modelClass should looks like this:
type User #modelClass(class: "App\\Models\\versionC\\User") {
id: Int!
username: String!
}
Since I have this binding:
$this->app->bind(UserInterface::class, User::class)
I should have something like:
type User #modelClass(interface: "App\\Interfaces\\UserInterface") {
id: Int!
username: String!
}
But I cannot override or extend the #modelClass directive.
The solution I found it was manipulate the schema
type User #entity(interface: "App\\Interfaces\\UserInterface") {
id: Int!
username: String!
}
class EntityDirective extends BaseDirective implements TypeManipulator
{
public static function definition(): string
{
return /** #lang GraphQL */ <<<'SDL'
"""
Map an interface model class to an object type.
This can be used when an eloquent model it is bind to an interface.
"""
directive #entity(
"""
The interface class name of the corresponding model binding.
"""
interface: String!
) on OBJECT
SDL;
}
public function manipulateTypeDefinition(DocumentAST &$documentAST, TypeDefinitionNode &$objectType)
{
$modelClass = $this->directiveArgValue('interface');
if(!$modelClass) {
throw new DefinitionException(
"An `interface` argument must be assigned to the '{$this->name()}'directive on '{$this->nodeName()}"
);
}
$modelClass = get_class(resolve($modelClass));
// If the type already exists, we use that instead
if (isset($documentAST->types[$objectType->name->value])) {
$objectType = $documentAST->types[$objectType->name->value];
}
$objectType->directives = ASTHelper::mergeNodeList(
$objectType->directives,
[PartialParser::directive('#modelClass(class: "'.addslashes($modelClass).'")')]
);
$documentAST->setTypeDefinition($objectType);
}
}
for the record I'm using lighthouse v4.10
Is there any easy way to query a heterogeneous collection, where the objects in the collection all derive from the same base class but some may be of one derived type and some may be of another?
For example, here's a class hierarchy:
public class Ship
{
public string Name { get; set; }
public string Description { get; set; }
}
public class SailingVessel : Ship
{
public string Rig { get; set; }
public int NumberOfMasts { get; set; }
}
public class MotorVessel : Ship
{
public string Propulsion { get; set; }
public decimal TopSpeed { get; set; }
}
And here's an XML document I want to query:
<?xml version="1.0" ?>
<ships xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ship xsi:type="sailingVessel">
<name>Cutty Sark</name>
<description>Tea clipper</description>
<rig>Ship</rig>
<numberOfMasts>3</numberOfMasts>
</ship>
<ship xsi:type="sailingVessel">
<name>Peking</name>
<description>Windjammer of the Flying P Line</description>
<rig>Barque</rig>
<numberOfMasts>4</numberOfMasts>
</ship>
<ship xsi:type="motorVessel">
<name>HMS Hood</name>
<description>Last British battlecruiser</description>
<propulsion>SteamTurbine</propulsion>
<topSpeed>28</topSpeed>
</ship>
<ship xsi:type="motorVessel">
<name>RMS Queen Mary 2</name>
<description>Last transatlantic passenger liner</description>
<propulsion>IntegratedElectricPropulsion</propulsion>
<topSpeed>30</topSpeed>
</ship>
<ship xsi:type="motorVessel">
<name>USS Enterprise</name>
<description>First nuclear-powered aircraft carrier</description>
<propulsion>Nuclear</propulsion>
<topSpeed>33.6</topSpeed>
</ship>
</ships>
I can query the XML document and read its contents into a list of Ship objects:
XDocument xmlDocument = XDocument.Load("Ships.xml")
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
var records = (from record in xmlDocument.Descendants("ship")
let type = record.Attribute(xsi + "type").Value
select new Ship
{
Name = (string)record.Element("name"),
Description = (string)record.Element("description")
}).ToArray<Ship>();
This returns the following:
Ship[0] (type: Ship):
Name: Cutty Sark
Description: Tea clipper
Ship[1] (type: Ship):
Name: Peking
Description: Windjammer of the Flying P Line
Ship[2] (type: Ship):
Name: HMS Hood
Description: Last British battlecruiser
Ship[3] (type: Ship):
Name: RMS Queen Mary 2
Description: Last transatlantic passenger liner
Ship[4] (type: Ship):
Name: USS Enterprise
Description: First nuclear-powered aircraft carrier
What I would really like to be able to produce, though, is this:
Ship[0] (type: SailingVessel):
Name: Cutty Sark
Description: Tea clipper
Rig: Ship
NumberOfMasts: 3
Ship[1] (type: SailingVessel):
Name: Peking
Description: Windjammer of the Flying P Line
Rig: Barque
NumberOfMasts: 4
Ship[2] (type: MotorVessel):
Name: HMS Hood
Description: Last British battlecruiser
Propulsion: SteamTurbine
TopSpeed: 28
Ship[3] (type: MotorVessel):
Name: RMS Queen Mary 2
Description: Last transatlantic passenger liner
Propulsion: IntegratedElectricPropulsion
TopSpeed: 30
Ship[4] (type: MotorVessel):
Name: USS Enterprise
Description: First nuclear-powered aircraft carrier
Propulsion: Nuclear
TopSpeed: 33.6
How can I modify the LINQ query to intialize a SailingVessel object or a MotorVessel object as appropriate, instead of a base Ship object?
Do I have to do two selects, and duplicate the object initialization for the base class properties (Name and Description) in each one? That is all I can think of but I hate the duplication of code involved. Alternatively, is there some way to initialize the properties for the base class and optionally initialize additional properties for a SailingVessel (Rig, NumberOfMasts) or a MotorVessel (Propulsion, TopSpeed) as appropriate?
Personally I would give each type a static FromXElement method (or constructor), and then create a Dictionary<string, Func<Ship>> like this:
var factories = new Dictionary<string, Func<Ship>>
{
{ "sailingVessel", SailingVessel.FromXElement },
{ "motorVessl", MotorVessel.FromXElement },
...
};
Then your query would be:
var records = from record in xmlDocument.Descendants("ship")
let type = record.Attribute(xsi + "type").Value
select factories[type](record);
You could give the Ship class a protected constructor taking XElement to extract the common properties, leaving something like:
public class SailingVessel : Ship
{
public Rig Rig { get; set; }
public int NumberOfMasts { get; set; }
private SailingVessel(XElement element) : base(element)
{
Rig = (Rig) Enum.Parse(typeof(Rig), (string) element.Element("Rig"));
NumberOfMasts = (int) element.Element("NumberOfMasts");
}
// Don't really need this of course - could put constructor calls
// into your factory instead. I like the flexibility of factory
// methods though, e.g. for caching etc.
public static FromXElement(element)
{
return new SailingVessel(element);
}
}
There will certainly be a fair amount of code involved, but it will all be reasonably simple, and also easy to test.
I have some custom fields on my User object that I want to access with APEX code in my VisualForce trigger. When I access it from a Formula field I get to use a nifty $User reference like this:
$User.my_prop__c
From APEX I have to query the User object by UserId like this:
[select my_prop__c from User where id = :UserInfo.getUserId()].my_prop__c;
Is there something baked into APEX already that would let me get at the user properties without the SOQL query? If not, does anyone know of a utility class for lazy loading and caching user properties so the overhead is minimal.
I would use something similar to the following code sample. It uses a singleton pattern to statically store the information in memory for the duration of your transaction. It's similar to the lazy loading that twamley proposed but I feel this is a much simpler approach.
Usage 1: UserUtil.CurrentUser.Email;
Usage 2: User someUser = UserUtil.getUser(someUserId);
This will allow you to access the same information on the current user or other users in the system. Notice the queryUsers method just returns a query result. This makes it easy to add and remove fields from your query as it is isolated in its own method keeping things simple.
Note: that this code pulls in all users when used. Most orgs do not have multiple hundreds of users so heap size shouldn't be a concern. But if it is you can just modify the queryUsers() method to only return active users or filter down based on other criteria.
public class UserUtil {
//Protected Members
private static final UserUtil instance = new UserUtil();
private Map<Id, User> mapUsers;
//Properties
public static User CurrentUser {
get { return getUser(UserInfo.getUserId()); }
}
//Constructor
private UserUtil() {
mapUsers = new Map<Id, User>(queryUsers());
}
//Public Methods
public static User getUser(Id userId) {
if (instance.mapUsers.containsKey(userId)) {
return instance.mapUsers.get(userId);
}
else {
throw new InvalidUserIdException('Unable to locate user id: ' + userId);
}
}
//Private Methods
private List<User> queryUsers() {
return [SELECT
Id
, Name
, UserName
, Email
, Alias
FROM
User];
}
//Internal Classes
public class InvalidUserIdException extends Exception {}
}
I wrote my own utility class. I'm still interested in better techniques though.
This utility class lazy loads when the first property is accessed. Update_Closed_Won_Opportunities__c and Set_Opportunities_to_Closed_Won__c are my custom fields on the User object (visible only to System Administrators so people can't upgrade their permissions).
public with sharing class MyUserInfo {
private Id userId;
private User myUser; // Hold onto the user object once we've loaded it
// Default constructor uses the active user id
public MyUserInfo() {
userId = UserInfo.getUserId();
}
// Secondary constructor accepts a user id as a parameter
public MyUserInfo(Id someOtherUserId) {
userId = someOtherUserId;
}
// Only called one time when we first need it so grab all of the custom fields now
private void LazyLoadUser() {
System.AssertNotEquals(null, userId);
myUser = [
SELECT Update_Closed_Won_Opportunities__c, Set_Opportunities_To_Closed_Won__c
FROM User
WHERE id = :userId
];
System.AssertNotEquals(null, myUser, 'Unable to load user with id ' + userId); // could return defaults instead
}
// Getters (be sure to include each field in the SOQL of LazyLoadUser)
public boolean UpdateClosedWonOpportunities { get {
if (myUser == null) LazyLoadUser();
return myUser.Update_Closed_Won_Opportunities__c;
} }
public boolean SetOpportunitiesToClosedWon { get {
if (myUser == null) LazyLoadUser();
return myUser.Set_Opportunities_To_Closed_Won__c;
} }
}
Here is my trigger utilizing that class. The first line myUserInfo = new MyUserInfo(); doesn't run any SOQL. That won't happen until the first custom get property is used. Subsequent calls don't need SOQL.
trigger LockClosedOpportunity on Opportunity (before update) {
MyUserInfo myUserInfo = new MyUserInfo();
for (Opportunity o : trigger.old)
{
if (!myUserInfo.UpdateClosedWonOpportunities && o.StageName == 'Closed Won')
trigger.newMap.get(o.Id).addError('You do not have permission to change an Opportunity after it has been set to Closed Won.');
}
for (Opportunity o : trigger.new)
{
if ( !myUserInfo.SetOpportunitiesToClosedWon && o.StageName == 'Closed Won' && trigger.oldMap.get(o.Id).StageName != 'Closed Won' )
o.addError('You do not have permission to set an Opportunity to Closed Won.');
}
}
It reads similar to $User in formulas and I don't have to worry about tacking on multiple SOQL calls when one (or zero) suffices.