[Workflow] streamline XML schema definition
This commit is contained in:
parent
6381caa0e2
commit
94a7e7ea88
@ -8,7 +8,7 @@
|
|||||||
<xsd:element name="config" type="config" />
|
<xsd:element name="config" type="config" />
|
||||||
|
|
||||||
<xsd:complexType name="config">
|
<xsd:complexType name="config">
|
||||||
<xsd:all>
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="assets" type="assets" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="assets" type="assets" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
|
||||||
@ -26,8 +26,8 @@
|
|||||||
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="workflows" type="workflows" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="workflow" type="workflow" minOccurs="0" maxOccurs="unbounded" />
|
||||||
</xsd:all>
|
</xsd:choice>
|
||||||
|
|
||||||
<xsd:attribute name="http-method-override" type="xsd:boolean" />
|
<xsd:attribute name="http-method-override" type="xsd:boolean" />
|
||||||
<xsd:attribute name="trusted-proxies" type="xsd:string" />
|
<xsd:attribute name="trusted-proxies" type="xsd:string" />
|
||||||
@ -228,42 +228,45 @@
|
|||||||
<xsd:attribute name="clearer" type="xsd:string" />
|
<xsd:attribute name="clearer" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="workflows">
|
|
||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
||||||
<xsd:element name="workflow" type="workflow" />
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
|
|
||||||
<xsd:complexType name="workflow">
|
<xsd:complexType name="workflow">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="marking-store" type="marking_store" />
|
<xsd:element name="marking-store" type="marking_store" />
|
||||||
<xsd:element name="supports" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="support" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
<xsd:element name="places" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="place" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
<xsd:element name="transitions" type="transitions" />
|
<xsd:element name="transition" type="transition" minOccurs="1" maxOccurs="unbounded" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
<xsd:attribute name="type" type="workflow_type" />
|
||||||
<xsd:attribute name="initial-place" type="xsd:string" />
|
<xsd:attribute name="initial-place" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="marking_store">
|
<xsd:complexType name="marking_store">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="type" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="argument" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||||
<xsd:element name="arguments" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
|
||||||
<xsd:element name="service" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="type" type="marking_store_type" />
|
||||||
|
<xsd:attribute name="service" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="transitions">
|
<xsd:simpleType name="marking_store_type">
|
||||||
<xsd:sequence>
|
<xsd:restriction base="xsd:string">
|
||||||
<xsd:element name="transition" type="transition" />
|
<xsd:enumeration value="multiple_state" />
|
||||||
</xsd:sequence>
|
<xsd:enumeration value="single_state" />
|
||||||
</xsd:complexType>
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
|
|
||||||
<xsd:complexType name="transition">
|
<xsd:complexType name="transition">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:simpleType name="workflow_type">
|
||||||
|
<xsd:restriction base="xsd:string">
|
||||||
|
<xsd:enumeration value="state_machine" />
|
||||||
|
<xsd:enumeration value="workflow" />
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest;
|
|
||||||
|
|
||||||
$container->loadFromExtension('framework', array(
|
|
||||||
'workflows' => array(
|
|
||||||
'my_workflow' => array(
|
|
||||||
'marking_store' => array(
|
|
||||||
'type' => 'multiple_state',
|
|
||||||
),
|
|
||||||
'supports' => array(
|
|
||||||
FrameworkExtensionTest::class,
|
|
||||||
),
|
|
||||||
'places' => array(
|
|
||||||
'first',
|
|
||||||
'last',
|
|
||||||
),
|
|
||||||
'transitions' => array(
|
|
||||||
'go' => array(
|
|
||||||
'from' => array(
|
|
||||||
'first',
|
|
||||||
),
|
|
||||||
'to' => array(
|
|
||||||
'last',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest;
|
||||||
|
|
||||||
|
$container->loadFromExtension('framework', array(
|
||||||
|
'workflows' => array(
|
||||||
|
'article' => array(
|
||||||
|
'type' => 'workflow',
|
||||||
|
'marking_store' => array(
|
||||||
|
'type' => 'multiple_state',
|
||||||
|
),
|
||||||
|
'supports' => array(
|
||||||
|
FrameworkExtensionTest::class,
|
||||||
|
),
|
||||||
|
'initial_place' => 'draft',
|
||||||
|
'places' => array(
|
||||||
|
'draft',
|
||||||
|
'wait_for_journalist',
|
||||||
|
'approved_by_journalist',
|
||||||
|
'wait_for_spellchecker',
|
||||||
|
'approved_by_spellchecker',
|
||||||
|
'published',
|
||||||
|
),
|
||||||
|
'transitions' => array(
|
||||||
|
'request_review' => array(
|
||||||
|
'from' => 'draft',
|
||||||
|
'to' => array('wait_for_journalist', 'wait_for_spellchecker'),
|
||||||
|
),
|
||||||
|
'journalist_approval' => array(
|
||||||
|
'from' => 'wait_for_journalist',
|
||||||
|
'to' => 'approved_by_journalist',
|
||||||
|
),
|
||||||
|
'spellchecker_approval' => array(
|
||||||
|
'from' => 'wait_for_spellchecker',
|
||||||
|
'to' => 'approved_by_spellchecker',
|
||||||
|
),
|
||||||
|
'publish' => array(
|
||||||
|
'from' => array('approved_by_journalist', 'approved_by_spellchecker'),
|
||||||
|
'to' => 'published',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'pull_request' => array(
|
||||||
|
'type' => 'state_machine',
|
||||||
|
'marking_store' => array(
|
||||||
|
'type' => 'single_state',
|
||||||
|
),
|
||||||
|
'supports' => array(
|
||||||
|
FrameworkExtensionTest::class,
|
||||||
|
),
|
||||||
|
'initial_place' => 'start',
|
||||||
|
'places' => array(
|
||||||
|
'start',
|
||||||
|
'coding',
|
||||||
|
'travis',
|
||||||
|
'review',
|
||||||
|
'merged',
|
||||||
|
'closed',
|
||||||
|
),
|
||||||
|
'transitions' => array(
|
||||||
|
'submit' => array(
|
||||||
|
'from' => 'start',
|
||||||
|
'to' => 'travis',
|
||||||
|
),
|
||||||
|
'update' => array(
|
||||||
|
'from' => array('coding', 'travis', 'review'),
|
||||||
|
'to' => 'travis',
|
||||||
|
),
|
||||||
|
'wait_for_review' => array(
|
||||||
|
'from' => 'travis',
|
||||||
|
'to' => 'review',
|
||||||
|
),
|
||||||
|
'request_change' => array(
|
||||||
|
'from' => 'review',
|
||||||
|
'to' => 'coding',
|
||||||
|
),
|
||||||
|
'accept' => array(
|
||||||
|
'from' => 'review',
|
||||||
|
'to' => 'merged',
|
||||||
|
),
|
||||||
|
'reject' => array(
|
||||||
|
'from' => 'review',
|
||||||
|
'to' => 'closed',
|
||||||
|
),
|
||||||
|
'reopen' => array(
|
||||||
|
'from' => 'closed',
|
||||||
|
'to' => 'review',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" ?>
|
|
||||||
|
|
||||||
<container xmlns="http://symfony.com/schema/dic/services"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
|
||||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
|
|
||||||
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
|
||||||
|
|
||||||
<framework:config>
|
|
||||||
<framework:workflows>
|
|
||||||
<framework:workflow name="my_workflow">
|
|
||||||
<framework:marking-store>
|
|
||||||
<framework:type>multiple_state</framework:type>
|
|
||||||
<framework:arguments>a</framework:arguments>
|
|
||||||
<framework:arguments>a</framework:arguments>
|
|
||||||
</framework:marking-store>
|
|
||||||
<framework:supports>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:supports>
|
|
||||||
<framework:places>first</framework:places>
|
|
||||||
<framework:places>last</framework:places>
|
|
||||||
<framework:transitions>
|
|
||||||
<framework:transition name="foobar">
|
|
||||||
<framework:from>a</framework:from>
|
|
||||||
<framework:to>a</framework:to>
|
|
||||||
</framework:transition>
|
|
||||||
</framework:transitions>
|
|
||||||
</framework:workflow>
|
|
||||||
</framework:workflows>
|
|
||||||
</framework:config>
|
|
||||||
</container>
|
|
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
|
||||||
|
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
||||||
|
|
||||||
|
<framework:config>
|
||||||
|
<framework:workflow name="article" type="workflow" initial-place="draft">
|
||||||
|
<framework:marking-store type="multiple_state">
|
||||||
|
<framework:argument>a</framework:argument>
|
||||||
|
<framework:argument>a</framework:argument>
|
||||||
|
</framework:marking-store>
|
||||||
|
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:support>
|
||||||
|
<framework:place>draft</framework:place>
|
||||||
|
<framework:place>wait_for_journalist</framework:place>
|
||||||
|
<framework:place>approved_by_journalist</framework:place>
|
||||||
|
<framework:place>wait_for_spellchecker</framework:place>
|
||||||
|
<framework:place>approved_by_spellchecker</framework:place>
|
||||||
|
<framework:place>published</framework:place>
|
||||||
|
<framework:transition name="request_review">
|
||||||
|
<framework:from>draft</framework:from>
|
||||||
|
<framework:to>wait_for_journalist</framework:to>
|
||||||
|
<framework:to>wait_for_spellchecker</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="journalist_approval">
|
||||||
|
<framework:from>wait_for_journalist</framework:from>
|
||||||
|
<framework:to>approved_by_journalist</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="spellchecker_approval">
|
||||||
|
<framework:from>wait_for_spellcheker</framework:from>
|
||||||
|
<framework:to>approved_by_spellchker</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="publish">
|
||||||
|
<framework:from>approved_by_journalist</framework:from>
|
||||||
|
<framework:from>approved_by_spellchker</framework:from>
|
||||||
|
<framework:to>published</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
</framework:workflow>
|
||||||
|
|
||||||
|
<framework:workflow name="pull_request" type="state_machine" initial-place="start">
|
||||||
|
<framework:marking-store type="single_state"/>
|
||||||
|
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:support>
|
||||||
|
<framework:place>start</framework:place>
|
||||||
|
<framework:place>coding</framework:place>
|
||||||
|
<framework:place>travis</framework:place>
|
||||||
|
<framework:place>review</framework:place>
|
||||||
|
<framework:place>merged</framework:place>
|
||||||
|
<framework:place>closed</framework:place>
|
||||||
|
<framework:transition name="submit">
|
||||||
|
<framework:from>start</framework:from>
|
||||||
|
<framework:to>travis</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="update">
|
||||||
|
<framework:from>coding</framework:from>
|
||||||
|
<framework:from>travis</framework:from>
|
||||||
|
<framework:from>review</framework:from>
|
||||||
|
<framework:to>travis</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="wait_for_review">
|
||||||
|
<framework:from>travis</framework:from>
|
||||||
|
<framework:to>review</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="request_change">
|
||||||
|
<framework:from>review</framework:from>
|
||||||
|
<framework:to>coding</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="accept">
|
||||||
|
<framework:from>review</framework:from>
|
||||||
|
<framework:to>merged</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="reject">
|
||||||
|
<framework:from>review</framework:from>
|
||||||
|
<framework:to>closed</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
<framework:transition name="reopen">
|
||||||
|
<framework:from>closed</framework:from>
|
||||||
|
<framework:to>review</framework:to>
|
||||||
|
</framework:transition>
|
||||||
|
</framework:workflow>
|
||||||
|
</framework:config>
|
||||||
|
</container>
|
@ -1,16 +0,0 @@
|
|||||||
framework:
|
|
||||||
workflows:
|
|
||||||
my_workflow:
|
|
||||||
marking_store:
|
|
||||||
type: multiple_state
|
|
||||||
supports:
|
|
||||||
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest
|
|
||||||
places:
|
|
||||||
- first
|
|
||||||
- last
|
|
||||||
transitions:
|
|
||||||
go:
|
|
||||||
from:
|
|
||||||
- first
|
|
||||||
to:
|
|
||||||
- last
|
|
@ -0,0 +1,65 @@
|
|||||||
|
framework:
|
||||||
|
workflows:
|
||||||
|
article:
|
||||||
|
type: workflow
|
||||||
|
marking_store:
|
||||||
|
type: multiple_state
|
||||||
|
supports:
|
||||||
|
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest
|
||||||
|
initial_place: draft
|
||||||
|
places:
|
||||||
|
- draft
|
||||||
|
- wait_for_journalist
|
||||||
|
- approved_by_journalist
|
||||||
|
- wait_for_spellchecker
|
||||||
|
- approved_by_spellchecker
|
||||||
|
- published
|
||||||
|
transitions:
|
||||||
|
request_review:
|
||||||
|
from: [draft]
|
||||||
|
to: [wait_for_journalist, wait_for_spellchecker]
|
||||||
|
journalist_approval:
|
||||||
|
from: [wait_for_journalist]
|
||||||
|
to: [approved_by_journalist]
|
||||||
|
spellchecker_approval:
|
||||||
|
from: [wait_for_spellchecker]
|
||||||
|
to: [approved_by_spellchecker]
|
||||||
|
publish:
|
||||||
|
from: [approved_by_journalist, approved_by_spellchecker]
|
||||||
|
to: [published]
|
||||||
|
pull_request:
|
||||||
|
type: state_machine
|
||||||
|
marking_store:
|
||||||
|
type: single_state
|
||||||
|
supports:
|
||||||
|
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest
|
||||||
|
initial_place: start
|
||||||
|
places:
|
||||||
|
- start
|
||||||
|
- coding
|
||||||
|
- travis
|
||||||
|
- review
|
||||||
|
- merged
|
||||||
|
- closed
|
||||||
|
transitions:
|
||||||
|
submit:
|
||||||
|
from: start
|
||||||
|
to: travis
|
||||||
|
update:
|
||||||
|
from: [coding, travis, review]
|
||||||
|
to: travis
|
||||||
|
wait_for_review:
|
||||||
|
from: travis
|
||||||
|
to: review
|
||||||
|
request_change:
|
||||||
|
from: review
|
||||||
|
to: coding
|
||||||
|
accept:
|
||||||
|
from: review
|
||||||
|
to: merged
|
||||||
|
reject:
|
||||||
|
from: review
|
||||||
|
to: closed
|
||||||
|
reopen:
|
||||||
|
from: closed
|
||||||
|
to: review
|
@ -120,11 +120,51 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$this->assertFalse($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() does not load collectors.xml');
|
$this->assertFalse($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() does not load collectors.xml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWorkflow()
|
public function testWorkflows()
|
||||||
{
|
{
|
||||||
$container = $this->createContainerFromFile('workflow');
|
$container = $this->createContainerFromFile('workflows');
|
||||||
|
|
||||||
$this->assertTrue($container->hasDefinition('workflow.my_workflow'));
|
$this->assertTrue($container->hasDefinition('workflow.article', 'Workflow is registered as a service'));
|
||||||
|
$this->assertTrue($container->hasDefinition('workflow.article.definition', 'Workflow definition is registered as a service'));
|
||||||
|
|
||||||
|
$workflowDefinition = $container->getDefinition('workflow.article.definition');
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
array(
|
||||||
|
'draft',
|
||||||
|
'wait_for_journalist',
|
||||||
|
'approved_by_journalist',
|
||||||
|
'wait_for_spellchecker',
|
||||||
|
'approved_by_spellchecker',
|
||||||
|
'published',
|
||||||
|
),
|
||||||
|
$workflowDefinition->getArgument(0),
|
||||||
|
'Places are passed to the workflow definition'
|
||||||
|
);
|
||||||
|
$this->assertSame(array('workflow.definition' => array(array('name' => 'article', 'type' => 'workflow', 'marking_store' => 'multiple_state'))), $workflowDefinition->getTags());
|
||||||
|
|
||||||
|
$this->assertTrue($container->hasDefinition('state_machine.pull_request', 'State machine is registered as a service'));
|
||||||
|
$this->assertTrue($container->hasDefinition('state_machine.pull_request.definition', 'State machine definition is registered as a service'));
|
||||||
|
$this->assertCount(4, $workflowDefinition->getArgument(1));
|
||||||
|
$this->assertSame('draft', $workflowDefinition->getArgument(2));
|
||||||
|
|
||||||
|
$stateMachineDefinition = $container->getDefinition('state_machine.pull_request.definition');
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
array(
|
||||||
|
'start',
|
||||||
|
'coding',
|
||||||
|
'travis',
|
||||||
|
'review',
|
||||||
|
'merged',
|
||||||
|
'closed',
|
||||||
|
),
|
||||||
|
$stateMachineDefinition->getArgument(0),
|
||||||
|
'Places are passed to the state machine definition'
|
||||||
|
);
|
||||||
|
$this->assertSame(array('workflow.definition' => array(array('name' => 'pull_request', 'type' => 'state_machine', 'marking_store' => 'single_state'))), $stateMachineDefinition->getTags());
|
||||||
|
$this->assertCount(9, $stateMachineDefinition->getArgument(1));
|
||||||
|
$this->assertSame('start', $stateMachineDefinition->getArgument(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRouter()
|
public function testRouter()
|
||||||
|
Reference in New Issue
Block a user