mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-02 15:51:48 +03:00
Alternate model naming strategy.
This commit is contained in:
parent
fad6f576ee
commit
a5a13501e2
@ -137,6 +137,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->arrayNode('swagger')
|
->arrayNode('swagger')
|
||||||
->addDefaultsIfNotSet()
|
->addDefaultsIfNotSet()
|
||||||
->children()
|
->children()
|
||||||
|
->scalarNode('model_naming_strategy')->defaultValue('dot_notation')->end()
|
||||||
->scalarNode('api_base_path')->defaultValue('/api')->end()
|
->scalarNode('api_base_path')->defaultValue('/api')->end()
|
||||||
->scalarNode('swagger_version')->defaultValue('1.2')->end()
|
->scalarNode('swagger_version')->defaultValue('1.2')->end()
|
||||||
->scalarNode('api_version')->defaultValue('0.1')->end()
|
->scalarNode('api_version')->defaultValue('0.1')->end()
|
||||||
|
@ -64,6 +64,7 @@ class NelmioApiDocExtension extends Extension
|
|||||||
$container->setParameter('nelmio_api_doc.swagger.swagger_version', $config['swagger']['swagger_version']);
|
$container->setParameter('nelmio_api_doc.swagger.swagger_version', $config['swagger']['swagger_version']);
|
||||||
$container->setParameter('nelmio_api_doc.swagger.api_version', $config['swagger']['api_version']);
|
$container->setParameter('nelmio_api_doc.swagger.api_version', $config['swagger']['api_version']);
|
||||||
$container->setParameter('nelmio_api_doc.swagger.info', $config['swagger']['info']);
|
$container->setParameter('nelmio_api_doc.swagger.info', $config['swagger']['info']);
|
||||||
|
$container->setParameter('nelmio_api_doc.swagger.model_naming_strategy', $config['swagger']['model_naming_strategy']);
|
||||||
|
|
||||||
if ($config['cache']['enabled'] === true) {
|
if ($config['cache']['enabled'] === true) {
|
||||||
$arguments = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor')->getArguments();
|
$arguments = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor')->getArguments();
|
||||||
|
@ -47,6 +47,10 @@ class SwaggerConfigCompilerPass implements CompilerPassInterface
|
|||||||
|
|
||||||
$authentication = $container->getParameter('nelmio_api_doc.sandbox.authentication');
|
$authentication = $container->getParameter('nelmio_api_doc.sandbox.authentication');
|
||||||
|
|
||||||
|
$formatter->setArguments(array(
|
||||||
|
$container->getParameter('nelmio_api_doc.swagger.model_naming_strategy'),
|
||||||
|
));
|
||||||
|
|
||||||
if ($authentication !== null) {
|
if ($authentication !== null) {
|
||||||
$formatter->addMethodCall('setAuthenticationConfig', array($authentication));
|
$formatter->addMethodCall('setAuthenticationConfig', array($authentication));
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Formatter;
|
|||||||
|
|
||||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||||
use Nelmio\ApiDocBundle\DataTypes;
|
use Nelmio\ApiDocBundle\DataTypes;
|
||||||
|
use Nelmio\ApiDocBundle\Swagger\ModelRegistry;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Router;
|
use Symfony\Component\Routing\Router;
|
||||||
use Symfony\Component\Routing\RouterInterface;
|
use Symfony\Component\Routing\RouterInterface;
|
||||||
@ -54,6 +55,16 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
DataTypes::DATETIME => 'date-time',
|
DataTypes::DATETIME => 'date-time',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Nelmio\ApiDocBundle\Swagger\ModelRegistry
|
||||||
|
*/
|
||||||
|
protected $modelRegistry;
|
||||||
|
|
||||||
|
public function __construct($namingStategy)
|
||||||
|
{
|
||||||
|
$this->modelRegistry = new ModelRegistry($namingStategy);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -192,8 +203,6 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
|
|
||||||
$apiBag = array();
|
$apiBag = array();
|
||||||
|
|
||||||
$models = array();
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($collection as $item) {
|
foreach ($collection as $item) {
|
||||||
|
|
||||||
@ -252,10 +261,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
$data = $apiDoc->toArray();
|
$data = $apiDoc->toArray();
|
||||||
|
|
||||||
if (isset($data['parameters'])) {
|
if (isset($data['parameters'])) {
|
||||||
$parameters = array_merge($parameters, $this->deriveParameters($data['parameters'],
|
$parameters = array_merge($parameters, $this->deriveParameters($data['parameters'], $input['paramType']));
|
||||||
$models,
|
|
||||||
$input['paramType']
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$responseMap = $apiDoc->getParsedResponseMap();
|
$responseMap = $apiDoc->getParsedResponseMap();
|
||||||
@ -273,7 +279,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
$responseModel = array(
|
$responseModel = array(
|
||||||
'code' => $statusCode,
|
'code' => $statusCode,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'responseModel' => $this->registerModel($prop['type']['class'], $prop['model'], '', $models),
|
'responseModel' => $this->registerModel($prop['type']['class'], $prop['model'], ''),
|
||||||
);
|
);
|
||||||
$responseMessages[$statusCode] = $responseModel;
|
$responseMessages[$statusCode] = $responseModel;
|
||||||
}
|
}
|
||||||
@ -315,7 +321,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$apiDeclaration['models'] = $models;
|
$apiDeclaration['models'] = $this->modelRegistry->getModels();
|
||||||
|
|
||||||
return $apiDeclaration;
|
return $apiDeclaration;
|
||||||
}
|
}
|
||||||
@ -374,7 +380,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function deriveParameters(array $input, array &$models, $paramType = 'form')
|
protected function deriveParameters(array $input, $paramType = 'form')
|
||||||
{
|
{
|
||||||
|
|
||||||
$parameters = array();
|
$parameters = array();
|
||||||
@ -403,8 +409,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
$this->registerModel(
|
$this->registerModel(
|
||||||
$prop['subType'],
|
$prop['subType'],
|
||||||
isset($prop['children']) ? $prop['children'] : null,
|
isset($prop['children']) ? $prop['children'] : null,
|
||||||
$prop['description'] ?: $prop['dataType'],
|
$prop['description'] ?: $prop['dataType']
|
||||||
$models
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -414,8 +419,7 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
$ref = $this->registerModel(
|
$ref = $this->registerModel(
|
||||||
$prop['subType'],
|
$prop['subType'],
|
||||||
isset($prop['children']) ? $prop['children'] : null,
|
isset($prop['children']) ? $prop['children'] : null,
|
||||||
$prop['description'] ?: $prop['dataType'],
|
$prop['description'] ?: $prop['dataType']
|
||||||
$models
|
|
||||||
);
|
);
|
||||||
$items = array(
|
$items = array(
|
||||||
'$ref' => $ref,
|
'$ref' => $ref,
|
||||||
@ -477,150 +481,16 @@ class SwaggerFormatter implements FormatterInterface
|
|||||||
/**
|
/**
|
||||||
* Registers a model into the model array. Returns a unique identifier for the model to be used in `$ref` properties.
|
* Registers a model into the model array. Returns a unique identifier for the model to be used in `$ref` properties.
|
||||||
*
|
*
|
||||||
* @param $className
|
* @param $className
|
||||||
* @param array $parameters
|
* @param array $parameters
|
||||||
* @param string $description
|
* @param string $description
|
||||||
* @param $models
|
*
|
||||||
|
* @internal param $models
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function registerModel($className, array $parameters = null, $description = '', &$models)
|
public function registerModel($className, array $parameters = null, $description = '')
|
||||||
{
|
{
|
||||||
if (isset ($models[$className])) {
|
return $this->modelRegistry->register($className, $parameters, $description);
|
||||||
return $models[$className]['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Converts \Fully\Qualified\Class\Name to Fully.Qualified.Class.Name
|
|
||||||
*/
|
|
||||||
$id = preg_replace('#(\\\|[^A-Za-z0-9])#', '.', $className);
|
|
||||||
//Replace duplicate dots.
|
|
||||||
$id = preg_replace('/\.+/', '.', $id);
|
|
||||||
//Replace trailing dots.
|
|
||||||
$id = preg_replace('/^\./', '', $id);
|
|
||||||
|
|
||||||
$model = array(
|
|
||||||
'id' => $id,
|
|
||||||
'description' => $description,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (is_array($parameters)) {
|
|
||||||
|
|
||||||
$required = array();
|
|
||||||
$properties = array();
|
|
||||||
|
|
||||||
foreach ($parameters as $name => $prop) {
|
|
||||||
|
|
||||||
$subParam = array();
|
|
||||||
|
|
||||||
if ($prop['actualType'] === DataTypes::MODEL) {
|
|
||||||
|
|
||||||
$subParam['$ref'] = $this->registerModel(
|
|
||||||
$prop['subType'],
|
|
||||||
isset($prop['children']) ? $prop['children'] : null,
|
|
||||||
$prop['description'] ?: $prop['dataType'],
|
|
||||||
$models
|
|
||||||
);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
$type = null;
|
|
||||||
$format = null;
|
|
||||||
$items = null;
|
|
||||||
$enum = null;
|
|
||||||
$ref = null;
|
|
||||||
|
|
||||||
if (isset($this->typeMap[$prop['actualType']])) {
|
|
||||||
$type = $this->typeMap[$prop['actualType']];
|
|
||||||
} else{
|
|
||||||
|
|
||||||
switch ($prop['actualType']) {
|
|
||||||
case DataTypes::ENUM:
|
|
||||||
$type = 'string';
|
|
||||||
if (isset($prop['format'])) {
|
|
||||||
$enum = array_keys(json_decode($prop['format'], true));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DataTypes::COLLECTION:
|
|
||||||
$type = 'array';
|
|
||||||
|
|
||||||
if ($prop['subType'] === DataTypes::MODEL) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if ($prop['subType'] === null
|
|
||||||
|| isset($this->typeMap[$prop['subType']])) {
|
|
||||||
$items = array(
|
|
||||||
'type' => 'string',
|
|
||||||
);
|
|
||||||
} elseif (!isset($this->typeMap[$prop['subType']])) {
|
|
||||||
$items = array(
|
|
||||||
'$ref' =>
|
|
||||||
$this->registerModel(
|
|
||||||
$prop['subType'],
|
|
||||||
isset($prop['children']) ? $prop['children'] : null,
|
|
||||||
$prop['description'] ?: $prop['dataType'],
|
|
||||||
$models
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* @TODO: Handle recursion if subtype is a model. */
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DataTypes::MODEL:
|
|
||||||
$ref = $this->registerModel(
|
|
||||||
$prop['subType'],
|
|
||||||
isset($prop['children']) ? $prop['children'] : null,
|
|
||||||
$prop['description'] ?: $prop['dataType'],
|
|
||||||
$models
|
|
||||||
);
|
|
||||||
$type = $ref;
|
|
||||||
/* @TODO: Handle recursion. */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($this->formatMap[$prop['actualType']])) {
|
|
||||||
$format = $this->formatMap[$prop['actualType']];
|
|
||||||
}
|
|
||||||
|
|
||||||
$subParam = array(
|
|
||||||
'type' => $type,
|
|
||||||
'description' => empty($prop['description']) === false ? (string) $prop['description'] : $prop['dataType'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($format !== null) {
|
|
||||||
$subParam['format'] = $format;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($enum !== null) {
|
|
||||||
$subParam['enum'] = $enum;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($ref !== null) {
|
|
||||||
$subParam['$ref'] = $ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($items !== null) {
|
|
||||||
$subParam['items'] = $items;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($prop['required']) {
|
|
||||||
$required[] = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
$properties[$name] = $subParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
$model['properties'] = $properties;
|
|
||||||
$model['required'] = $required;
|
|
||||||
$models[$id] = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
244
Swagger/ModelRegistry.php
Normal file
244
Swagger/ModelRegistry.php
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the NelmioApiDocBundle.
|
||||||
|
*
|
||||||
|
* (c) Nelmio <hello@nelm.io>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
namespace Nelmio\ApiDocBundle\Swagger;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\DataTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ModelRegistry
|
||||||
|
*
|
||||||
|
* @author Bez Hermoso <bez@activelamp.com>
|
||||||
|
*/
|
||||||
|
class ModelRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $namingStrategies = array(
|
||||||
|
'dot_notation' => 'nameDotNotation',
|
||||||
|
'last_segment_only' => 'nameLastSegmentOnly',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $models = array();
|
||||||
|
|
||||||
|
protected $classes = array();
|
||||||
|
|
||||||
|
protected $classMap = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
protected $namingStategy;
|
||||||
|
|
||||||
|
protected $typeMap = array(
|
||||||
|
DataTypes::INTEGER => 'integer',
|
||||||
|
DataTypes::FLOAT => 'number',
|
||||||
|
DataTypes::STRING => 'string',
|
||||||
|
DataTypes::BOOLEAN => 'boolean',
|
||||||
|
DataTypes::FILE => 'string',
|
||||||
|
DataTypes::DATE => 'string',
|
||||||
|
DataTypes::DATETIME => 'string',
|
||||||
|
);
|
||||||
|
|
||||||
|
protected $formatMap = array(
|
||||||
|
DataTypes::INTEGER => 'int32',
|
||||||
|
DataTypes::FLOAT => 'float',
|
||||||
|
DataTypes::FILE => 'byte',
|
||||||
|
DataTypes::DATE => 'date',
|
||||||
|
DataTypes::DATETIME => 'date-time',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __construct($namingStrategy)
|
||||||
|
{
|
||||||
|
if (!isset($this->namingStrategies[$namingStrategy])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf(
|
||||||
|
'Invalid naming strategy. Choose from: %s',
|
||||||
|
json_encode(array_keys($this->namingStrategies))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->namingStategy = array($this, $this->namingStrategies[$namingStrategy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register($className, array $parameters = null, $description = '')
|
||||||
|
{
|
||||||
|
if (!isset($this->classes[$className])) {
|
||||||
|
$this->classes[$className] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = call_user_func_array($this->namingStategy, array($className));
|
||||||
|
|
||||||
|
if (isset($this->models[$id])) {
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->classes[$className][] = $id;
|
||||||
|
|
||||||
|
$model = array(
|
||||||
|
'id' => $id,
|
||||||
|
'description' => $description,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_array($parameters)) {
|
||||||
|
|
||||||
|
$required = array();
|
||||||
|
$properties = array();
|
||||||
|
|
||||||
|
foreach ($parameters as $name => $prop) {
|
||||||
|
|
||||||
|
$subParam = array();
|
||||||
|
|
||||||
|
if ($prop['actualType'] === DataTypes::MODEL) {
|
||||||
|
|
||||||
|
$subParam['$ref'] = $this->register(
|
||||||
|
$prop['subType'],
|
||||||
|
isset($prop['children']) ? $prop['children'] : null,
|
||||||
|
$prop['description'] ?: $prop['dataType']
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$type = null;
|
||||||
|
$format = null;
|
||||||
|
$items = null;
|
||||||
|
$enum = null;
|
||||||
|
$ref = null;
|
||||||
|
|
||||||
|
if (isset($this->typeMap[$prop['actualType']])) {
|
||||||
|
|
||||||
|
$type = $this->typeMap[$prop['actualType']];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
switch ($prop['actualType']) {
|
||||||
|
case DataTypes::ENUM:
|
||||||
|
$type = 'string';
|
||||||
|
if (isset($prop['format'])) {
|
||||||
|
$enum = array_keys(json_decode($prop['format'], true));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DataTypes::COLLECTION:
|
||||||
|
$type = 'array';
|
||||||
|
|
||||||
|
if ($prop['subType'] === DataTypes::MODEL) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($prop['subType'] === null
|
||||||
|
|| isset($this->typeMap[$prop['subType']])) {
|
||||||
|
$items = array(
|
||||||
|
'type' => 'string',
|
||||||
|
);
|
||||||
|
} elseif (!isset($this->typeMap[$prop['subType']])) {
|
||||||
|
$items = array(
|
||||||
|
'$ref' =>
|
||||||
|
$this->register(
|
||||||
|
$prop['subType'],
|
||||||
|
isset($prop['children']) ? $prop['children'] : null,
|
||||||
|
$prop['description'] ?: $prop['dataType']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* @TODO: Handle recursion if subtype is a model. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DataTypes::MODEL:
|
||||||
|
$ref = $this->register(
|
||||||
|
$prop['subType'],
|
||||||
|
isset($prop['children']) ? $prop['children'] : null,
|
||||||
|
$prop['description'] ?: $prop['dataType']
|
||||||
|
);
|
||||||
|
|
||||||
|
$type = $ref;
|
||||||
|
/* @TODO: Handle recursion. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->formatMap[$prop['actualType']])) {
|
||||||
|
$format = $this->formatMap[$prop['actualType']];
|
||||||
|
}
|
||||||
|
|
||||||
|
$subParam = array(
|
||||||
|
'type' => $type,
|
||||||
|
'description' => empty($prop['description']) === false ? (string) $prop['description'] : $prop['dataType'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($format !== null) {
|
||||||
|
$subParam['format'] = $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($enum !== null) {
|
||||||
|
$subParam['enum'] = $enum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ref !== null) {
|
||||||
|
$subParam['$ref'] = $ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($items !== null) {
|
||||||
|
$subParam['items'] = $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($prop['required']) {
|
||||||
|
$required[] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$properties[$name] = $subParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model['properties'] = $properties;
|
||||||
|
$model['required'] = $required;
|
||||||
|
$this->models[$id] = $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nameDotNotation($className)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Converts \Fully\Qualified\Class\Name to Fully.Qualified.Class.Name
|
||||||
|
*/
|
||||||
|
$id = preg_replace('#(\\\|[^A-Za-z0-9])#', '.', $className);
|
||||||
|
//Replace duplicate dots.
|
||||||
|
$id = preg_replace('/\.+/', '.', $id);
|
||||||
|
//Replace trailing dots.
|
||||||
|
$id = preg_replace('/^\./', '', $id);
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nameLastSegmentOnly($className)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Converts \Fully\Qualified\ClassName to ClassName
|
||||||
|
*/
|
||||||
|
$segments = explode('\\', $className);
|
||||||
|
$id = end($segments);
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModels()
|
||||||
|
{
|
||||||
|
return $this->models;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user