Actualización: 15/02/2018

En este post explicaré como crear desde cero una nueva entidad en Magento 2 con tres simples campos. Para ello, hablaré de todo lo relacionado con repositorios, API, interfaces y extensión de atributos y explicaremos dichos conceptos.

Cabe destacar que todo el módulo sigue las recomendaciones estándar de PHP.

A continuación pasaremos a explicar los pasos a seguir para conseguir nuestro objetivo.

Creación del módulo

app / code / Interactiv4 / Post / registration.php

app / code / Interactiv4 / Post / composer.json

 {
    "name": "interactiv4/post",
    "description": "Example Module of Custom Entity",
    "require": {
        "php": "~7.0.0",
        "magento/magento-composer-installer": "*"
    },
    "type": "magento2-module",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Interactiv4\\Post\\\": ""
        }
    }
 }

app / code / Interactiv4 / Post / etc / module.xml

Interfaz de modelo de datos

Esta interfaz, contendrá todos los getters y setters y definiremos en constantes el nombre de la tabla y campos de nuestra entidad en la base de datos.

app / code / Interactiv4 / Post / Api / Data / EntityInterface.php

Extensión de atributos

Nuestra interfaz extiende de Magento \ Framework \ Api \ CustomAttributesDataInterface, la cual a su vez extiende de Magento \ Framework \ Api \ ExtensibleDataInterface.

La pregunta es, ¿para qué queremos que nuestra interfaz extienda de ExtensibleDataInterface? Esto sólo es necesario si deseamos que otros módulos puedan agregar atributos a nuestra entidad, ya que no podemos cambiar la interfaz cada vez que queramos añadir un nuevo atributo. Si es así, necesitamos agregar las funciones getExtensionAttributes() y setExtensionAttributes().

Es muy importante indicar de manera correcta la devolución y parámetro en estas 2 nuevas funciones. Magento generará esta interfaz si el nombre indicado es el correcto. Si la interfaz que queremos hacer extensible es Interactiv4 / Post / Api / Data / EntityInterface, entonces el tipo de extensión de atributos será Interactiv4 / Post / Api / Data / EntityExtensionInterface añadiendo la palabra Extension después del nombre de la entidad y antes del sufijo Interface.

Además de todo esto, cabe destacar que tanto la interfaz del modelo de datos como la del repositorio tienen que cumplir lo siguiente:

  • Añadir todas las anotaciones PHPDoc para todos los parámetros y devoluciones de cada función.
  • Usar el nombre completo de clases en el PHPDoc Block. Esto significa que la utilización del use al principio de las clases no funcionará.

Base de datos

El siguiente paso es crear el esquema de base de datos de nuestra nueva entidad.

app / code / Interactiv4 / Post / Setup / InstallSchema.php

startSetup();
         $table = $installer->getConnection()->newTable(
             $installer->getTable(EntityInterface::TABLE)
         )->addColumn(
             EntityInterface::ID,
             Table::TYPE_INTEGER,
             null,
             ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
             'Id'
         )->addColumn(
             EntityInterface::NAME,
             Table::TYPE_TEXT,
             255,
             ['nullable' => true],
             'Name'
         )->addColumn(
             EntityInterface::DESCRIPTION,
             Table::TYPE_TEXT,
             null,
             ['nullable' => true],
             'Description'
         )->setComment(
             'Custom Entity'
         );
         $installer->getConnection()->createTable($table);
         $installer->endSetup();
     }
 }

Interfaz de repositorio

Los repositorios en Magento son los encargados de las funcionalidades CRUD. En Magento todos deben tener los métodos save, getById, delete y getList, en nuestro caso hemos añadido alguno más para hacerlo un poco más completo cubriendo tus necesidades.

app / code / Interactiv4 / Post / Api / EntityRepositoryInterface.php

SearchResults

El método getList del repositorio devuelve una instancia de SearchResultsInterface. Este método podría devolver un array de objetos que coincidan con el SearchCriteria especificado, pero crearemos esta interfaz para añadir información sobre los valores que debe devolver.

app / code / Interactiv4 / Post / Api / Data / EntitySearchResultsInterface.php

Implementación del modelo de datos

Si nuestra interfaz de modelo de datos extiende de Magento \ Framework \ Api \ ExtensibleDataInterface, su implementación deberá extender de Magento \ Framework \ Model \ AbstractExtensibleModel.

app / code / Interactiv4 / Post / Model / Entity.php

_init(ResourceModelEntity::class);
     }
     /**
      * @inheritdoc
      */
     public function getName()
     {
         return $this->_getData(self::NAME);
     }
     /**
      * @inheritdoc
      */
     public function setName($name)
     {
         return $this->setData(self::NAME, $name);
     }
     /**
      * @inheritdoc
      */
     public function getDescription()
     {
         return $this->_getData(self::DESCRIPTION);
     }
     /**
      * @inheritdoc
      */
     public function setDescription($description)
     {
         return $this->setData(self::DESCRIPTION, $description);
     }
     /**
      * @inheritdoc
      */
     public function getExtensionAttributes()
     {
         return $this->_getExtensionAttributes();
     }
     /**
      * @inheritdoc
      */
     public function setExtensionAttributes(EntityExtensionInterface $extensionAttributes)
     {
         return $this->_setExtensionAttributes($extensionAttributes);
     }
     public function getIdentities()
     {
         return [self::CACHE_TAG . '_' . $this->getId()];
     }
 }

En este ejemplo, nuestra clase implementa de Magento \ Framework \ DataObject \ IdentityInterface, esto hace que nuestro modelo use el método getIdentities(), el cual devuelve un único id para el modelo. IdentityInterface sólo se debe usar si nuestro modelo necesita refrescar la caché después de alguna operación en la base datos y mostrar la información en el frontend. El método getIdentities() es usado por Varnish para añadir la información en la cabecera como X-Magento-Tags.

  • $_cacheTag: identificador único para usarlo dentro del almacenamiento de caché.
  • $_eventPrefix: prefijo único usado a la hora de ejecutar los eventos. Esto hará que se creé un único evento para nuestra entidad, como por ejemplo interactiv4_post_entity_model_save_before.

Implementación de repositorio

app / code / Interactiv4 / Post / Model / EntityRepository.php

 entityFactory                       = $entityFactory;
         $this->resourceModelEntity                 = $resourceModelEntity;
         $this->entityCollectionFactory             = $entityCollectionFactory;
         $this->entitySearchResultsInterfaceFactory = $entitySearchResultsInterfaceFactory;
         $this->collectionProcessor                 = $collectionProcessor;
         $this->extensionAttributesJoinProcessor    = $extensionAttributesJoinProcessor;
     }
     /**
      * @inheritdoc
      */
     public function save(EntityInterface $entity)
     {
         $this->resourceModelEntity->save($entity);
         return $entity;
     }
     /**
      * @inheritdoc
      */
     public function getById($entityId)
     {
         return $this->get($entityId);
     }
     /**
      * @inheritdoc
      */
     public function get($value, $attributeCode = null)
     {
         /** @var Entity $entity */
         $entity = $this->entityFactory->create()->load($value, $attributeCode);
         if (!$entity->getId()) {
             throw new NoSuchEntityException(__('Unable to find entity'));
         }
         return $entity;
     }
     /**
      * @inheritdoc
      */
     public function delete(EntityInterface $entity)
     {
         $entityId = $entity->getId();
         try {
             $this->resourceModelEntity->delete($entity);
         } catch (Exception $e) {
             throw new CouldNotDeleteException(
                 __('Unable to remove entity %1', $entityId)
             );
         }
         return true;
     }
     /**
      * @inheritdoc
      */
     public function deleteById($entityId)
     {
         $entity = $this->getById($entityId);
         return $this->delete($entity);
     }
     /**
      * @inheritdoc
      */
     public function getList(SearchCriteriaInterface $searchCriteria)
     {
         /** @var Collection $collection */
         $collection = $this->entityCollectionFactory->create();
         $this->extensionAttributesJoinProcessor->process($collection, EntityInterface::class);
         $this->collectionProcessor->process($searchCriteria, $collection);
         /** @var EntitySearchResultsInterface $searchResults */
         $searchResults = $this->entitySearchResultsInterfaceFactory->create();
         $searchResults->setSearchCriteria($searchCriteria);
         $searchResults->setItems($collection->getItems());
         $searchResults->setTotalCount($collection->getSize());
         return $searchResults;
     }
 }
 

Factory

En POO, un Factory es usado para instanciar un objeto. El nombre de la clase Factory es el nombre de la clase Model con el sufijo Factory. Esta clase Magento la genera automáticamente, por lo que no es necesario crearla. Para crear una instancia del objeto a partir de una clase Factory haremos lo siguiente:

$entity = $this->entityFactory->create();

Implementación SearchResults

Esta clase es la más simple de todas, ya que hereda toda su funcionalidad de Magento \ Framework \ Api \ SearchResults

app / code / Interactiv4 / Post / Model / EntitySearchResults.php

ResourceModel

Como sabes, el modelo contiene la lógica de base de datos general, pero no ejecuta consultas SQL, de esto se encarga el resourceModel.

app / code / Interactiv4 / Post / Model / ResourceModel / Entity.php

_init(EntityInterface::TABLE, EntityInterface::ID);
     }
 }

app / code / Interactiv4 / Post / Model / ResourceModel / Entity / Collection.php

_init(ModelEntity::class, ResourceModelEntity::class);
     }
 }

Preference

Después de realizar todo lo comentado anteriormente, aún nos queda un último paso. Especificar las interfaces como dependencias de otras clases.

app / code / Interactiv4 / Post / etc / di.xml

En todos los ejemplos escritos anteriormente, hacemos uso de las interfaces y no de sus clases, ya que una interfaz no debe cambiar en el futuro, pero una clase si puede hacerlo. De esta manera nos aseguramos que no haya problemas o conflictos en el futuro.

Habilitar API para el uso de terceros

app / code / Interactiv4 / Post / etc / webapi.xml

Es importante que el nombre de los parámetros de la API coincidan con el nombre del parámetro del repositorio. El param entityId debe coincidir en /V1/interactiv4_entities/:entityId con public function getById($entityId);

Permisos

  • <resource ref=»anonymous»/> hace que el recurso esté abierto públicamente sin ningún tipo de restricción.
  • <resource ref=»self»/> hace que el recurso esté abierto sólo a clientes conectados.
  • Para que un recurso sea solo accesible para usuarios administradores, este recurso debe de estar definido en /etc/acl.xml. En nuestro caso hemos definido el resource Interactiv4_Post::manage

app / code / Interactiv4 / Post / etc / acl.xml

Espero que os haya sido de ayuda y cualquier duda podéis dejar un comentario.

En este link podréis descargar el código completo.