Author Image

Ruud Silvrants

Developer

TYPO3 Wizard. Also magical with Web applications. Always want to help. Has finally domesticated Dachshund Meggie.

Tired of performing repetitive actions to start a new project on your local development environment? Help yourself a bit and automate some of this for your standard workflow by using your own script.

Starting a new or existing project usually consists of the following steps:

  • repository clones
  • create database and database user
  • configure database credentials
  • restore backup
  • configure other environment variable / settings
  • composer install
  • npm install
  • .htaccess

These steps can be automated so that they can be executed within a minute and you can get started with the project. The script is based on the package `symfony / console`, the creation of a class e.g.`setupCommand` and an executable php file with which the command can be started.

For more information about symfony / console see: https://symfony.com/doc/current/components/console.html

 

Code example local installation

To the execute method of the setupCommand we initialize a questionHelper that provides support in asking questions and suggestions to the user.

After the base is set to execute a command and to ask any questions, one can start with the questions and own steps to set up a project. Below is a code example on how to quickly set up a local installation for TYPO3. This example can be used for your base to create a "quick start" script.

<?php

namespace BeechIt\LocalEnvironmentSetup\Command;

/*
 * This source file is proprietary property of Beech.it
 * Date: 14-11-2016 13:36
 * All code (c) Beech.it all rights reserved
 */
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;

/**
 * Class SetupCommand
 */
class SetupCommand extends Command
{

    /**
     * Configure the command with the name, description and help text
     */
    protected function configure()
    {
        $this
            ->setName('setup:local')
            ->setDescription('Setup the local environment for the user')
            ->setHelp('This command lets you setup a local environment');
    }

    /**
     * Execute command
     *
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('<info>Welcome to the BeechIt project initializer</info>');
        /** @var QuestionHelper $helper */
        $helper = $this->getHelper('question');

        $defaultRepoUrl = 'default repository url';

        //Which repository should we use for the setup
        $output->writeln('<info>Do you want to create a new project based on  the default?</info>');
        $newProjectQuestion = new ConfirmationQuestion('New project? [No]:', false);
        $newProject = $helper->ask($input, $output, $newProjectQuestion);

        if ($newProject) {
            $output->writeln('<info>Create the new repository in gitlab and enter the url here.</info>');
            $repositoryUrl = $helper->ask($input, $output, new Question(
                'Repository url of new empty git repository:'
            ));
        } else {
            $output->writeln('<info>Please provide the existing repository url.</info>');
            $repositoryUrl = $helper->ask($input, $output, new Question(
                'Repository url of existing project [default]:',
                $defaultRepoUrl
            ));
        }

        // under which directory the repository should exists (default to the repository name)
        $dirSuggestion = preg_replace('/\.git$/i', '', basename($repositoryUrl)) ?: 'default';
        $projectDirName = $helper->ask($input, $output, new Question(
            'Enter the project dirname [' . $dirSuggestion . ']:',
            $dirSuggestion
        ));

        $this->shellExecute('git clone ' . $defaultRepoUrl . ' ' . $projectDirName, $output);

        if ($newProject) {
            $this->shellExecute('cd ' . $projectDirName . '; git remote set-url origin ' . $repositoryUrl, $output);
        }

        // Create the database
        $dbName = '';
        $output->writeln('<info>Please provide a name for the database with user and password to be created.</info>');
        $output->writeln('<comment>The total length of the name is max 16 chars.</comment>');
        while (true) {
            $dbName = $helper->ask($input, $output, new Question(
                'Project DB name/user: '
            ));
            $dbName = trim($dbName);
            if ($this->validDatabaseName($dbName)) {
                break;
            } else {
                $output->writeln('<error>The entered name contains to many characters.</error>');
            }
        }
        $rootUser = $helper->ask($input, $output, new Question('Enter the database root username [root]:', 'root'));
        $passWordQuestion = new Question('Enter the database root password:');
        $passWordQuestion->setHidden(true);
        $passWordQuestion->setHiddenFallback(false);
        $rootPassword = $helper->ask($input, $output, $passWordQuestion);
        $shellOutput = $this->createDatabaseAndUser($output, $rootUser, $rootPassword, $dbName);
        // When there is shell output, a error or message is provided by the database
        // manually check/create the database before continuing
        if ($shellOutput) {
            $output->writeln('<error>There were warnings with creating the database, please fix these errors before continuing.</error>');
            $output->writeln($shellOutput);
            $continue = $helper->ask($input, $output,
                new ConfirmationQuestion('I\'ve fixed the errors and created the database. I can continue with setup local development.'));
            if (!$continue) {
                return 10;
            }
        }

        // Initialize correct typo3 environment settings based on your TYPO3 version
        $typoVersion = $helper->ask($input, $output, new Question('TYPO3 Version 7/8 [8]:', '8'));
        $this->createEnvironmentFile($projectDirName, $dbName, $typoVersion);
        $projectPath = realpath('.') . '/' . $projectDirName . '/';

        //Setup the local/development .htaccess
        $this->shellExecute(
            'ln -s ' . $projectPath . 'Web/.htaccess_development ' . $projectPath . '/Web/.htaccess',
            $output
        );

        //Initialize dependencies and restore a backup
        $this->shellExecute('composer install -d ' . $projectPath, $output);
        $this->shellExecute($projectPath . 'typo3cms backup:restore dummy --plain-restore', $output);
        $this->shellExecute('cd ' . $projectDirName . '; npm install', $output);

        $output->writeln('<info>The project ' . $projectDirName . ' is created under path ' . $projectPath);

        return 0;
    }

    /**
     * Execute commands and output command info when in verbose mode
     *
     * @param $cmd
     * @param OutputInterface $output
     */
    private function shellExecute($cmd, $output)
    {
        if ($output->isVerbose()) {
            $output->writeln('<info>Execute cmd: ' . $cmd . '</info>');
        }
        shell_exec($cmd);
    }

    /**
     * Checks if the databaseName is valid
     *
     * @param $projectDatabase
     * @return bool
     */
    private function validDatabaseName($projectDatabase)
    {
        return !(strlen($projectDatabase) > 16);
    }

    /**
     * Create the database and user with correct permissions and utf set
     * For easier setup at your local enviroment databasename and username is the same
     *
     * @param OutputInterface $output
     * @param $userName
     * @param $password
     * @param $newDatabaseName
     * @return string
     */
    protected function createDatabaseAndUser($output, $userName, $password, $newDatabaseName)
    {
        $createDatabaseUser = 'CREATE USER \'' . $newDatabaseName . '\'@\'localhost\' IDENTIFIED BY \'' . $newDatabaseName . '\';';
        $grantUsages = 'GRANT USAGE ON * . * TO \'' . $newDatabaseName . '\'@\'localhost\'  WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;';
        $createDatabase = 'CREATE DATABASE IF NOT EXISTS ' . $newDatabaseName . ' DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;';
        $grantPrivileges = 'GRANT ALL PRIVILEGES ON  ' . $newDatabaseName . ' . * TO  \'' . $newDatabaseName . '\'@\'localhost\';';
        $flushPrivileges = 'FLUSH PRIVILEGES;';

        return $this->shellExecute(
            'mysql -u' . $userName . ' -p' . $password .
            ' -e "' . $createDatabaseUser . $grantUsages . $createDatabase . $grantPrivileges . $flushPrivileges . '"',
            $output
        );
    }

    /**
     * Creates the environmentFile for local development
     *
     * @param $projectPath
     * @param $dbName
     */
    private function createEnvironmentFile($projectPath, $dbName, $typoVersion)
    {
        $envFilePath = $projectPath . '/.env';
        if (!file_exists($envFilePath)) {
            if ((int)$typoVersion === 8) {
                file_put_contents($envFilePath,
                    'TYPO3_CONTEXT="Development"' . PHP_EOL .
                    PHP_EOL .
                    PHP_EOL .
                    '# Credentials' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__dbname="' . $dbName . '"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__password="' . $dbName . '"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__host="localhost"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__user="' . $dbName . '"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__port="3306"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__driver="mysqli"' . PHP_EOL .
                    'TYPO3__DB__Connections__Default__charset="utf8"' . PHP_EOL .

                    PHP_EOL .
                    '# Host specifics' . PHP_EOL .
                    PHP_EOL .
                    '## graphicsmagick path' . PHP_EOL .
                    'TYPO3__GFX__processor_path="/usr/local/bin/"' . PHP_EOL .
                    'TYPO3__GFX__processor_path_lzw="/usr/local/bin/"' . PHP_EOL .
                    PHP_EOL .
                    '# Secrets' . PHP_EOL .
                    'TYPO3__SYS__encryptionKey="PROVIDE_ENCRYPTION_KEY"' . PHP_EOL .
                    'TYPO3__BE__installToolPassword="INSTALL_TOOL_PASSWORD_HASH"' . PHP_EOL .
                    PHP_EOL
                );
            } else {
                file_put_contents($envFilePath,
                    'TYPO3_CONTEXT="Development"' . PHP_EOL .
                    PHP_EOL .
                    PHP_EOL .
                    '# Credentials' . PHP_EOL .
                    'TYPO3__DB__database="' . $dbName . '"' . PHP_EOL .
                    'TYPO3__DB__host="localhost"' . PHP_EOL .
                    'TYPO3__DB__password="' . $dbName . '"' . PHP_EOL .
                    'TYPO3__DB__port="3306"' . PHP_EOL .
                    'TYPO3__DB__username="' . $dbName . '"' . PHP_EOL .
                    PHP_EOL .
                    '# Host specifics' . PHP_EOL .
                    PHP_EOL .
                    '## graphicsmagick path' . PHP_EOL .
                    'TYPO3__GFX__im_path="/usr/local/bin/"' . PHP_EOL .
                    'TYPO3__GFX__im_path_lzw="/usr/local/bin/"' . PHP_EOL .
                    PHP_EOL .
                    '# Secrets' . PHP_EOL .
                    'TYPO3__SYS__encryptionKey="PROVIDE_ENCRYPTION_KEY"' . PHP_EOL .
                    'TYPO3__BE__installToolPassword="INSTALL_TOOL_PASSWORD_HASH"' . PHP_EOL .
                    PHP_EOL
                );
            }
        }
    }
}php

Read more