Skip to content

Commit

Permalink
MVC: add "migration_prefix" attribute to model, so plugins can choose…
Browse files Browse the repository at this point in the history
… different prefixes than "M" when sharing the same namespace. While here, also perform a style sweep.

Needed for opnsense/plugins#1749 (using the same namespace as the aliases in core)
  • Loading branch information
AdSchellevis committed Mar 25, 2020
1 parent db24905 commit c81b5b8
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 34 deletions.
66 changes: 43 additions & 23 deletions src/opnsense/mvc/app/models/OPNsense/Base/BaseModel.php
@@ -1,7 +1,7 @@
<?php

/*
* Copyright (C) 2015 Deciso B.V.
* Copyright (C) 2015-2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand All @@ -28,9 +28,15 @@

namespace OPNsense\Base;

use Exception;
use OPNsense\Base\FieldTypes\ContainerField;
use OPNsense\Core\Config;
use Phalcon\Logger\Adapter\Syslog;
use Phalcon\Validation;
use Phalcon\Validation\Message\Group;
use ReflectionClass;
use ReflectionException;
use SimpleXMLElement;

/**
* Class BaseModel implements base model to bind config and definition to object.
Expand Down Expand Up @@ -61,6 +67,13 @@ abstract class BaseModel
*/
private $internal_model_version = "0.0.0";

/**
* prefix for migration files, default is M (e.g. M1_0_0.php equals version 1.0.0)
* when models share a namespace, they should be allowed to use their own unique prefix
* @var string
*/
private $internal_model_migration_prefix = "M";

/**
* model version in config.xml
* @var null
Expand Down Expand Up @@ -111,7 +124,7 @@ private function parseOptionData($xmlNode)
* @param string $classname classname to construct
* @return BaseField type class
* @throws ModelException when unable to parse field type
* @throws \ReflectionException when unable to create class
* @throws ReflectionException when unable to create class
*/
private function getNewField($classname)
{
Expand All @@ -122,7 +135,7 @@ private function getNewField($classname)
if (!isset(self::$internalCacheReflectionClasses[$classname_idx])) {
$is_derived_from_basefield = false;
if (class_exists($classname)) {
$field_rfcls = new \ReflectionClass($classname);
$field_rfcls = new ReflectionClass($classname);
$check_derived = $field_rfcls->getParentClass();
while ($check_derived != false) {
if ($check_derived->name == 'OPNsense\Base\FieldTypes\BaseField') {
Expand All @@ -147,7 +160,7 @@ private function getNewField($classname)
* @param SimpleXMLElement $config_data (current) config data
* @param BaseField $internal_data output structure using FieldTypes,rootnode is internalData
* @throws ModelException parse error
* @throws \ReflectionException
* @throws ReflectionException
*/
private function parseXml(&$xml, &$config_data, &$internal_data)
{
Expand Down Expand Up @@ -268,7 +281,7 @@ private function parseXml(&$xml, &$config_data, &$internal_data)
/**
* Construct new model type, using its own xml template
* @throws ModelException if the model xml is not found or invalid
* @throws \ReflectionException
* @throws ReflectionException
*/
public function __construct()
{
Expand All @@ -280,7 +293,7 @@ public function __construct()

// determine our caller's filename and try to find the model definition xml
// throw error on failure
$class_info = new \ReflectionClass($this);
$class_info = new ReflectionClass($this);
$model_filename = substr($class_info->getFileName(), 0, strlen($class_info->getFileName()) - 3) . "xml";
if (!file_exists($model_filename)) {
throw new ModelException('model xml ' . $model_filename . ' missing');
Expand All @@ -298,6 +311,11 @@ public function __construct()
$this->internal_model_version = (string)$model_xml->version;
}

if (!empty($model_xml->migration_prefix)) {
$this->internal_model_migration_prefix = (string)$model_xml->migration_prefix;
}


// use an xpath expression to find the root of our model in the config.xml file
// if found, convert the data to a simple structure (or create an empty array)
$tmp_config_data = $internalConfigHandle->xpath($model_xml->mount);
Expand Down Expand Up @@ -364,9 +382,9 @@ public function getNodes()

/**
* structured setter for model
* @param array|$data named array
* @return array
* @throws \Exception
* @param array $data named array
* @return void
* @throws Exception
*/
public function setNodes($data)
{
Expand All @@ -385,12 +403,12 @@ public function iterateItems()
/**
* validate full model using all fields and data in a single (1 deep) array
* @param bool $validateFullModel validate full model or only changed fields
* @return \Phalcon\Validation\Message\Group
* @return Group
*/
public function performValidation($validateFullModel = false)
{
// create a Phalcon validator and collect all model validations
$validation = new \Phalcon\Validation();
$validation = new Validation();
$validation_data = array();
$all_nodes = $this->internalData->getFlatNodes();

Expand All @@ -409,7 +427,7 @@ public function performValidation($validateFullModel = false)
if (count($validation_data) > 0) {
$messages = $validation->validate($validation_data);
} else {
$messages = new \Phalcon\Validation\Message\Group();
$messages = new Group();
}

return $messages;
Expand Down Expand Up @@ -443,7 +461,7 @@ public function validate($sourceref = null, $targetref = "")
* render xml document from model including all parent nodes.
* (parent nodes are included to ease testing)
*
* @return \SimpleXMLElement xml representation of the model
* @return SimpleXMLElement xml representation of the model
*/
public function toXML()
{
Expand All @@ -461,7 +479,7 @@ public function toXML()
}
}

$xml = new \SimpleXMLElement($xml_root_node);
$xml = new SimpleXMLElement($xml_root_node);
$this->internalData->addToXMLNode($xml->xpath($this->internal_mountpoint)[0]);
// add this model's version to the newly created xml structure
if (!empty($this->internal_current_model_version)) {
Expand Down Expand Up @@ -518,7 +536,7 @@ private function internalSerializeToConfig()
*
* @param bool $validateFullModel by default we only validate the fields we have changed
* @param bool $disable_validation skip validation, be careful to use this!
* @throws \Phalcon\Validation\Exception validation errors
* @throws Validation\Exception validation errors
*/
public function serializeToConfig($validateFullModel = false, $disable_validation = false)
{
Expand All @@ -542,7 +560,7 @@ public function serializeToConfig($validateFullModel = false, $disable_validatio
$logger->error($exception_msg_part);
}
if (!$disable_validation) {
throw new \Phalcon\Validation\Exception($exception_msg);
throw new Validation\Exception($exception_msg);
}
}
$this->internalSerializeToConfig();
Expand Down Expand Up @@ -595,21 +613,22 @@ public function setNodeByReference($reference, $value)
* prefixed with an M and . replaced by _ for example : M1_0_1 equals version 1.0.1
*
* @return bool status (true-->success, false-->failed)
* @throws \ReflectionException
* @throws ReflectionException
*/
public function runMigrations()
{
if (version_compare($this->internal_current_model_version, $this->internal_model_version, '<')) {
$upgradePerfomed = false;
$migObjects = array();
$logger = new Syslog("config", array('option' => LOG_PID, 'facility' => LOG_LOCAL4));
$class_info = new \ReflectionClass($this);
$class_info = new ReflectionClass($this);
// fetch version migrations
$versions = array();
// set default migration for current model version
$versions[$this->internal_model_version] = __DIR__ . "/BaseModelMigration.php";
foreach (glob(dirname($class_info->getFileName()) . "/Migrations/M*.php") as $filename) {
$version = str_replace('_', '.', explode('.', substr(basename($filename), 1))[0]);
$migprefix = $this->internal_model_migration_prefix;
foreach (glob(dirname($class_info->getFileName()) . "/Migrations/{$migprefix}*.php") as $filename) {
$version = str_replace('_', '.', explode('.', substr(basename($filename), strlen($migprefix)))[0]);
$versions[$version] = $filename;
}

Expand All @@ -629,15 +648,15 @@ public function runMigrations()
$mig_classname = str_replace('/', '\\', $mig_classname);
// Phalcon's autoloader uses _ as a directory locator, we need to import these files ourselves
require_once $filename;
$mig_class = new \ReflectionClass($mig_classname);
$mig_class = new ReflectionClass($mig_classname);
$chk_class = empty($mig_class->getParentClass()) ? $mig_class : $mig_class->getParentClass();
if ($chk_class->name == 'OPNsense\Base\BaseModelMigration') {
$migobj = $mig_class->newInstance();
try {
$migobj->run($this);
$migObjects[] = $migobj;
$upgradePerfomed = true;
} catch (\Exception $e) {
} catch (Exception $e) {
$logger->error("failed migrating from version " .
$this->internal_current_model_version .
" to " . $mig_version . " in " .
Expand All @@ -656,14 +675,15 @@ public function runMigrations()
foreach ($migObjects as $migobj) {
$migobj->post($this);
}
} catch (\Exception $e) {
} catch (Exception $e) {
$logger->error("Model " . $class_info->getName() . " can't be saved, skip ( " . $e . " )");
return false;
}
}

return true;
}
return false;
}

/**
Expand Down
28 changes: 17 additions & 11 deletions src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseField.php
@@ -1,7 +1,7 @@
<?php

/*
* Copyright (C) 2015 Deciso B.V.
* Copyright (C) 2015-2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand All @@ -28,7 +28,13 @@

namespace OPNsense\Base\FieldTypes;

use Exception;
use Generator;
use InvalidArgumentException;
use Phalcon\Validation\Validator\PresenceOf;
use ReflectionClass;
use ReflectionException;
use SimpleXMLElement;

/**
* Class BaseField
Expand Down Expand Up @@ -222,14 +228,14 @@ public function __clone()
/**
* change internal reference, if set it can't be changed for safety purposes.
* @param $ref internal reference
* @throws \Exception change exception
* @throws Exception change exception
*/
public function setInternalReference($ref)
{
if ($this->internalReference == null) {
$this->internalReference = $ref;
} else {
throw new \Exception("cannot change internal reference");
throw new Exception("cannot change internal reference");
}
}

Expand Down Expand Up @@ -294,7 +300,7 @@ public function __get($name)

/**
* iterate (non virtual) child nodes
* @return mixed
* @return Generator
*/
public function iterateItems()
{
Expand All @@ -315,7 +321,7 @@ public function __set($name, $value)
if (isset($this->internalChildnodes[$name])) {
$this->internalChildnodes[$name]->setValue($value);
} else {
throw new \InvalidArgumentException($name . " not an attribute of " . $this->internalReference);
throw new InvalidArgumentException($name . " not an attribute of " . $this->internalReference);
}
}

Expand Down Expand Up @@ -433,13 +439,13 @@ public function getConstraintByName($name)
$constraint = $this->internalConstraints[$name];
if (!empty($constraint['type'])) {
try {
$constr_class = new \ReflectionClass('OPNsense\\Base\\Constraints\\' . $constraint['type']);
$constr_class = new ReflectionClass('OPNsense\\Base\\Constraints\\' . $constraint['type']);
if ($constr_class->getParentClass()->name == 'OPNsense\Base\Constraints\BaseConstraint') {
$constraint['name'] = $name;
$constraint['node'] = $this;
return $constr_class->newInstance($constraint);
}
} catch (\ReflectionException $e) {
} catch (ReflectionException $e) {
null; // ignore configuration errors, if the constraint can't be found, skip.
}
}
Expand Down Expand Up @@ -569,8 +575,8 @@ public function getNodeData()

/**
* update model with data returning missing repeating tag types.
* @param $data named array structure containing new model content
* @throws \Exception
* @param $data array structure containing new model content
* @throws Exception
*/
public function setNodes($data)
{
Expand All @@ -581,7 +587,7 @@ public function setNodes($data)
if (is_array($data[$key])) {
$node->setNodes($data[$key]);
} else {
throw new \Exception("Invalid input type for {$key} (configuration error?)");
throw new Exception("Invalid input type for {$key} (configuration error?)");
}
} else {
$node->setValue($data[$key]);
Expand All @@ -603,7 +609,7 @@ public function setNodes($data)

/**
* Add this node and its children to the supplied simplexml node pointer.
* @param \SimpleXMLElement $node target node
* @param SimpleXMLElement $node target node
*/
public function addToXMLNode($node)
{
Expand Down

0 comments on commit c81b5b8

Please sign in to comment.