Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

docs: add reasoning example #53

Merged
merged 1 commit into from
Sep 26, 2024
Merged

docs: add reasoning example #53

merged 1 commit into from
Sep 26, 2024

Conversation

chr-hertel
Copy link
Member

@chr-hertel chr-hertel commented Sep 26, 2024

Output:

Plan for the Directory Structure

To build the Symfony application that meets the requirements, we'll structure the directories and files as follows:

  • src/
    • Controller/
      • QuestionController.php - Handles the HTTP requests and responses.
    • Entity/
      • Question.php - The entity representing the question and answer.
    • Repository/
      • QuestionRepository.php - Custom repository for the Question entity.
    • Service/
      • QuestionMatcher.php - Contains the logic for matching questions.
  • templates/
    • question/
      • index.html.twig - Template for the main question form and displaying answers.
      • ask.html.twig - Template for asking the user to provide an answer.
  • config/
    • packages/
      • doctrine.yaml - Database configuration (if not using .env).
    • routes.yaml - Defines the application routes.

Each File in Full

Below are the contents of each file needed for the application.


src/Entity/Question.php

<?php

namespace App\Entity;

use App\Repository\QuestionRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: QuestionRepository::class)]
class Question
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(type: 'text')]
    private ?string $questionText = null;

    #[ORM\Column(type: 'text')]
    private ?string $answerText = null;

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

    public function getQuestionText(): ?string
    {
        return $this->questionText;
    }

    public function setQuestionText(string $questionText): self
    {
        $this->questionText = $questionText;

        return $this;
    }

    public function getAnswerText(): ?string
    {
        return $this->answerText;
    }

    public function setAnswerText(string $answerText): self
    {
        $this->answerText = $answerText;

        return $this;
    }
}

src/Repository/QuestionRepository.php

<?php

namespace App\Repository;

use App\Entity\Question;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class QuestionRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Question::class);
    }

    public function add(Question $entity, bool $flush = false): void
    {
        $this->_em->persist($entity);
        if ($flush) {
            $this->_em->flush();
        }
    }

    public function remove(Question $entity, bool $flush = false): void
    {
        $this->_em->remove($entity);
        if ($flush) {
            $this->_em->flush();
        }
    }

    /**
     * Finds questions that closely match the input question.
     *
     * @param string $input
     * @return Question[]
     */
    public function findSimilarQuestions(string $input): array
    {
        return $this->createQueryBuilder('q')
            ->where('LOWER(q.questionText) LIKE :input')
            ->setParameter('input', '%' . strtolower($input) . '%')
            ->getQuery()
            ->getResult();
    }
}

src/Service/QuestionMatcher.php

<?php

namespace App\Service;

use App\Entity\Question;
use App\Repository\QuestionRepository;

class QuestionMatcher
{
    private QuestionRepository $questionRepository;

    public function __construct(QuestionRepository $questionRepository)
    {
        $this->questionRepository = $questionRepository;
    }

    public function findBestMatch(string $inputQuestion): ?Question
    {
        $similarQuestions = $this->questionRepository->findSimilarQuestions($inputQuestion);

        if (!empty($similarQuestions)) {
            // Return the first match or implement more sophisticated matching logic.
            return $similarQuestions[0];
        }

        return null;
    }
}

src/Controller/QuestionController.php

<?php

namespace App\Controller;

use App\Entity\Question;
use App\Repository\QuestionRepository;
use App\Service\QuestionMatcher;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

use Symfony\Component\Routing\Annotation\Route;

class QuestionController extends AbstractController
{
    #[Route('/', name: 'question_index', methods: ['GET', 'POST'])]
    public function index(
        Request $request,
        QuestionMatcher $questionMatcher
    ): Response {
        if ($request->isMethod('POST')) {
            $inputQuestion = $request->request->get('question');

            $matchedQuestion = $questionMatcher->findBestMatch($inputQuestion);

            if ($matchedQuestion) {
                $answer = $matchedQuestion->getAnswerText();
                return $this->render('question/index.html.twig', [
                    'question' => $inputQuestion,
                    'answer' => $answer,
                ]);
            } else {
                return $this->redirectToRoute('question_ask', [
                    'question' => $inputQuestion,
                ]);
            }
        }

        return $this->render('question/index.html.twig');
    }

    #[Route('/ask', name: 'question_ask', methods: ['GET', 'POST'])]
    public function ask(
        Request $request,
        EntityManagerInterface $entityManager
    ): Response {
        $inputQuestion = $request->query->get('question', '');

        if ($request->isMethod('POST')) {
            $inputQuestion = $request->request->get('question');
            $inputAnswer = $request->request->get('answer');

            $question = new Question();
            $question->setQuestionText($inputQuestion);
            $question->setAnswerText($inputAnswer);

            $entityManager->persist($question);
            $entityManager->flush();

            return $this->redirectToRoute('question_index');
        }

        return $this->render('question/ask.html.twig', [
            'question' => $inputQuestion,
        ]);
    }
}

templates/question/index.html.twig

{% extends 'base.html.twig' %}

{% block title %}Question Answering System{% endblock %}

{% block body %}
    <h1>Ask a Question</h1>

    <form method="post">
        <input type="text" name="question" placeholder="Enter your question" required style="width: 50%;">
        <button type="submit">Ask</button>
    </form>

    {% if answer is defined %}
        <h2>Answer:</h2>
        <p>{{ answer }}</p>
    {% elseif question is defined %}
        <p>No matching answer found for your question.</p>
    {% endif %}
{% endblock %}

templates/question/ask.html.twig

{% extends 'base.html.twig' %}

{% block title %}Provide an Answer{% endblock %}

{% block body %}
    <h1>No Answer Found</h1>
    <p>Please provide an answer to the following question:</p>

    <form method="post">
        <input type="hidden" name="question" value="{{ question }}">
        <p><strong>{{ question }}</strong></p>
        <textarea name="answer" placeholder="Enter the answer" required style="width: 50%; height: 100px;"></textarea><br>
        <button type="submit">Submit Answer</button>
    </form>
{% endblock %}

templates/base.html.twig

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Symfony App{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

config/routes.yaml

controllers:
    resource: '../src/Controller/'
    type: annotation

.env

Make sure your .env file contains the database connection parameters:

DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=8.0"

composer.json

Ensure the required packages are included (standard for Symfony 6):

{
    "require": {
        "php": ">=8.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/console": "6.2.*",
        "symfony/dotenv": "6.2.*",
        "symfony/flex": "^2",
        "symfony/framework-bundle": "6.2.*",
        "symfony/runtime": "6.2.*",
        "symfony/yaml": "6.2.*",
        "doctrine/orm": "^2.12",
        "doctrine/doctrine-bundle": "^2.7",
        "symfony/twig-bundle": "6.2.*",
        "symfony/asset": "6.2.*",
        "symfony/validator": "6.2.*"
    },
    "require-dev": {
        "symfony/web-profiler-bundle": "6.2.*",
        "symfony/var-dumper": "6.2.*",
        "symfony/debug-bundle": "6.2.*"
    }
}

Additional Notes:

  1. Database setup: Run the following commands to create the database and the schema:

    php bin/console doctrine:database:create
    php bin/console doctrine:schema:update --force
  2. Twig Templates: Ensure that Twig is installed and configured properly in config/packages/twig.yaml:

    twig:
        default_path: '%kernel.project_dir%/templates'
  3. Services Configuration: Symfony will autowire services by default. If not, register QuestionMatcher in config/services.yaml:

    services:
        App\Service\QuestionMatcher:
            arguments:
                $questionRepository: '@App\Repository\QuestionRepository'
  4. Doctrine Configuration: Ensure Doctrine is configured in config/packages/doctrine.yaml.

    doctrine:
        dbal:
            driver: 'pdo_mysql'
            server_version: '8.0'
            charset: utf8mb4
            url: '%env(resolve:DATABASE_URL)%'
        orm:
            auto_generate_proxy_classes: true
            naming_strategy: doctrine.orm.naming_strategy.underscore
            auto_mapping: true

Conclusion

This directory structure and the corresponding files provide a basic Symfony application that fulfills the requirements. The QuestionController handles user interactions, the Question entity maps questions and answers to the database, and the QuestionMatcher service contains the logic to find matching questions. The Twig templates render the forms and display answers to the user. By following this structure, the application remains organized, scalable, and adheres to Symfony best practices.

@OskarStark
Copy link
Contributor

Wow 😮

@chr-hertel
Copy link
Member Author

Wow 😮

exactly

@chr-hertel chr-hertel merged commit 04b4abb into main Sep 26, 2024
5 checks passed
@chr-hertel chr-hertel deleted the feat-reasoning-example branch September 26, 2024 19:43
@chr-hertel chr-hertel added the documentation Improvements or additions to documentation label Mar 6, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants