Avançar para a área de conteúdo

ir para o conteúdo principal

developerWorks Brasil  >  Software livre  >

Usando o Flex SDK com Mate e PHP

Criando serviços PHP para ativar aplicativos ricos com o Eclipse PDT

developerWorks
Opções de documento

Opções de documento que necessitam de JavaScript não são exibidas


Classificar esta página

Ajude-nos a melhorar este conteúdo


Nível: Intermediário

Nathan A. Good, Senior Information Engineer, Freelance Developer

14/Jul/2009

Mate é uma estrutura leve orientada por eventos que possibilita a construção de interfaces com o usuário (UIs) e serviços em um padrão Model-View-Controller (MVC). Aprenda como usar as ferramentas de desenvolvimento PHP (PDT) do Eclipse e o kit de desenvolvimento (SDK) Flex juntos para estender um aplicativo usando a estrutura Mate. Este artigo expande a documentação existente do Mate, já que foca o uso do Eclipse PDT como a ferramenta.

O Adobe® Flash® é uma alternativa atraente para construir aplicativos ricos da Internet (RIAs). Usando o Eclipse e o Adobe Flex SDK, é possível compilar seus projetos em aplicativos que são executados no Flash Player. O Mate é uma estrutura leve orientada por eventos que traz o poder de chamar facilmente objetos remotos usando invocadores. Esses objetos remotos podem ser serviços gravados em PHP.

Para executar o exemplo neste artigo, é necessário ter o Eclipse PDT. O restante das coisas necessárias é coberto neste artigo.

Visão Geral de Mate e Flex SDK

O Flex SDK permite construir aplicativos Adobe Flex. Os aplicativos Flex usam arquivos de origem no formato XML e ActionScript V3.0. Usando o compilador, é possível compilar esses arquivos .mxml e os arquivos ActionScript para aplicativos Adobe Flex.

Há duas versões do Flex SDK que pode ser transferido por download:

  • A versão Adobe Free que inclui os produtos Adobe que permitem compilar aplicativos e também inclui o Adobe AIR e outros componentes. O Adobe Free SDK é licenciado com a licença do Adobe Flex SDK (consulte Recursos).
  • O SDK também é fornecido em uma versão Open Source Flex SDK licenciada sob a Mozilla Public License (MPL). Inclui um compilador e tudo que for necessário para executar o exemplo deste artigo. No entanto, a licença MPL pode não atender suas necessidades. Verifique com seu departamento jurídico se a licença serve para você.

Além disso, a Adobe oferece um produto chamado Flex Builder que pode ser transferido por download como um plug-in do Eclipse ou como um produto integral. Para os propósitos deste artigo, faça download do Flex SDK de software livre. Faça download do arquivo ZIP e descompacte-o em um local fácil de se lembrar; neste artigo, refiro-me a esse local como FLEX_HOME. Este artigo demonstra como configurar o Eclipse com o SDK gratuito para que você possa construir RIAs com apenas o Eclipse, PDT e o Flex SDK.

O Mate é uma estrutura que ajuda a construir aplicativos que seguem o padrão MVC. O Mate é orientado por eventos, portanto, são criados eventos que são classes ActionScript. Pode-se, então, mapear os eventos para a chamada de objeto remoto, deixando os detalhes do código fora de suas visualizações e modelo.



Voltar para parte superior


Criando as Estruturas do Projeto

Neste artigo, um aplicativo simples de entrada de hora — chamado ChronoLog — demonstra como construir uma UI Flex com serviços PHP. O exemplo requer dois projetos na área de trabalho do Eclipse: um é um projeto PHP que é usado para os serviços e o outro é um projeto normal que inclui um arquivo Ant que é configurado como um construtor Ant. O construtor Ant é usado para executar o compilador Flex para compilar o projeto que é usado para a UI.

Para criar o projeto PHP de serviços, selecione Arquivo > Novo > Projeto no menu Eclipse. Selecione Projeto PHP, selecione então Criar Projeto a partir de Origem Existente e escolha um local sob a raiz do documento de seu servidor de arquivos. Isso permite testar rapidamente os serviços PHP criados.

Para criar o projeto da UI, crie um projeto vazio usando Arquivo > Novo > Projeto e selecione Projeto.

Após criar o projeto para a UI, crie a pasta chamada src e outra dentro dela chamada chronolog. Sob essa pasta, crie pastas chamadas events, model, views e maps. A estrutura será semelhante àquela abaixo.


Figura 1. A Estrutura do Projeto de UI
A Estrutura do Projeto de UI



Voltar para parte superior


Fazendo Download e Instalando o Mate

O Mate está disponível para download como um único arquivo compilado com uma extensão .swc. Faça download do arquivo a partir do site e instale-o importando-o para a pasta libs no projeto de UI. O resultado deve ser semelhante à Figura 2.


Figura 2. O Arquivo .swc do Mate na Pasta libs
O Arquivo .swc do Mate



Voltar para parte superior


Construindo um Serviço Simples

O serviço é gravado em PHP e possui somente duas classes: uma classe de serviço que não funciona e uma classe PHP para a qual a classe de UI TimeEntry é mapeada.

Antes de gravar classes de serviços, faça download e instale os arquivos AMFPHP necessários. Para executar esse exemplo, simplesmente instale a pasta amfphp em seu projeto PHP, conforme mostrado abaixo.


Figura 3. A Pasta amfphp
A Pasta amfphp

A classe de serviço é mostrada na Lista 1. A classe de serviço simplesmente grava os valores como um pequeno arquivo XML em um local temporário. Em seu computador, pode ser necessário modificar esse local (em negrito) para que seja gravado com êxito. Você deve alterá-lo para o nome de um arquivo que possa ser lido e gravado em seu servidor da Web.


Lista 1. O Arquivo amfphp/services/ChronoLogManager.php

<?php
require_once('./vo/TimeEntry.php');
/*
 * A service for managing time entries.
 */
class ChronoLogManager
{

    public function saveTimeEntry($entry)
        {

        $myFile = "/tmp/time.xml";
        $fh = fopen($myFile, 'w') or die("can't open file");

        $stringData = "<projects>";
        $stringData .= "<entry project=\"$entry->project\" " .
                    "time=\"$entry->time\" user=\"$entry->username\" />";
        $stringData .= "</projects>";

        fwrite($fh, $stringData);
        fclose($fh);

        }

}

?>

O objeto modelo do lado do serviço, TimeEntry.php, é mostrado na Lista 2. AMFPHP requer os objetos de valor a serem armazenados na pasta services/vo, de forma que o nome do caminho relativo do arquivo PHP da raiz do projeto PHP seja amfphp/service/vo/TimeEntry.php. AMFPHP requer que os nomes de classes e os nomes de arquivos (menos a extensão) sejam correspondentes.


Lista 2. O Arquivo amfphp/services/vo/TimeEntry.php

<?php
class TimeEntry
{
    var $_explicitType = 'model.TimeEntry';

    public $username;
    public $project;
    public $time;
}
?>

Uma vantagem de usar uma classe como um objeto de parâmetro em vez de muitos parâmetros diferentes é que se mais algum campo for incluído no campo de entrada de hora, é possível simplesmente incluir um novo campo no objeto. Uma desvantagem é que qualquer problema encontrado durante o uso de estruturas para sistemas de mensagens provavelmente ocorrerão com tipos complexos. Às vezes, tipos simples (cadeia de caracteres, número inteiro, booleano) são mais fáceis de usar, pois evitam problemas.



Voltar para parte superior


Usando um Objeto Modelo

Um objeto modelo é uma classe ActionScript que contém dados no aplicativo e mapeia para um objeto remoto (TimeEntry.php) na camada de serviços PHP. É possível usar o objeto como um argumento para um método — ou o resultado de um método — sem construir arrays para passar dados entre a UI e os serviços.

Normalmente, ao construir UIs em uma tecnologia e serviços em outra, deve-se gravar ou encontrar uma estrutura que serialize as classes em algum formato para que ambas as tecnologias entendam. XML é frequentemente usado, pois é geralmente fácil de localizar utilitários nas linguagens de programação modernas que leem e gravam de XML.

Neste aplicativo, AMFPHP é usado para mapear os objetos Flex para os objetos PHP remotos. Adobe Message Format (AMF) é o formato da Adobe para serializar e desserializar objetos. Uma vantagem de usar o AMF é que permite conectar a objetos sem precisar gravar seu próprio código de serialização e desserialização entre a UI e os serviços. Há duas estruturas para conectar Flex a PHP via AMF, incluindo um de Zend (consulte Recursos).

O objeto modelo para TimeEntry, que contém as informações usadas para cada campo de entrada de hora, é mostrado na Lista 3. Salve-o na pasta src/chronolog/model do projeto de UI.


Lista 3. O Arquivo src/chronolog/model/TimeEntry.as

package model
{
    [Bindable]
    [RemoteClass(alias='TimeEntry')]
    public class TimeEntry
    {
        private var _username : String;
        private var _project : String;
        private var _time : String;
        
        public function TimeEntry()
        {
        }
        
        public function get username() : String
        {
            return _username;
        }
        
        public function set username(value : String) : void
        {
            _username = value;
        }
        
        public function get project() : String
        {
            return _project;
        }
        
        public function set project(value : String) : void
        {
            _project = value;
        }
        
        public function get time() : String
        {
            return _time;
        }
        
        public function set time(value : String) : void
        {
            _time = value;
        }

    }
}

Além do atributo RemoteObject declarado na classe, é exatamente como qualquer outra classe ActionScript que seria criada para suportar seu aplicativo.



Voltar para parte superior


Construindo um Evento

O Mate é uma estrutura orientada por eventos, portanto, muitos eventos são criados durante o uso de Mate. Um evento Mate é uma classe ActionScript que se estende da base flash.events.Event class. Uma constante da cadeia de caracteres identifica o evento específico e é possível ter uma ou mais constantes em cada classe de eventos. Uso essa capacidade para agrupar eventos de acordo com seu propósito no domínio de negócios. Por exemplo, no exemplo ChronoLog há uma classe TimeEntryEvent . Ela usa a constante SAVE, que representa um evento que deve acionar uma função que salva a entrada de hora. À medida que o projeto cresce, mais eventos da mesma classe podem incluir GET ou DELETE.

Denomine seus eventos usando termos de negócios e mapeie-os para funções de negócios. Não crie uma classe de evento chamada ButtonEvent nem inclua constantes chamadas SAVE_CLICKED. Ao fazer isso, você usa nomenclatura pobre para limitar implicitamente a reutilização de seu evento. Se os usuários mudarem de ideia e um botão for alterado para um link, isso apresenta um problema com eventos que estão amarrados muito fortemente à UI.

Um exemplo da classe ActionScript TimeEntryEvent é mostrada na Lista 4. Além de incluir a constante SAVE , o padrão para o parâmetro bubbles no construtor é alterado para true. Incluo um argumento para o evento como uma variável pública do tipo TimeEntry.


Lista 4. O Arquivo src/chronolog/events/TimeEntryEvent.as

package events
{
    import flash.events.Event;

    import model.TimeEntry;

    public class TimeEntryEvent extends Event
    {
        public static const SAVE : String = 'TimeEntryEvent_SAVE';
        
       public var entry : TimeEntry;
            
        public function TimeEntryEvent(type:String, bubbles:Boolean=true, 
            cancelable:Boolean=false)
        {
            super(type, bubbles, cancelable);
        }
        
    }
}



Voltar para parte superior


Construindo uma Visualização

Além da classe ActionScript, o restante dos arquivos no projeto da UI são componentes Flex. Esses componentes são colocados em arquivos com uma extensão .mxml. Os arquivos MXML são arquivos XML bem-formados, portanto, associo os mesmo no Eclipse ao editor XML para tirar proveito dos recursos do editor XML, como formatação e fechamento de tag. Para criar o arquivo MXML, selecione Arquivo > Novo > Arquivo e forneça a ele o nome apropriado.

Para a UI, o arquivo de aplicativo principal (main.mxml) contém somente a referência para o EventMap principal e para a visualização. A origem do arquivo main.xml é mostrada na Lista 5. O arquivo main.mxml é salvo na pasta src/chronolog do projeto de UI.


Lista 5. O Arquivo src/chronolog/main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    xmlns:maps="maps.*"
    xmlns:views="views.*">
<mx:ViewStack>
    <views:DetailView id="detailView" />
</mx:ViewStack>
</mx:Application>

A visualização detailView é um componente Flex que estende o objeto Flex Panel . Todo o conteúdo da visualização é mostrado abaixo.


Lista 6. O Arquivo src/chronolog/views/DetailView.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"
    width="100%"
    height="100%"
    xmlns:mate="http://mate.asfusion.com/">
<mx:Script>
    <![CDATA[
        import com.asfusion.mate.events.ResponseEvent;
        import mx.controls.Alert;
        import model.TimeEntry;
        import events.TimeEntryEvent;

        [Bindable]
        public var entry : TimeEntry;

        private function save() : void
        {
            entry = new TimeEntry();
            entry.username = 'jdoe';
            entry.project = projectInput.text;
            entry.time = timeInput.text;
        }
        
        private function handleResult(event : ResponseEvent) : void
        {
            Alert.show('Success!');
        }
            
        private function handleFault(event : ResponseEvent) : void
        {
            Alert.show(event.fault.toString());
        }
        
    ]]>
</mx:Script>
    <mx:HBox>
        <mx:FormItem label="Project Number">
            <mx:TextInput id="projectInput" />
        </mx:FormItem>
        <mx:FormItem label="Time">
            <mx:TextInput id="timeInput" />
        </mx:FormItem>
        <mx:Button label="Save" click="save()" />
    </mx:HBox>
</mx:Panel>

A visualização contém dois campos e um botão. Os campos são o número do projeto e um valor que representa o tempo trabalhado. Um nome de usuário, que também é usado no campo de entrada de hora, é codificado permanentemente. À medida que o aplicativo cresce, o nome de usuário vem de dados adquiridos quando o usuário efetua login no aplicativo. Salve a visualização como um arquivo MXML na pasta views como DetailView.mxml.



Voltar para parte superior


Construindo o EventMap

Um componente que estende o EventMap do Mate é usado para mapear os eventos para ações. É aí que os eventos são "conectados" a objetos remotos. Além disso, é possível usar o EventMap para que os eventos chamem outros eventos quando estiverem concluídos, configurem valores nos objetos locais e iniciem métodos localmente.

Especificamente, encadear eventos é poderoso. Ao encadear eventos, é possível evitar a codificação permanente de muitas interações para tornar o projeto mais resiliente para alterar as necessidades comerciais. Por exemplo, se os usuários quiserem que a tela seja alterada para uma visualização diferente após terem salvado dados com êxito, é possível criar um NavigateEvent que faça essa mudança. O evento TimeEntry.SAVE pode efetuar dispatch do NavigateEvent. Dessa forma, se os usuários mudarem de ideia posteriormente, é possível simplesmente atualizar o EventMap para refletir os novos requisitos.

Um exemplo do MainEventMap é mostrado na Lista 7. É simplesmente um arquivo .mxml criado na pasta maps.


Lista 7. O Arquivo src/chronolog/maps/MainEventMap.mxml

<?xml version="1.0" encoding="utf-8"?>
<mate:EventMap 
    xmlns:mate="http://mate.asfusion.com/" 
    xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script>
    <![CDATA[
    import events.TimeEntryEvent;
    ]]>
</mx:Script>
  <mate:EventHandlers type="{TimeEntryEvent.SAVE}">
    <mate:RemoteObjectInvoker destination="amfphp" 
                source="ChronoLogManager" 
                method="saveTimeEntry" 
                arguments="{event.entry}">
      <mate:resultHandlers>
        <mate:ServiceResponseAnnouncer type="result" />
      </mate:resultHandlers>
      <mate:faultHandlers>
        <mate:ServiceResponseAnnouncer type="fault" />
      </mate:faultHandlers>
    </mate:RemoteObjectInvoker>
  </mate:EventHandlers>
</mate:EventMap>

No EventMap, o RemoteObjectInvoker contém as informações usadas para mapear o evento do tipo TimeEntryEvent.SAVE para o método correto (saveTimeEntry) no objeto remoto (ChronoLogManger). A entrada RemoteObjectInvoker também especifica os argumentos — o campo de entrada do evento, conforme observado por {event.entry}. O destino, amfphp, é o ID do destino especificado no arquivo services-config.xml.

Após criar o EventMap, inclua-o no aplicativo principal, conforme mostrado abaixo.


Lista 8. O MainEventMap no Arquivo main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    xmlns:maps="maps.*"
    xmlns:views="views.*">
 <maps:MainEventMap id="eventMap" /> 
<mx:ViewStack>
    <views:DetailView id="detailView" />
</mx:ViewStack>
</mx:Application>



Voltar para parte superior


Efetuando Dispatch do Evento na Visualização

Usando o Mate, é efetuado dispatch dos eventos de muitas maneiras. É possível usar o dispatcher fornecido em cada componente. Também é possível usar o Dispatcher do Mate, que é o que é usado no aplicativo ChronoLog. Um benefício de usar o Dispatcher é que permite configurar facilmente os parâmetros do evento para valores locais, como entrada de texto, sem precisar gravar nenhum código em ActionScript para fazer isso. Além disso, com o Dispatcher, é possível configurar os métodos executados quando o evento é concluído.

O Dispatcher é mostrado em negrito abaixo.


Lista 9. O Dispatcher Mostrado no Arquivo src/chronolog/views/DetailView.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"
    width="100%"
    height="100%"
    xmlns:mate="http://mate.asfusion.com/">
<mx:Script>
    <![CDATA[
        import com.asfusion.mate.events.ResponseEvent;
        import mx.controls.Alert;
        import model.TimeEntry;
        import events.TimeEntryEvent;

        [Bindable]
        public var entry : TimeEntry;

        private function save() : void
        {
            entry = new TimeEntry();
            entry.username = 'jdoe';
            entry.project = projectInput.text;
            entry.time = timeInput.text;
            saveDispatcher.generateEvent();
        }

        private function handleResult(event : ResponseEvent) : void
        {
            Alert.show('Success!');
        }

        private function handleFault(event : ResponseEvent) : void
        {
            Alert.show(event.fault.toString());
        }

    ]]>
</mx:Script>
    <mx:HBox>
        <mx:FormItem label="Project Number">
            <mx:TextInput id="projectInput" />
        </mx:FormItem>
        <mx:FormItem label="Time">
            <mx:TextInput id="timeInput" />
        </mx:FormItem>
        <mx:Button label="Save" click="save()" />
    </mx:HBox>
		
    <mate:Dispatcher id="saveDispatcher" generator="{TimeEntryEvent}"
            type="{TimeEntryEvent.SAVE}">
        <mate:eventProperties>
            <mate:EventProperties entry="{entry}" />
        </mate:eventProperties>
        <mate:ServiceResponseHandler result="handleResult(event)"
            fault="handleFault(event)" />
    </mate:Dispatcher>
		
</mx:Panel>



Voltar para parte superior


Chamando o Serviço

Para chamar o serviço, é necessário configurar a classe PHP no arquivo de configuração do serviço AMFPHP em seu arquivo services-config.xml. O arquivo services-config.xml contém a URL do terminal do serviço que funciona para a estrutura de suporte AMF. O arquivo services-config.xml de exemplo é mostrado abaixo.


Lista 10. O Arquivo src/services-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="amfphp-flashremoting-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">

            <destination id="amfphp">
                <channels>
                    <channel ref="my-amfphp" />
                </channels>
                <properties>
                    <source>*</source>
                </properties>
            </destination>
        </service>
    </services>
    <channels>
        <channel-definition id="my-amfphp"
            class="mx.messaging.channels.AMFChannel">
            <endpoint uri="http://localhost/chronolog/amfphp/gateway.php"
                class="flex.messaging.endpoints.AMFEndpoint" />
        </channel-definition>
    </channels>
</services-config>

Observe que é necessário alterar a URL para o gateway usado por AMFPHP para o que funcionar para seu ambiente.



Voltar para parte superior


Construindo o Projeto

Quando todos os arquivos Flex são configurados, um construtor Ant usa o arquivo mxml.jar fornecido com o Flex SDK para compilar o projeto. O arquivo Ant é mostrado abaixo.


Lista 11. O Arquivo build.xml

<?xml version="1.0"?>
<project name="chronologUI" basedir="." default="build">
    <property name="flex.home" value="/home/ngood/bin/flex3-sdk" />
    <property name="mxmlc.jar" value="${flex.home}/lib/mxmlc.jar" />
    <property name="project.home" value="${basedir}" />
    <property name="project.src" value="${project.home}/src" />
    <property name="build.out" value="${project.home}/bin" />
    <target name="build">
        <java jar="${mxmlc.jar}" fork="true" failonerror="true">
            <jvmarg value="-Xms256m"/>
            <jvmarg value="-Xmx256m"/>
            <arg value="-compiler.source-path=${project.src}" />
            <arg value="+flexlib=${flex.home}/frameworks" />
            <arg value="-compiler.library-path+=${project.home}/libs/Mate_08_8_1.swc" />
            <arg value="-file-specs=${project.src}/chronolog/main.mxml" />
            <arg value="-locale=en_US" />
            <arg value="-services=${project.src}/services-config.xml" />
            <arg value="-compiler.strict=true" />
            <arg value="-warnings=false" />
            <arg value="-output=${build.out}/chronolog.swf" />
        </java>
    </target>
</project>

Após o arquivo build.xml ser incluído no projeto, inclua um Construtor Ant selecionando o projeto chronologUI no Explorador de Projetos e selecionando Projeto > Propriedades. Clique em Novo na janela Propriedades. Selecione Construtor Ant na lista. Na próxima tela (consulte a Figura 4), clique em Navegar pela Área de Trabalho para localizar e selecionar o arquivo build.xml. A configuração do construtor usa automaticamente o destino padrão, para que você possa clicar em Concluir para fechar o navegador. Para obter uma explicação mais completa da criação de seus construtores para projetos, consulte Recursos.


Figura 4. Criando o Construtor Ant
Criando o Construtor Ant

Agora que o construtor foi incluído no projeto, será executado ao selecionar Projeto > Construir no menu no Eclipse.

Como esse exemplo usa AMFPHP, o diretório AMF deve estar lá para a estrutura AMFPHP de suporte. Informações adicionais sobre como configurar e instalar AMFPHP estão em Recursos.



Voltar para parte superior


Executando o Exemplo

Após construir o projeto de UI, coloque o arquivo chronolog.swf sob a raiz do documento do navegador da Web e navegue até ele em seu navegador. Deve ter a aparência do exemplo abaixo.


Figura 5. O Aplicativo Concluído em Execução
O Aplicativo Concluído em Execução

Ao incluir dados e clicar em Salvar, a camada de serviços grava os dados em um pequeno arquivo XML no local especificado no serviço.



Voltar para parte superior


Resumo

Usando Flex SDK, Mate e PHP, é possível construir aplicativos da Web ricos. Com alguns ajustes em sua configuração do projeto Eclipse, é possível usar o Flex SDK para construir aplicativos Flex no Eclipse com o uso do Flex Builder, apesar de os recursos de edição e depuração da UI do WYSIWYG do Flex Builder tornarem a construção de aplicativos Flex muito mais fácil.

Usando o Eclipse PDT e o AMFPHP, é possível construir serviços que podem ser consumidos por seus aplicativos Flex. Ficando remoto — usando AMF — permite gravar eventos rapidamente que chamam métodos em objetos remotos a partir do Flex.



Recursos

Aprender

Obter produtos e tecnologias

Discutir


Sobre o autor

Nathan Good

Nathan A. Good vive na área de Twin Cities em Minnesota. Profissionalmente, ele realiza desenvolvimento de software, arquitetura de software e administração de sistemas. Quando não está gravando software, ele gosta de construir PCs e servidores, ler sobre e trabalhar com novas tecnologias e tentar convencer seus amigos de migrar para software livre. Ele escreveu e foi co-autor de muitos livros e artigos, incluindo Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach e Foundations of PEAR: Rapid PHP Development.




Avalie esta página


Reserve um instante para completar este formulário para nos ajudar a servi-lo melhor.



 


 


Não
são úteis
Extremamente
úteis
 






Voltar para parte superior