Skip to content

Commit 8b1fe24

Browse files
authored
Feature/thead safety (#31)
* Added support for PhpProperty on enums, allowing consumers to specify different field names Co-authored-by: Dion Leurink <dion@onlineplasticsgroup.com>
1 parent 05289ff commit 8b1fe24

File tree

2 files changed

+71
-39
lines changed

2 files changed

+71
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# vNext
22
- Added support for PhpProperty on enums, allowing consumers to specify different field names
33
- Performance: Cache enum field information with `TypeCacheFlag.PropertyInfo`.
4+
- Added thread safety to caches.
45

56
# 1.0.0
67

PhpSerializerNET/PhpDeserializer.cs

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,21 @@ This Source Code Form is subject to the terms of the Mozilla Public
1414
namespace PhpSerializerNET {
1515
internal class PhpDeserializer {
1616
private readonly PhpDeserializationOptions _options;
17-
private PhpSerializeToken _token;
17+
private readonly PhpSerializeToken _token;
18+
1819
private static readonly Dictionary<string, Type> TypeLookupCache = new() {
1920
{ "DateTime", typeof(PhpDateTime) }
2021
};
22+
private static readonly object TypeLookupCacheSyncObject = new();
2123

2224
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> PropertyInfoCache = new();
25+
private static readonly object PropertyInfoCacheSyncObject = new();
2326

2427
private static Dictionary<Type, Dictionary<string, FieldInfo>> FieldInfoCache { get; set; } = new();
28+
private static readonly object FieldInfoCacheCacheSyncObject = new();
29+
2530
private static Dictionary<Type, Dictionary<string, FieldInfo>> EnumInfoCache { get; set; } = new();
31+
private static readonly object EnumInfoCacheSyncObject = new();
2632

2733
public PhpDeserializer(string input, PhpDeserializationOptions options) {
2834
_options = options;
@@ -49,17 +55,24 @@ public T Deserialize<T>() {
4955
/// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks.
5056
/// </summary>
5157
public static void ClearTypeCache() {
52-
TypeLookupCache.Clear();
53-
TypeLookupCache.Add("DateTime", typeof(PhpDateTime));
58+
lock (TypeLookupCache) {
59+
TypeLookupCache.Clear();
60+
TypeLookupCache.Add("DateTime", typeof(PhpDateTime));
61+
}
5462
}
5563

5664
/// <summary>
5765
/// Reset the property info cache.
5866
/// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks.
5967
/// </summary>
6068
public static void ClearPropertyInfoCache() {
61-
PropertyInfoCache.Clear();
62-
EnumInfoCache.Clear();
69+
lock (PropertyInfoCacheSyncObject) {
70+
PropertyInfoCache.Clear();
71+
}
72+
73+
lock (EnumInfoCacheSyncObject) {
74+
EnumInfoCache.Clear();
75+
}
6376
}
6477

6578
private object DeserializeToken(PhpSerializeToken token) {
@@ -89,21 +102,30 @@ private object MakeClass(PhpSerializeToken token) {
89102
var typeName = token.Value;
90103
object constructedObject;
91104
Type targetType = null;
92-
if (typeName != "sdtClass" && this._options.EnableTypeLookup) {
93-
if (TypeLookupCache.ContainsKey(typeName)) {
94-
targetType = TypeLookupCache[typeName];
95-
} else {
96-
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic)) {
97-
// TODO: PhpClass attribute should win over classes who happen to have the name...?
98-
targetType = assembly
99-
.GetExportedTypes()
100-
.FirstOrDefault(y => y.Name == typeName || y.GetCustomAttribute<PhpClass>()?.Name == typeName);
101-
if (targetType != null) {
102-
break;
105+
if (typeName != "stdClass" && this._options.EnableTypeLookup) {
106+
lock (TypeLookupCacheSyncObject)
107+
{
108+
if (TypeLookupCache.ContainsKey(typeName)) {
109+
targetType = TypeLookupCache[typeName];
110+
} else {
111+
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic)) {
112+
113+
targetType = assembly
114+
.GetExportedTypes()
115+
.Select(type => new { type, attribute = type.GetCustomAttribute<PhpClass>() })
116+
// PhpClass attribute should win over classes who happen to have the name
117+
.OrderBy(c => c.attribute != null ? 0 : 1)
118+
.Where(y => y.type.Name == typeName || y.attribute?.Name == typeName)
119+
.Select(c => c.type)
120+
.FirstOrDefault();
121+
122+
if (targetType != null) {
123+
break;
124+
}
125+
}
126+
if (this._options.TypeCache.HasFlag(TypeCacheFlag.ClassNames)) {
127+
TypeLookupCache.Add(typeName, targetType);
103128
}
104-
}
105-
if (this._options.TypeCache.HasFlag(TypeCacheFlag.ClassNames)) {
106-
TypeLookupCache.Add(typeName, targetType);
107129
}
108130
}
109131
}
@@ -198,7 +220,8 @@ private object DeserializeInteger(Type targetType, PhpSerializeToken token) {
198220
private object DeserializeDouble(Type targetType, PhpSerializeToken token) {
199221
if (targetType == typeof(double) || targetType == typeof(float)) {
200222
return token.ToDouble();
201-
};
223+
}
224+
202225
token.Value = token.Value switch {
203226
"INF" => double.PositiveInfinity.ToString(CultureInfo.InvariantCulture),
204227
"-INF" => double.NegativeInfinity.ToString(CultureInfo.InvariantCulture),
@@ -259,11 +282,13 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
259282

260283
FieldInfo foundFieldInfo = null;
261284
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
262-
if (!EnumInfoCache.ContainsKey(targetType)) {
263-
EnumInfoCache.Add(targetType, new());
264-
}
265-
if (EnumInfoCache[targetType].ContainsKey(token.Value)) {
266-
foundFieldInfo = EnumInfoCache[targetType][token.Value];
285+
lock (EnumInfoCacheSyncObject) {
286+
if (!EnumInfoCache.ContainsKey(targetType)) {
287+
EnumInfoCache.Add(targetType, new());
288+
}
289+
if (EnumInfoCache[targetType].ContainsKey(token.Value)) {
290+
foundFieldInfo = EnumInfoCache[targetType][token.Value];
291+
}
267292
}
268293
}
269294

@@ -274,7 +299,9 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
274299
.FirstOrDefault(c => c.fieldInfo.Name == token.Value || c.phpPropertyAttribute != null && c.phpPropertyAttribute.Name == token.Value)
275300
?.fieldInfo;
276301
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
277-
EnumInfoCache[targetType].Add(token.Value, foundFieldInfo);
302+
lock (EnumInfoCacheSyncObject) {
303+
EnumInfoCache[targetType].Add(token.Value, foundFieldInfo);
304+
}
278305
}
279306
}
280307

@@ -326,12 +353,14 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
326353
private object MakeStruct(Type targetType, PhpSerializeToken token) {
327354
var result = Activator.CreateInstance(targetType);
328355
Dictionary<string, FieldInfo> fields;
329-
if (FieldInfoCache.ContainsKey(targetType)) {
330-
fields = FieldInfoCache[targetType];
331-
} else {
332-
fields = targetType.GetFields().GetAllFields(this._options);
333-
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
334-
FieldInfoCache.Add(targetType, fields);
356+
lock (FieldInfoCacheCacheSyncObject) {
357+
if (FieldInfoCache.ContainsKey(targetType)) {
358+
fields = FieldInfoCache[targetType];
359+
} else {
360+
fields = targetType.GetFields().GetAllFields(this._options);
361+
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
362+
FieldInfoCache.Add(targetType, fields);
363+
}
335364
}
336365
}
337366

@@ -365,12 +394,14 @@ private object MakeStruct(Type targetType, PhpSerializeToken token) {
365394
private object MakeObject(Type targetType, PhpSerializeToken token) {
366395
var result = Activator.CreateInstance(targetType);
367396
Dictionary<string, PropertyInfo> properties;
368-
if (PropertyInfoCache.ContainsKey(targetType)) {
369-
properties = PropertyInfoCache[targetType];
370-
} else {
371-
properties = targetType.GetProperties().GetAllProperties(this._options);
372-
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
373-
PropertyInfoCache.Add(targetType, properties);
397+
lock (PropertyInfoCacheSyncObject) {
398+
if (PropertyInfoCache.ContainsKey(targetType)) {
399+
properties = PropertyInfoCache[targetType];
400+
} else {
401+
properties = targetType.GetProperties().GetAllProperties(this._options);
402+
if (this._options.TypeCache.HasFlag(TypeCacheFlag.PropertyInfo)) {
403+
PropertyInfoCache.Add(targetType, properties);
404+
}
374405
}
375406
}
376407

@@ -405,7 +436,7 @@ private object MakeObject(Type targetType, PhpSerializeToken token) {
405436
}
406437

407438
private object MakeArray(Type targetType, PhpSerializeToken token) {
408-
var elementType = targetType.GetElementType();
439+
var elementType = targetType.GetElementType() ?? throw new InvalidOperationException("targetType.GetElementType() returned null");
409440
Array result = System.Array.CreateInstance(elementType, token.Children.Length / 2);
410441

411442
var arrayIndex = 0;

0 commit comments

Comments
 (0)