Skip to content

Get started

Installation

Before Installation:

Before you can install and use enhavo, your local environment must fulfill some (really just some) basic requirements. First of all, you need the composer, a dependency management tool, in php, downloadable at getcomposer.org

The second necessary tool is "yarn", another powerful JavaScript dependency management tool, which you will find on this page

That's it! After you have installed these two tools, your system is ready for action.

Install Enhavo within 5 Minutes.

The Enhavo App Edition only contains basic admin features, use this, if your application is not used without standard content management features to create a project with enhavo, you just need to run the following composer command.

bash
$ composer create-project enhavo/enhavo-app project-name dev-master

Now, only a few terminal-commands are left (take care, that you are in your created project folder dir for all following commands). Use

bash
$ yarn install

for the installation of all JavaScript dependencies managed by yarn. With

bash
$ yarn encore dev

you will compile your assets once to a single final app.js-File which includes everything your app needs (Vue.js, Sass, TypeScript etc.)

Yarn is also responsible for managing the project's routes. You have to update them after each route-change with the following command:

bash
$ yarn routes:dump

Now you need to create the configuration file. Just create a file with the name .env.local in your project dir. Paste the following content and edit your database setting.

APP_ENV=dev
APP_DEBUG=true
DATABASE_URL=mysql://root:root@127.0.0.1:3306/enhavo

Make sure your database exists or create it by following command

bash
$ bin/console doctrine:database:create

Now you need to create the database schema

bash
$ bin/console doctrine:schema:update --force

The finale installation steps are initializing Enhavo once and creating your first backend user account with super-admin permissions.

bash
$ bin/console enhavo:init
$ bin/console fos:user:create my@email.com my@email.com password --super-admin

Launching Project

So far, so good. The installation is complete and you're ready to launch your empty base-application.

You can run this project on any webserver (like apache, nginx, etc.), but for testing reasons, the fastest way to start your application for the first time is using the PHP´s build-in web-server.

Start that build-in server with

bash
$ php bin/console server:run

and see the result in your browser under http://127.0.0.1:8000/admin

Use the username and password from the user account, you´ve created before with fos:user:create, to log in.

Final Words

Great! We've installed the basic, really basic, enhavo CMS with our two awesome dependency management tools composer and yarn.

Well-intentioned Advices

  • During the complete developing process, it´s better to recompile assets automatically when files change, to do that, use:
bash
$ yarn encore dev --watch
  • If you want to launch your application with any other web server, use the ~/PathToYourProject/YourProject/public - Folder as your document root.

Create Entity

Creating an Entity Class

Suppose you´re building an application where products with three properties id, title and price need to be created as a Product object. This class has to be in the directory src/Entity and the namespace is App\Entity;

At this point, our object class looks like:

php
<?php
// src/Entity/Product.php

namespace App\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;

class Product implements ResourceInterface
{
    protected $id;

    public function getId()
    {
        return $this->id;
    }
}

As you can see, this class also implements a interface called ResourceInterface . Why we do that, will be explained later, but it has to be mentioned now, because it is the reason, why our Product class needs the id property. This id will be used as our primary key in our database table and the getId()-Function is the only function we need, to include this interface.

For the other two properties, we also need a variable and as common in classes, each of them has its own public getter and setter methods.

To define the database type of the variables, we use annotations. Our id as primary key has to be unique, the best datatype is an integer. The title of a product is usually a word, so we mark the title as string. The price can be an integer, because we can save 1.78 USD as 178¢.

If we want to save an entity in a database-table, we need a column for each class-property we want to save. For our example the columns would be id, title and price. To create and map them we will use one more time annotations. In that case, they start with @ORM\...

Let's take a look at our code:

php
<?php
// src/Entity/Product.php

namespace App\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ProductRepository")
 * @ORM\Table(name="app_product")
 */
class Product implements ResourceInterface
{
    /**
     * @var integer
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $title;

    /**
     * @var float
     * @ORM\Column(type="integer", nullable=true)
     */
    private $price;


    /**
    * @return int
    */
    public function getId()
    {
        return $this->id;
    }

    /**
    * @return string
    */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set title
     * @param string $title
     * @return Product
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }

    /**
     * @return int
     */
    public function getPrice()
    {
        return $this->price;
    }

    /**
     * Set price
     * @param integer $price
     * @return Product
     */
    public function setPrice($price)
    {
        $this->price = $price;
        return $this;
    }
}

We´ve already talked about the annotations for our attributes. We can also use PHP-annotations for functions, as you can see in our example. For more information about annotations, take a look at this documentation.

Or for the doctrine annotations, have a look at the doctrine annotation reference

An optional, but important annotation is @ORM\Table, which defines the table name for this entity. A good structured and well-named database is always a goal which should be sought.

One step is mapping all properties of the entity to columns in the table. We can do this with @ORM\Column(type="integer"). Other common datatypes are string, float, boolean etc. You can find a full list and way more about basic mapping in doctrine here.

Another option for the column is, if the value in the column can be NULL or not. We define that with nullable=true/false (the default value is false).

The id needs some special annotations, for example @ORM\Id, which mark this property as primary key in a table and @ORM\GeneratedValue(strategy="AUTO") specifies which strategy is used for identifier generation for an instance variable which is annotated by id.

At this page, a reference of all Doctrine annotations is given with short explanations on their context and usage.

Awesome! We´ve just created our first PHP-class, which is also called Entity in Symfony.

Creating an Repository Class

Our next step is, how we can easily save our entity in our database, with the powerful Doctrine ORM, which helps us to manage our database and synchronize it with our project.

Before we mark our entity class with @ORM\Entity and define the repositoryClass, which we will need for more complex database queries, in order to isolate, reuse and test these queries, it's a good practice to create this custom repository class for your entity.

The common path for the Repository-classes are src/Repository.

php
<?php
// src/Repository/ProductRepository

namespace App\Repository;

use Enhavo\Bundle\AppBundle\Repository\EntityRepository;

class ProductRepository extends EntityRepository
{

}

An empty Repository is very unspectacular, but we will learn how useful they can be later.

Update Database

After this, we have a useable Product class with all important information for Doctrine to create the product table. But after all, we still have no table in our database, but creating it is very comfortable now, just run:

$ php bin/console doctrine:schema:update --force

It seems to be nothing special, but this command does a lot! It checks, how your database should look like (based on the mapping information we´ve defined with the annotations in our product class before) and compares it with how the database actually looks like. Only the differences will be executed as SQL statements to update the database.

Well-intentioned Advices

An even better way to synchronize your database with the mapping information from your project is via migrations, which are as powerful as the schema:update command. In addition, changes to your database schema are safely and reliably tracked and reversible.

Even it is quite powerful, the doctrine:schema:update command should only be used during development.

WARNING

It should never be used in a production environment with important information in your database.

You can also create or update an entity with the command:

$ php bin/console make:entity

which will ask you everything you need to create or update an entity. You will find a good explanation in the Symfony Docs , but for the first time, we recommend to create your classes without this command, to understand how they work.

Create Form

Creating a Form

After we created our entity now we want to create our form, so later we can edit it in the admin interface. For this task, we need to build a form, which translate data from an object to a in HTML printable form, so a user can modify this data. After this modification, submitted by the user, this data has to be validated and, if possible, converted back to a data object.

At the beginning, focus on this Product class we´ve created in the chapter Create Entity before.

Symfony comes with many built-in types which are listed on this page https://symfony.com/doc/4.2/forms.html#forms-type-reference and also Enhavo offers a variety of build-in forms specially new designed or customized for Enhavo.

Usually form are stored under src/Form/Type

php
<?php
// src/Form/Type/ProductType.php

namespace App\Form\Type;

use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class, array(
                'label' => 'Title'
            ))
            ->add('price', CurrencyType::class, array(
                'label' => 'Price'
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults( array(
            'data_class' => Product::class,
        ));
    }
}

Creating a form with the FormBuilder is quite comfortable and requires relatively less code. With the FormBuilder, we just have to specify some important properties and individual settings for each property of our Product object, and it will do the hard work for us: building the form.

We know, that the Product object has two properties (title and price), so we add two fields with these properties to our form. We also set a type (e.g. TextType and CurrencyType) which determines which kind of HTML form tag(s) will be rendered for that field.

As third parameter the add method from the FormBuilderInterface expects an array. Inside this array, we can assign individual values for many options. Build-in Symfony Form Types have different numbers and types of options depending on its functionality. In our example we defined custom labels for each Form Field.

Symfony comes with many built-in types which are listed on this page.

As an example of available options here a link to the options of the very common TextType

Even it´s not always (but very often) necessary, it´s generally a good practice to specify the data_class, which represents the class that holds the underlying data (e.g. AppEntityProduct).

Creating a Service

Now, when our separate form class is complete, we´re close to success. The last thing we have to do is to create a service (if you´ve never heard about services/Dependency Injection, check the Symfony Service Container Documentation) for our form, so we can use it everywhere in our application.

First, we create a yaml-file in the directory [config/services/forms.yaml]{.title-ref}`, for this and all future form services. Of course, you can name it completely different e.g. my_services.yml and you can also use one yaml-file for many different service-types, but in many applications, we will need a lot of form services, so it is a good practice to separate services by type in their own files.

yaml
# config/services/forms.yaml
services:
    App\Form\Type\ProductType:
        tags:
            - { name: form.type }

To tell your application something about your new separate service yaml file, we have to import it inside our config/services.yaml file with this simple line at the end of the file:

yaml
# config/services.yaml
parameters:
    locale: 'en'

services:
    _defaults:
        # Automatically injects dependencies in your services.
        autowire: true
        # Automatically registers your services as commands, event subscribers, etc.
        autoconfigure: true

# add this lines to your file
imports:
    - { resource: services/forms.yaml }

Final words

That's it! We ´ve created a simple symfony form and service in the most flexible way, so we can use it everywhere in our application and reuse it as often as we like. In the next chapter, we will see, how all previous developed parts of our application can be connected and be part of our first Enhavo resource.

Routing and Configuration

Add configuration

The next step is adding the new resource consisting of the Product entity and the associated ProductType form to the Enhavo configuration. In the config/packages/enhavo.yml we need to add the menu entry as well, so we can navigate to our product. There are already some default types for the menu, used by the Enhavo standard menu items. In order to add our product to the menu we should use the base type, which we extend with details of our product:

yaml
# config/packages/enhavo.yml

parameters:
    locale: en

enhavo_app:
    menu:
        dashboard:
            type: dashboard
        user:
            type: user_user
        group:
            type: user_group
        # add this lines for the menu entry
        product:
            type: base
            label: Product
            route: app_product_index
            icon: widgets

# add this lines to the end of the file
sylius_resource:
    resources:
        app.product:
            classes:
                model: App\Entity\Product
                controller: Enhavo\Bundle\AppBundle\Controller\ResourceController
                form: App\Form\Type\ProductType
                repository: App\Repository\ProductRepository

Generate routes

After we add our new resource to the configuration, we still have to connect the resource, form and actions (Create, View, Edit, Delete) with the still existing Resource Controller. Normally, that would be much work, but Enhavo gives us a Command, which generates the basic routing after some simple questions.

bash
$ bin/console make:enhavo:routing

 What is the name of the resource?:
 > Product

 What is the bundle name? Type "no" if no bundle is needed:
 > no

 Is the resource sortable? [yes/no]:
 > no

 created: config/routes/admin/product.yaml

The routes will now be saved automatically to config/routes/admin/product.yaml. Later we will lear how we can edit this file to customize our user interface.

We have to dump all new routes with the following command, otherwise, they are not in our public application web folder and Enhavo won´t find them.

bash
$ yarn routes:dump

Finished

Now we have done all steps to add our own model to enhavo. Just start your webserver again if it's not running and login into the admin.

bash
$ php bin/console server:run

See the result in your browser under http://127.0.0.1:8001/admin. You should able to create and edit products.