Conceptos generales
Las siguientes secciones entran en detalles sobre algunos sistemas y conceptos generales que se encontrarán al desarrollar un complemento de XenForo. Si se está familiarizado con el desarrollo de XenForo 1.x, unos cuantos de estos conceptos serán familiares, aunque vale la pena revisarlos, ya que hay algunas nuevas herramientas y características excelentes para ayudar al desarrollo de complementos.
Componentes de proveedores
XF2XF2 no está motorizado por un marco de trabajo específico como lo fue XF1, sin embargo, hemos empleado el uso de ciertos paquetes de código abierto populares y bien probados, que ayudan en tareas específicas. Por ejemplo, usamos un proyecto denominado SwiftMailer para el envío de emails y otro proyecto denominado Guzzle como cliente HTTP. Todos los proyectos de terceras partes se cargan desde el directorio src/vendor
.
Actualmente no es posible a los desarrolladores el agregar sus propias dependencias en esta ubicación.
Entorno Integrado de Desarrollo (EID) Integrated Development Environment (IDE)
Antes de comenzar a trabajar con el desarrollo de XF2, es posible que se desee pasar algún tiempo evaluando la aplicación con la que realmente se va a crear y editar archivos PHP. Esto es lo que se conoce comúnmente como EID o IDE. Existen disponibles un número de opciones que van desde el básico Notepad a algo como Sublime Text que puede expanderse para tener un mejor soporte de PHP con los complementos, hasta un EID adecuado tal como PhpStorm. Internamente, nuestro preferido es PhpStorm EID. Es un producto comercial y premium, pèro tiene alternativas libres disponibles. De cualquier modo, nadie va a indicarte cuál es la mejor aplicación para tus necesidades, y debes pasar algún tiempo con una serie de productos (incluso alguno gratuito) y usar esa experiencia para encontrar tu preferido.
Autoinicio
XF2 usa un autocargador que se genera automaticamente por Composer. Esto permite a todos los códigos de XF, código de proveedores de terceras partes y a cualquier código de desarrolladores de complementos ser incluidos automáticamente en el proyecto entero sin tener que incluir manualmente las clases include/require
.
La raíz de autocarga para todos los complementos de XF es el directorio src/addons
. Esto significa que todos los nombres de clase deberán ser relativos a esta ubicación base. También vale la pena señalar que XF2 emplea un patrón estricto de "clase por archivo" para nominar. Cada archivo sólo puede contener una clase y el nombre de ésta clase debe identificar la ubicación exacta del archivo de clase PHP en el sistema de archivos.
Por ejemplo, si se quiere crear una nueva clase en un archivo denominado src/addons/Demo/Setup.php
(donde Demo
es el ID del complemento) deberá denominarse Demo\Setup
. A la inversa, si se tiene una clase denominada Demo\Entidad\Cosa
se conocerá que el archivo de esta clase está ubicado en la ruta src/addons/Demo/Entidad/Cosa.php
.
Espacios de nombres
A traves de XF usamos espacios de nombres de modo que podamos referenciar las clases en el mismo espacio de nombres más sucintamente. Se recomienda que todos los complementos usen también espacios de nombres. En el ejemplo anterior hablamos de una clase denominada Demo\Setup
. Usando espacios de nombres, La clase podría haberse llamado simplemente Setup
pero el nombre del espacio deberá configurarse a Demo
. Como ejemplo más concreto, También hablamos sobre una clase denominada Demo\Entidad\Cosa
. Veamos cómo sería el código PHP para esta clase:
<?php
namespace Demo\Entidad;
class Cosa
{
}
Si hubo una clase llamada OtraCosa
en el directorio src/addons/Demo/Entidad
, podríamos hacer referencia a esta clase Cosa
simplemente como OtraCosa
porque la case está en el mismo espacio de nombres Demo\Entidad
.
Nombres cortos de clase
Occasionalmente, las clases referenciadas en XF son recortadas. Por ejemplo, si se desea invocar a la entidad User
(más sobre entidades, debajo) se puede ver el nombre de clase referenciado simplemente como XF:User
. El uso de nombres de clase recortados y el nombre completo de la clase se resuleve y es enteramente sensible al contexto. De otro modo, en un contexto de una llamada a una entidad, el nombre recortado de la clase se resolverá al siguiente nombre completo de clase XF\Entidad\User
. La parte XF
indica la ruta del archivo (basada en el ID del complemento), la parte Entidad
se implica llamando a la entidad y la parte User
indica la entidad específica. Similarmente, cuando se comienza a crear clases propias, también pueden usarse nombres recortados de clase para referenciarlas. Por ejemplo, si se precisa crear una nueva entidad Cosa
para el complemento Demo
, puede escribirse los siguiente:
\XF::em()->create('Demo:cosa');
Esto resolvería la clase Demo\Entidad\Cosa
. Similarmente, si se quiere acceder al repositorio Cosa
, se podría escribir lo que sigue:
\XF::repository('Demo:Cosa');
Hay que advertir cómo los nombres recortados de clase son idénticos. La llamada al repositorio resolvería Demo\Repository\Cosa
.
Extender clases
Gran cantidad de clases de XF2 son extensibles lo que permite a los desarrolladores extender y sobreescribir el código motor sin tener que editarlo directamente. Si se está familiarizado con el desarrollo de XF1, se estará algo familiarizado con el siguiente proceso:
- Crear un archivo detector de PHP
- Crear una clase que en última instancia extenderá la clase original
- Escribir una función que coincida con la firma de devolución de llamada esperada de uno de los eventos
load_class
y agregue el nombre de la clase extendida - Agregar un "Detector de evento de código" en el panel de control de administración que especifica la clase Detector y el nombre del método para la función arriba mencionada, y optionalmente un indicio sobre la clase que se está extendiendo
En XF2 se han eliminado estos eventos a favor de un sistema específico denominado "Extensiones de clase". El proceso es como sigue:
- Crear una clase que estenderá por último la clase original
- Agregar una "Extensión de clase" en el panel de control de administración que especifique el nombre de la clase que se está extendiendo y el nombre de la clase que lo está extendiendo
Esto claramente reduce algunos de los elementos necesarios para extender las clases, y también provee un dedicado UI para ver y gestionar estas extensiones. Echemos un vistazo al proceso extendiendo el controlador público Member
y agregando una nueva acción que muestre un simple mensaje.
Lo primero a hacer es crear un complemento. Anteriormente, se explicó cómo hacerlo usando el comando xf-addon:create
aquí. En este ejemplo, se asumirá que se ha creado un complemento con un ID y un título de "Demo".
Ahora se tendrá un archivo addon.json para este complemento en la siguiente ubicación src/addons/Demo/addon.json
.
<?php
namespace Demo\XF\Pub\Controller;
class Member extends XFCP_Member
{
}
Si se está familiarizado con la extensión de clases de PHP pero no se está con XF de modo general, el ejemplo de arriba puede ser confuso inicialmente. La razón de esto es que se puede haber estado esperando para extender la clase XF\Pub\Controller\Member
directamente, en lugar de XFCP_Member
. En XF usamos el sistema "Proxy de clase de Xenforo (XenForo Class Proxy" - XFCP para más corto) para generar una "cadena de herencia" que, en último lugar, permite que una sola clase sea extendida por múltiples complementos. La convención es hacer referencia a una clase extendida ficticia que es el nombre de la clase Member
actual y prefijarla con XFCP_
.
La clase se ha creado ahora, podemos crear una extensión de clase en el panel de control de asministración > Desarrollo > Extensiones de clase > Agregar extensión de clase.
Todo lo que se necesita hacer es introducir el nombre de la clase base (XF\Pub\Controller\Member
) en el primer campo, y el nombre de la clase extendida (la que justo se acaba de crear) en el segundo campo (Demo\XF\Pub\Controller\Member
) y hacer clic en el botón "Guardar".
La extensión de clase debe estar activa ahora aunque actualmente no hace nada. Para que algo suceda, necesitamos anular un método existente dentro de esta clase, creando un método del mismo nombre que uno existente o añadiendo un nuevo método por completo. Hagamos esto último:
<?php
namespace Demo\XF\Pub\Controller;
class Member extends XFCP_Member
{
publicfunction actionHolaMundo()
{
return $this->message('¡Hola Mundo!');
}
}
Hablaremos más sobre controladores, acciones y respuestas en el apartado Básicos sobre controladores, así que no preocuparse especialmente por no entender esto ahora mismo.
Ahora hemos añadido algo de código a nuestro controlador extendido, vamos a verlo en acción. Simplemente introducir la siguiente URL (relativa a la URL del foro): index.php?members/hello-world
. ¡Podrá verse mostrado ahora una mensaje "¡Hola mundo!"!
Como se mencionó anteriormente, también es posible sobreescribir métodos existentes dentro de una clase. Por ejemplo, si cambiamos actionHolaMundo()
por actionIndex()
ya no se tendría entonces una lista de "Miembros notables", ¡en su lugar, se mostraría el mensaje "¡Hola Mundo!"! Esta no es la manera correcta de extender una acción de controlador existente (o cualquier método de clase, de hecho) aunque se verá más detalladamente eso en la sección Modificar la respuesta de acción de un controlador (correctamente).
Type hinting
Se instancian varios obletos en XF mediante métodos de fábrica. Por ejemplo, si se desea instanciar un repositorio específico, debemos escribir lo siguiente:
$repo = \XF::repository('Demo:Cosa');
Esta es una alta, conveniente y consistentemente vía de instanciar un objeto. Sabemos, sólo por mirarlo, qué objeto será instanciado. El código resultante en ese método sabe cómo devolver el objeto correcto que hemos solicitado.
Desafortunadamente, sin embargo, el IDE probablemente no tiene ni idea (al menos por defecto). En lo que se refiere al IDE, este método devolverá una instancia del objecto en el XF\Mvc\Entidad\Repository
. Esto es útil para ciertas extensiones, pero hay potencialmente muchos métodos disponibles en el específico objeto Demo\Repository\Cosa
que el EID no conoce. En última instancia, esto significa que cuando se trata de usar en el código el objeto $repo
, el EID no podrá hacer sugerencias o auto-completar nombres de método ni sus argumentos si es preciso.
Aquí es cuando resulta útil type hinting, y la sintaxis debe soportar, de forma estandar, por la mayoría de EIDs y algunos editores de texto "PHP aware". Cambiaríamos nuestra llamada al repositorio de la siguiente manera:
/**
@var \Demo\Repository\Cosa $repo */
$repo = \XF::repository('Demo:Cosa');
El type hint de encima de la llamada al repositorio ahora le dice al EID que $repo
se refiere a un objeto representado por la clase Demo\Repository\Cosa
en lugar del objeto que automáticamente dedujo originalmente.
Type hinting es especialmente útil al extender clases, además. Un problema potencial con los métodos de extensión de clases es que, esencialmente, las clases no extienden a la clase original que se desea extender, pero en su lugar esto se pone en proxy a través de una clase que en realidad no existe, ej. XFCP_Member
así como el ejemplo de arriba.
Un truco que podemos hacer aquí es agregar algún código inalcanzable a la clase. Esto es, código que existe en la clase, pero que PHP nunca podrá ejecutar actualmente. Veamos la clase extendida revisada con la que agregamos:
<?php
namespace Demo\XF\Pub\Controller;
class Member extends XFCP_Member
{
public function actionHola Mundo()
{
return $this->message('¡Hola Mundo!');
}
}
// ******************** FOR IDE AUTO COMPLETE ********************
if(false)
{
class XFCP_Member extends\XF\Pub\Controller\Member{}
}
Básicamente ahora hemos añadido una referencia que el EID puede leer, pero PHP no puede (lo que es bueno, de lo contrario esto ¡podría romper las cosas!), por que el EID ahora entiende que cuando se usa $this
dentro de cualquiera de los métodos de la clase extendida, puede sugerir y autocompletar métodos y propiedades disponibles en el controlador Member o en uno de sus primarios.