Skip to content

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 ​

php
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 ​

ParameterTypeDefaultPurpose
namespacestringrequiredSchema namespace
namestringrequiredType name
baseType?EntityTypeInterfacenullInheritance base type
isAbstractboolfalseAbstract type flag
isOpenboolfalseOpen type (dynamic properties)
hasStreamboolfalseMedia entity flag
keylist<PropertyInterface>[]Key properties
declaredPropertieslist<PropertyInterface>[]Structural properties
declaredNavigationPropertieslist<NavigationPropertyInterface>[]Navigation properties
annotationslist<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:

php
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:

php
$partnerType = new EntityType(
    namespace: 'Partners.Data',
    name: 'Partner',
    key: [$idProp],
    declaredProperties: [$idProp, $nameProp, $cityProp],
    declaredNavigationProperties: [$contactsNav],
);
ParameterTypeDefaultPurpose
namestringrequiredProperty name
targetTypeEntityTypeInterfacerequiredTarget entity type
isCollectionbooltrueCollection vs single-valued
isNullablebooltrueNullable flag
partnerName?stringnullPartner nav property name
isContainmentTargetboolfalseContainment flag
referentialConstraintsarray[]FK mapping
onDeleteAction?stringnullOn-delete behavior
annotationslist<AnnotationInterface>[]Annotations

Building entity sets ​

php
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.

php
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:

php
$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:

php
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:

php
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 ​

php
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 ​

MethodPurpose
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.