How to Submit an External Form using PHP

Recently, I published an article on website scraping using PHP. During the research, I found that Symfony components can do much more apart from website scraping. Though the list is long, I mentioned a few interesting stuff below.

  • Crawl the website and click on links programmatically.
  • Retrieve and set cookies on an external website.
  • Programmatically submit forms on the external website.

In this article, I will show you how to submit an external form using PHP. We’ll submit a form without visiting it on a browser. To achieve this task, we require to install below 3 Symfony Components.

Head over to the terminal and run the commands given below to install these components.

composer require symfony/browser-kit
composer require symfony/http-client
composer require symfony/mime

From the above components, the BrowserKit is the one that replicates the behavior of a web browser through which we can submit a form programmatically.

Setup the Environment

To illustrate the task, let’s create a simple environment. Create a form with a few input fields and a submit button.

form.php

<?php
if (isset($_POST['submit'])) {
    extract($_POST);
    file_put_contents('test.txt', print_r([$fullname, $email], true));

    if(isset($_FILES['file']['name'])) {
        if(!file_exists('uploads')) {
            mkdir('uploads', 0777);
        }
        move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/'.$_FILES['file']['name']);
    }
}
?>

<form method="post">
    <p>
        <input type="text" name="fullname" />
    </p>
    <p>
        <input type="email" name="email" />
    </p>
    <p>
        <input type="file" name="file" />
    </p>
    <input type="submit" name="submit" value="Submit" />
</form>

When this form is submitted, it creates a test.txt file and writes data to the file posted through the form. If an attachment is provided, it stores the attachment inside the uploads directory.

Now, we will write a script that posts data to this form and hits the submit button programmatically.

Implementation of a Browser

To send the request on the external website, we need to write a code that implements a browser using the HTTP Client component.

Create a config.php file and add the code for the HTTP Client to it. I am copying this code as provided by the BrowserKit component.

<?php

/*
 * This file is part of the Symfony package.
 *
 */

namespace Symfony\Component\BrowserKit;

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Mime\Part\AbstractPart;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Component\Mime\Part\TextPart;
use Symfony\Contracts\HttpClient\HttpClientInterface;

require_once "vendor/autoload.php";

/**
 * An implementation of a browser using the HttpClient component
 * to make real HTTP requests.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HttpBrowser extends AbstractBrowser
{
    private $client;

    public function __construct(HttpClientInterface $client = null, History $history = null, CookieJar $cookieJar = null)
    {
        if (!$client && !class_exists(HttpClient::class)) {
            throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
        }

        $this->client = $client ?? HttpClient::create();

        parent::__construct([], $history, $cookieJar);
    }

    /**
     * @param Request $request
     */
    protected function doRequest(object $request): Response
    {
        $headers = $this->getHeaders($request);
        [$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers);

        $response = $this->client->request($request->getMethod(), $request->getUri(), [
            'headers' => array_merge($headers, $extraHeaders),
            'body' => $body,
            'max_redirects' => 0,
        ]);

        return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
    }

    /**
     * @return array [$body, $headers]
     */
    private function getBodyAndExtraHeaders(Request $request, array $headers): array
    {
        if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) {
            return ['', []];
        }

        if (!class_exists(AbstractPart::class)) {
            throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
        }

        if (null !== $content = $request->getContent()) {
            if (isset($headers['content-type'])) {
                return [$content, []];
            }

            $part = new TextPart($content, 'utf-8', 'plain', '8bit');

            return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()];
        }

        $fields = $request->getParameters();

        if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
            $part = new FormDataPart(array_merge($fields, $uploadedFiles));

            return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
        }

        if (empty($fields)) {
            return ['', []];
        }

        array_walk_recursive($fields, $caster = static function (&$v) use (&$caster) {
            if (\is_object($v)) {
                if ($vars = get_object_vars($v)) {
                    array_walk_recursive($vars, $caster);
                    $v = $vars;
                } elseif (method_exists($v, '__toString')) {
                    $v = (string) $v;
                }
            }
        });

        return [http_build_query($fields, '', '&'), ['Content-Type' => 'application/x-www-form-urlencoded']];
    }

    protected function getHeaders(Request $request): array
    {
        $headers = [];
        foreach ($request->getServer() as $key => $value) {
            $key = strtolower(str_replace('_', '-', $key));
            $contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
            if (str_starts_with($key, 'http-')) {
                $headers[substr($key, 5)] = $value;
            } elseif (isset($contentHeaders[$key])) {
                // CONTENT_* are not prefixed with HTTP_
                $headers[$key] = $value;
            }
        }
        $cookies = [];
        foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
            $cookies[] = $name.'='.$value;
        }
        if ($cookies) {
            $headers['cookie'] = implode('; ', $cookies);
        }

        return $headers;
    }

    /**
     * Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
     * Keep the original hierarchy.
     */
    private function getUploadedFiles(array $files): array
    {
        $uploadedFiles = [];
        foreach ($files as $name => $file) {
            if (!\is_array($file)) {
                return $uploadedFiles;
            }
            if (!isset($file['tmp_name'])) {
                $uploadedFiles[$name] = $this->getUploadedFiles($file);
            }
            if (isset($file['tmp_name'])) {
                $uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
            }
        }

        return $uploadedFiles;
    }
}

Submit External Form using PHP

By using the HttpBrowser class written above we are able to make requests. We first crawl the website and then submit a form providing fields given in the HTML form.

form-submit.php

<?php
require_once "config.php";

use Symfony\Component\BrowserKit\HttpBrowser;

$client = new HttpBrowser();
$crawler = $client->request('GET', 'YOUR_URL/form.php');

// find the form with the 'submit' button and submit it
// 'submit' can be the text content, id, value or name of a <button> or <input type="submit">
$client->submitForm('submit', [
    'fullname' => 'Sam Doe',
    'email' => 'sam@test.com',
    'file' => getcwd().'/1.jpg'
]);

In the above code, the first parameter used for submitForm() methods is ‘submit’ which is the name of a button that we wish to click.

The keys fullname, email and file are also the names of respected input fields. To the file key, it needs to pass the absolute file path.

That’s it! Now run this form-submit.php on a browser and you should see the external form is submitted successfully. You can confirm it by looking into the test.txt file and your image should be stored inside the uploads folder.

Related Articles

If you liked this article, then please subscribe to our YouTube Channel for video tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *