Global navigation

   Documentation Center
   eZ Studio & eZ Platform
     User Manual
     Technical Manual
     Glossary
   eZ Publish 4.x / legacy

 
eZ Publish (5.x)

eZ Publish 5.x | For eZ Platform & eZ Studio topics see Technical manual and User manual, for eZ Publish 4.x and Legacy topics see eZ Publish legacy

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

As said in the introduction, the Type class of a FieldType must implement eZ\Publish\SPI\FieldType\FieldType (later referred to as "FieldType interface").

All native FieldTypes also extend the eZ\Publish\Core\FieldType\FieldType abstract class, that implements this interface, and provides implementation facilities through a set of abstract methods of its own. In this case, Type classes implement a mix of methods from the FieldType interface and from the abstract FieldType .

Let’s go over those methods and their implementation.

Identification method: getFieldTypeIdentifier()

It must return the string that uniquely identifies this FieldType (DataTypeString in eZ Publish 4). We will use "eztweet":

eZ/FieldType/Tweet/Type

Value handling methods: createValueFromInput() and checkValueStructure()

Both methods are used by the implementation of acceptValue(). This FieldType interface method checks and transforms various input values into the type's own Value class: eZ\FieldType\Tweet\Value. This method must either return the Value object it was able to create out of the input value, or return this value untouched. The API will detect this, and inform that the input value was not accepted.

The only acceptable value for our type is the URL of a tweet (we could of course imagine more possibilities). 

This should do:

protected function createValueFromInput( $inputValue )

{

   if ( is_string( $inputValue ) )

   {

       $inputValue = new Value( array( 'url' => $inputValue ) );

   }


 

   return $inputValue;

}


 

Use this method to provide convenient ways to set an attribute’s value using the API. This can be anything from primitives to complex business objects.


 

We now need to implement checkValueStructure() in order to check that the Value that was created is structurally sane. In our case, we want to be sure that the Tweet\Value::$url is a string:


 

protected function checkValueStructure( BaseValue $value )

{

   if ( !is_string( $value->url ) )

   {

       throw new InvalidArgumentType(

           '$value->url',

           'string',

           $value->url

       );

   }

}


 

Notice that since we use InvalidArgumentType, we add it to the imports at the top of the file:


 

use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;


 

The last method when it comes to value handling is getEmptyValue(). Nothing fancy here: we will return an empty instance of Tweet\Value. This is where we basically initialize and define what an empty value is, depending on our business requirements.


 

public function getEmptyValue()

{

   return new Value;

}


 

If you run the unit tests at this point, you should get about five failures, all of them on the fromHash() or toHash() methods.

Validation methods: validateValidatorConfiguration() and validate()

The Type class is also responsible for validating input data, as well as configuration input data. In this tutorial, we will run two validation operations on input data:

  • validate submitted urls, ensuring they actually reference a twitter status;

  • limit input to a known list of authors, as an optional validation step.


 

validateValidatorConfiguration() will be called when an instance of the Field Type is added to a Content Type, to ensure that the validator configuration is valid. For a TextLine, that has a length validator, it would mean checking that both min length and max length are positive integers, and that min is lower than max.


 

The implementation is quite simple: when an instance of the type is added to a content type, the method receives an array containing the configuration for the validators used by the Type. For TextLine, it looks like this:


 

array(

   'StringLengthValidator' => array(

       'minStringLength' => 0,

       'maxStringLength' => 100

   )

);


 

Each level one key is the name of a validator, as acknowledged by the Type (by convention the name of the validator class). That key contains a set of parameter name / parameter value rows. We must check that:

  • all the validators in this array are known to the type

  • arguments for those validators are valid and have sane values

The method must return an array of error messages if errors are found in the configuration.


 

We do not need to include mandatory validators if they don’t have options. We will in any case run the validation code in the validate() method. Here is an example of what our Type excpects as validation configuration:

array(

   ‘TweetAuthorValidator’ => array(

       ‘AuthorList’ => array( ‘johndoe’, ‘janedoe’ )

   )

);


 

The configuration says that tweets must must be either by johndoe, or by janedoe. If we had not provided TweetAuthorValidator at all, it would have been ignored.


 

We will iterate over the items in $validatorConfiguration, and:

  • log errors for those we don’t know about;

  • check that provided arguments are known and valid:

    • TweetAuthorValidator accepts a non-empty array of valid twitter usernames


 

public function validateValidatorConfiguration( $validatorConfiguration )

{

   $validationErrors = array();


 

   foreach ( $validatorConfiguration as $validatorIdentifier => $constraints )

   {

       // Report unknown validators

       if ( !$validatorIdentifier != 'TweetAuthorValidator' )

       {

           $validationErrors[] = new ValidationError( "Validator '$validatorIdentifier' is unknown" );

           continue;

       }


 

       // Validate arguments from TweetAuthorValidator

       if ( !isset( $constraints['AuthorList'] ) || !is_array( $constraints['AuthorList'] ) )

       {

           $validationErrors[] = new ValidationError( "Missing or invalid AuthorList argument" );

           continue;

       }


 

       foreach ( $constraints['AuthorList'] as $authorName )

       {

           if ( !preg_match( '/^[a-z0-9_]{1,15}$/i', $authorName ) )

           {

               $validationErrors[] = new ValidationError( "Invalid twitter username" );

           }

       }

   }


 

   return $validationErrors;

}


 

validate() is the method that runs the actual validation on data, when a content is created with a field of this type:


 

   public function validate( FieldDefinition $fieldDefinition, SPIValue $fieldValue )

   {

       $errors = array();


 

       if ( $this->isEmptyValue( $fieldValue ) )

       {

           return $errors;

       }


 

       // Tweet Url validation

       if ( !preg_match( '#^https?://twitter.com/([^/]+)/status/[0-9]+$#', $fieldValue->url, $m ) )

           $errors[] = new ValidationError( "Invalid twitter status url %url%", null, array( $fieldValue->url ) );


 

       $validatorConfiguration = $fieldDefinition->getValidatorConfiguration();

       if ( isset( $validatorConfiguration['TweetAuthorValidator'] ) )

       {

           if ( !in_array( $m[1], $validatorConfiguration['TweetAuthorValidator']['AuthorList'] ) )

           {

               $errors[] = new ValidationError(

                   "Twitter user %user% is not in the approved author list",

                   null,

                   array( $m[1] )

               );

           }

       }


 

       return $errors;

   }


 

First, we validate the url with a regular expression. If it doesn’t match, we add an instance of ValidationError to the return array. Note that the tested value isn’t directly embedded in the message but passed as an argument. This ensures that the variable is properly encoded in order to prevent attacks, and allows for singular/plural phrases using the 2nd parameter.


 

Then, if our FieldType instance’s configuration contains a TweetAuthorValidator key, we check that the username in the status url matches one of the valid authors.

Metadata handling methods: getName() and getSortInfo().

FieldTypes require two methods related to Field metadata:

  • getName() is used to generate a name out of a field value, either to name a content object (naming pattern in legacy) or to generate a part for an URL Alias.

  • getSortInfo() will be used by the persistence layer to obtain the value it can use to sort & filter on a field of this type

Obviously, a tweet’s full URL isn’t really suitable as a name. Let’s use a subset of it: <username>-<tweetId> should be reasonable enough, and suitable for both sorting and naming.


 

We can assume that this method will not be called if the field is empty, and will assume that the URL is a valid twitter URL:


 

public function getName( SPIValue $value )

{

   return preg_replace(

       '#^https?://twitter\.com/([^/]+)/status/([0-9]+)$#',

       '$1-$2',

       (string)$value->url );

}


 

protected function getSortInfo( CoreValue $value )

{

   return $this->getName( $value );

}


 

In getName(), we run a regular expression replace on the URL to extract the part we’re interested in.


 

This name is a perfect match for getSortInfo(), as it allows us to sort on the tweet’s author and on the tweet’s ID.

FieldType serialization methods

We can now implement fromHash() and toHash(). Both methods are core to the REST API, since they are used to hash fields values into exportable hashes.


 

In our case, it is quite easy:

  • toHash() will build a hash with every property from Tweet\Value;

  • fromHash() will instantiate a Tweet\Value with the hash it receives.


 

public function fromHash( $hash )

{

   if ( $hash === null )

   {

       return $this->getEmptyValue();

   }

   return new Value( $hash );

}


 

/**

* @param $value Value

*/

public function toHash( SPIValue $value )

{

   if ( $this->isEmptyValue( $value ) )

   {

       return null;

   }

   return array(

       'url' => $value->url

   );

}


 

And this is it for the API part ! You can run the unit tests, and they should all pass now.


  • No labels