Manual Schema
When you need full control over the OData schema — custom type names, complex types, enum types, or non-standard mappings — you can build the schema by hand using the EdmBuilder API.
Building an entity type
use LaravelUi5\OData\Edm\EdmPrimitiveType;
use LaravelUi5\OData\Edm\Property\Property;
use LaravelUi5\OData\Edm\Type\EntityType;
use LaravelUi5\OData\Edm\Type\PrimitiveType;
$int32 = new PrimitiveType(EdmPrimitiveType::Int32);
$string = new PrimitiveType(EdmPrimitiveType::String);
$idProp = new Property('id', $int32);
$nameProp = new Property('name', $string);
$cityProp = new Property('city', $string);
$partnerType = new EntityType(
namespace: 'Partners.Data',
name: 'Partner',
key: [$idProp],
declaredProperties: [$idProp, $nameProp, $cityProp],
);EntityType constructor parameters
| Parameter | Type | Default | Purpose |
|---|---|---|---|
namespace | string | required | Schema namespace |
name | string | required | Type name |
baseType | ?EntityTypeInterface | null | Inheritance base type |
isAbstract | bool | false | Abstract type flag |
isOpen | bool | false | Open type (dynamic properties) |
hasStream | bool | false | Media entity flag |
key | list<PropertyInterface> | [] | Key properties |
declaredProperties | list<PropertyInterface> | [] | Structural properties |
declaredNavigationProperties | list<NavigationPropertyInterface> | [] | Navigation properties |
annotations | list<AnnotationInterface> | [] | Annotations |
Building navigation properties
A navigation property points at another entity type — something with its own key and its own set. Build the target first, then the type that references it:
use LaravelUi5\OData\Edm\Property\NavigationProperty;
$emailProp = new Property('email', $string);
$contactType = new EntityType(
namespace: 'Partners.Data',
name: 'Contact',
key: [$idProp],
declaredProperties: [$idProp, $nameProp, $emailProp],
);
$contactsNav = new NavigationProperty(
name: 'contacts',
targetType: $contactType,
isCollection: true,
);The partner type from above, now carrying it:
$partnerType = new EntityType(
namespace: 'Partners.Data',
name: 'Partner',
key: [$idProp],
declaredProperties: [$idProp, $nameProp, $cityProp],
declaredNavigationProperties: [$contactsNav],
);NavigationProperty constructor parameters
| Parameter | Type | Default | Purpose |
|---|---|---|---|
name | string | required | Property name |
targetType | EntityTypeInterface | required | Target entity type |
isCollection | bool | true | Collection vs single-valued |
isNullable | bool | true | Nullable flag |
partnerName | ?string | null | Partner nav property name |
isContainmentTarget | bool | false | Containment flag |
referentialConstraints | array | [] | FK mapping |
onDeleteAction | ?string | null | On-delete behavior |
annotations | list<AnnotationInterface> | [] | Annotations |
Building entity sets
use LaravelUi5\OData\Edm\Container\EntitySet;
use LaravelUi5\OData\Edm\Container\NavigationPropertyBinding;
$contactSet = new EntitySet(
name: 'Contacts',
entityType: $contactType,
);
$partnerSet = new EntitySet(
name: 'Partners',
entityType: $partnerType,
navigationPropertyBindings: [
new NavigationPropertyBinding('contacts', 'Contacts'),
],
);Navigation property bindings tell the OData runtime which entity set holds the target entities for each navigation property. One binding per navigation property: the name on the left is the property declared on the entity type, the name on the right is the entity set it resolves to.
Building complex types
A complex type is a structured value, not a resource: it has properties but no key, no entity set, and no URL of its own. An address is the canonical case — it belongs to the partner that carries it, and nobody addresses it separately.
use LaravelUi5\OData\Edm\Type\ComplexType;
$streetProp = new Property('street', $string);
$zipProp = new Property('zip', $string);
$addressType = new ComplexType(
namespace: 'Partners.Data',
name: 'Address',
declaredProperties: [$streetProp, $cityProp, $zipProp],
);Use it the way you use a primitive type — as the type of a property, which then joins the entity type's declaredProperties:
$addressProp = new Property('address', $addressType);That is the line between the two: if it needs its own key and its own set, it is an entity type and you reach it through a navigation property. If it only ever travels with its parent, it is a complex type and it is just a property.
Building enum types
An EnumType projects a PHP enum onto the wire. The shortcut takes the class-string:
use LaravelUi5\OData\Edm\Container\EnumType;
enum PartnerTier: int
{
case Single = 1;
case Team = 2;
case Estate = 3;
}
$tierType = EnumType::fromBackedEnum('Partners.Data', PartnerTier::class);
$tierProp = new Property('tier', $tierType);The EDM name is the PHP short class name, members are emitted in declaration order under their case names, and the underlying type is fixed at Edm.Int32. Only int-backed enums work — a string-backed or pure enum is rejected with an exception, because OData v4 enum types are integer-keyed.
Build one by hand when the EDM name or the member values should not follow the PHP enum — or when there is no PHP enum at all:
use LaravelUi5\OData\Edm\Container\EnumMember;
$tierType = new EnumType(
namespace: 'Partners.Data',
name: 'Tier',
members: [
new EnumMember('Single', 1),
new EnumMember('Team', 2),
new EnumMember('Estate', 3),
],
);isFlags: true marks it as a bitmask, so members combine.
Registering on the builder
protected function configure(EdmBuilderInterface $builder): EdmBuilderInterface
{
// ... build types and sets ...
return $builder
->namespace('Partners.Data')
->addComplexType($addressType)
->addEnumType($tierType)
->addEntityType($contactType)
->addEntityType($partnerType)
->addEntitySet($contactSet)
->addEntitySet($partnerSet);
}EdmBuilder methods
| Method | Purpose |
|---|---|
namespace(string) | Set the schema namespace |
alias(string) | Set an optional namespace alias |
containerName(string) | Set the entity container name (default: DefaultContainer) |
version(string) | Set the OData version |
addEntityType(EntityTypeInterface) | Register an entity type |
addComplexType(ComplexTypeInterface) | Register a complex type |
addEnumType(EnumTypeInterface) | Register an enum type |
addTypeDefinition(TypeDefinitionInterface) | Register a type definition |
addFunction(FunctionInterface) | Register a function |
addEntitySet(EntitySetInterface) | Register an entity set |
addSingleton(SingletonInterface) | Register a singleton |
addFunctionImport(FunctionImportInterface) | Register a function import |
addReference(ReferenceInterface) | Add a vocabulary reference |
build() | Freeze into immutable EdmxInterface |
All methods return $this for fluent chaining. After build() is called, the builder cannot be mutated.
Available primitive types
See the full list in EdmPrimitiveType:
Binary, Boolean, Byte, Date, DateTimeOffset, Decimal, Double, Duration, Guid, Int16, Int32, Int64, SByte, Single, Stream, String, TimeOfDay
Plus geography and geometry spatial types.